Compare commits
12
Commits
9141a226b5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c14a25fbc2 | ||
|
|
93b37907f3 | ||
|
|
42cb19214f | ||
|
|
0646ebc59b | ||
|
|
2bc500723f | ||
|
|
c3a8c568a5 | ||
|
|
defaaf1b7f | ||
|
|
48d667b064 | ||
|
|
48a39a77f4 | ||
|
|
72bdbe083e | ||
|
|
881c6c773c | ||
|
|
52ad4dea00 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# For more information, please see the README.md
|
# For more information, please see the README.md
|
||||||
SUNO_COOKIE=
|
SUNO_COOKIE=
|
||||||
TWOCAPTCHA_KEY= # Obtain from 2captcha.com
|
TWOCAPTCHA_KEY= # Obtain from 2captcha.com
|
||||||
BROWSER=chromium # chromium or firefox
|
BROWSER=chromium # `chromium` or `firefox`, although `chromium` is highly recommended
|
||||||
BROWSER_GHOST_CURSOR=false
|
BROWSER_GHOST_CURSOR=false
|
||||||
BROWSER_LOCALE=en
|
BROWSER_LOCALE=en
|
||||||
BROWSER_HEADLESS=true
|
BROWSER_HEADLESS=true
|
||||||
+14
-8
@@ -1,25 +1,31 @@
|
|||||||
# syntax=docker/dockerfile:1
|
|
||||||
|
|
||||||
FROM node:lts-alpine AS builder
|
FROM node:lts-bookworm AS builder
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:lts-alpine
|
FROM node:lts-bookworm
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libnss3 \
|
||||||
|
libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
|
||||||
|
libgbm1 libxkbcommon0 libasound2 libcups2 xvfb
|
||||||
|
|
||||||
ARG SUNO_COOKIE
|
ARG SUNO_COOKIE
|
||||||
ARG BROWSER
|
RUN if [ -z "$SUNO_COOKIE" ]; then echo "Warning: SUNO_COOKIE is not set. You will have to set the cookies in the Cookie header of your requests."; fi
|
||||||
RUN if [ -z "$SUNO_COOKIE" ]; then echo "Warning: SUNO_COOKIE is not set"; fi
|
|
||||||
ENV SUNO_COOKIE=${SUNO_COOKIE}
|
ENV SUNO_COOKIE=${SUNO_COOKIE}
|
||||||
RUN if [ -z "$BROWSER" ]; then echo "Warning: BROWSER is not set; will use chromium by default"; fi
|
# Disable GPU acceleration, as with it suno-api won't work in a Docker environment
|
||||||
ENV BROWSER=${BROWSER:-chromium}
|
ENV BROWSER_DISABLE_GPU=true
|
||||||
|
|
||||||
RUN npm install --only=production
|
RUN npm install --only=production
|
||||||
RUN npx playwright install $BROWSER
|
|
||||||
|
# Install all supported browsers, else switching browsers requires an image rebuild
|
||||||
|
RUN npx playwright install chromium
|
||||||
|
# RUN npx playwright install firefox
|
||||||
|
|
||||||
COPY --from=builder /src/.next ./.next
|
COPY --from=builder /src/.next ./.next
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["npm", "run", "start"]
|
CMD ["npm", "run", "start"]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h1 align="center"">
|
<h1 align="center">
|
||||||
Suno AI API
|
Suno AI API
|
||||||
</h1>
|
</h1>
|
||||||
<p>Use API to call the music generation AI of Suno.ai and easily integrate it into agents like GPTs.</p>
|
<p>Use API to call the music generation AI of Suno.ai and easily integrate it into agents like GPTs.</p>
|
||||||
@@ -64,7 +64,11 @@ We have deployed an example bound to a free Suno account, so it has daily usage
|
|||||||
|
|
||||||
[Create](https://2captcha.com/auth/register?userType=customer) a new 2Captcha account, [top up](https://2captcha.com/pay) your balance and [get your API key](https://2captcha.com/enterpage#recognition).
|
[Create](https://2captcha.com/auth/register?userType=customer) a new 2Captcha account, [top up](https://2captcha.com/pay) your balance and [get your API key](https://2captcha.com/enterpage#recognition).
|
||||||
|
|
||||||
If you are located in Russia or Belarus, use the [ruCaptcha](https://rucaptcha.com) interface instead of 2Captcha. It's the same service, but it supports payments from those countries.
|
> [!NOTE]
|
||||||
|
> If you are located in Russia or Belarus, use the [ruCaptcha](https://rucaptcha.com) interface instead of 2Captcha. It's the same service, but it supports payments from those countries.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If you want as few CAPTCHAs as possible, it is recommended to use a macOS system. macOS systems usually get fewer CAPTCHAs than Linux and Windows—this is due to its unpopularity in the web scraping industry. Running suno-api on Windows and Linux will work, but in some cases, you could get a pretty large number of CAPTCHAs.
|
||||||
|
|
||||||
### 3. Clone and deploy this project
|
### 3. Clone and deploy this project
|
||||||
|
|
||||||
@@ -80,9 +84,11 @@ You can choose your preferred deployment method:
|
|||||||
git clone https://github.com/gcui-art/suno-api.git
|
git clone https://github.com/gcui-art/suno-api.git
|
||||||
cd suno-api
|
cd suno-api
|
||||||
npm install
|
npm install
|
||||||
npx playwright install chromium
|
|
||||||
```
|
```
|
||||||
#### Docker
|
#### Docker
|
||||||
|
>[!IMPORTANT]
|
||||||
|
> GPU acceleration will be disabled in Docker. If you have a slow CPU, it is recommended to [deploy locally](#run-locally).
|
||||||
|
|
||||||
Alternatively, you can use [Docker Compose](https://docs.docker.com/compose/). However, follow the step below before running.
|
Alternatively, you can use [Docker Compose](https://docs.docker.com/compose/). However, follow the step below before running.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+7
-2
@@ -64,7 +64,11 @@ Suno — потрясающий сервис для ИИ-музыки. Несм
|
|||||||
|
|
||||||
[Создайте](https://2captcha.com/ru/auth/register?userType=customer) новый аккаунт, [пополните](https://2captcha.com/ru/pay) баланс и [получите свой API-ключ](https://2captcha.com/ru/enterpage#recognition).
|
[Создайте](https://2captcha.com/ru/auth/register?userType=customer) новый аккаунт, [пополните](https://2captcha.com/ru/pay) баланс и [получите свой API-ключ](https://2captcha.com/ru/enterpage#recognition).
|
||||||
|
|
||||||
ℹ Если вы находитесь в России или Беларуси, используйте интерфейс [ruCaptcha](https://rucaptcha.com) вместо 2Captcha. Это абсолютно тот же сервис, но данный интерфейс поддерживает платежи из этих стран.
|
> [!NOTE]
|
||||||
|
> Если вы находитесь в России или Беларуси, используйте интерфейс [ruCaptcha](https://rucaptcha.com) вместо 2Captcha. Это абсолютно тот же сервис, но данный интерфейс поддерживает платежи из этих стран.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Если вы хотите получать как можно меньше капч, рекомендуется использовать macOS. Системы на macOS обычно получают меньше капч, чем Linux и Windows — это связано с их непопулярностью в сфере веб-скрейпинга. Запуск suno-api на Windows и Linux будет работать, но в некоторых случаях вы можете получить довольно большое количество капч.
|
||||||
|
|
||||||
### 3. Скачайте и запустите проект
|
### 3. Скачайте и запустите проект
|
||||||
|
|
||||||
@@ -80,9 +84,10 @@ Suno — потрясающий сервис для ИИ-музыки. Несм
|
|||||||
git clone https://github.com/gcui-art/suno-api.git
|
git clone https://github.com/gcui-art/suno-api.git
|
||||||
cd suno-api
|
cd suno-api
|
||||||
npm install
|
npm install
|
||||||
npx playwright install chromium
|
|
||||||
```
|
```
|
||||||
#### Docker
|
#### Docker
|
||||||
|
>[!IMPORTANT]
|
||||||
|
> Аппаратное видеоускорение браузера будет отключено в Docker. Если у вас медленный процессор, рекомендуется [развернуть локально](#локально).
|
||||||
Также можно использовать [Docker Compose](https://docs.docker.com/compose/), однако перед запуском выполните шаг ниже.
|
Также можно использовать [Docker Compose](https://docs.docker.com/compose/), однако перед запуском выполните шаг ниже.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+7
-5
@@ -2,11 +2,13 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
suno-api:
|
suno-api:
|
||||||
build:
|
image: registry.cn-shanghai.aliyuncs.com/easyaigc/suno-api:latest
|
||||||
context: .
|
# build:
|
||||||
args:
|
# context: .
|
||||||
SUNO_COOKIE: ${SUNO_COOKIE}
|
# args:
|
||||||
|
# SUNO_COOKIE: ${SUNO_COOKIE}
|
||||||
volumes:
|
volumes:
|
||||||
- ./public:/app/public
|
- ./public:/app/public
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3013:3000"
|
||||||
|
env_file: ".env"
|
||||||
Generated
+430
-6
@@ -10,6 +10,7 @@
|
|||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@2captcha/captcha-solver": "^1.3.0",
|
"@2captcha/captcha-solver": "^1.3.0",
|
||||||
|
"@playwright/browser-chromium": "^1.49.1",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
"bufferutil": "^4.0.8",
|
"bufferutil": "^4.0.8",
|
||||||
@@ -17,16 +18,19 @@
|
|||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
"ghost-cursor-playwright": "^2.1.0",
|
"ghost-cursor-playwright": "^2.1.0",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"next-swagger-doc": "^0.4.0",
|
"next-swagger-doc": "^0.4.0",
|
||||||
"pino": "^8.19.0",
|
"pino": "^8.19.0",
|
||||||
"pino-pretty": "^11.0.0",
|
"pino-pretty": "^11.0.0",
|
||||||
"playwright-core": "^1.49.1",
|
"playwright-extra": "^4.3.6",
|
||||||
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"rebrowser-playwright-core": "^1.49.1",
|
"rebrowser-playwright-core": "^1.49.1",
|
||||||
|
"socks-proxy-agent": "^8.0.5",
|
||||||
"swagger-ui-react": "^5.18.2",
|
"swagger-ui-react": "^5.18.2",
|
||||||
"tough-cookie": "^4.1.4",
|
"tough-cookie": "^4.1.4",
|
||||||
"user-agents": "^1.1.156",
|
"user-agents": "^1.1.156",
|
||||||
@@ -603,6 +607,19 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/browser-chromium": {
|
||||||
|
"version": "1.49.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.49.1.tgz",
|
||||||
|
"integrity": "sha512-LLeyllKSucbojsJBOpdJshwW27ZXZs3oypqffkVWLUvxX2azHJMOevsOcWpjCfoYbpevkaEozM2xHeSUGF00lg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.49.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@@ -1587,6 +1604,15 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@@ -1680,6 +1706,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/arr-union": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/array-buffer-byte-length": {
|
"node_modules/array-buffer-byte-length": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
|
||||||
@@ -2453,6 +2488,22 @@
|
|||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clone-deep": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/clone-deep/-/clone-deep-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"for-own": "^0.1.3",
|
||||||
|
"is-plain-object": "^2.0.1",
|
||||||
|
"kind-of": "^3.0.2",
|
||||||
|
"lazy-cache": "^1.0.3",
|
||||||
|
"shallow-clone": "^0.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clone-response": {
|
"node_modules/clone-response": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||||
@@ -3896,6 +3947,27 @@
|
|||||||
"is-callable": "^1.1.3"
|
"is-callable": "^1.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/for-in": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/for-own": {
|
||||||
|
"version": "0.1.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/for-own/-/for-own-0.1.5.tgz",
|
||||||
|
"integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"for-in": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||||
@@ -4596,6 +4668,19 @@
|
|||||||
"node": ">=10.19.0"
|
"node": ">=10.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -4716,6 +4801,15 @@
|
|||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ip-address": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-alphabetical": {
|
"node_modules/is-alphabetical": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
||||||
@@ -4816,6 +4910,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-buffer": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-bun-module": {
|
"node_modules/is-bun-module": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.2.1.tgz",
|
||||||
@@ -4897,6 +4997,15 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-extendable": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -5043,6 +5152,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-plain-object": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"isobject": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
@@ -5193,6 +5314,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/isobject": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iterator.prototype": {
|
"node_modules/iterator.prototype": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz",
|
||||||
@@ -5355,6 +5485,18 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kind-of": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-buffer": "^1.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/language-subtag-registry": {
|
"node_modules/language-subtag-registry": {
|
||||||
"version": "0.3.23",
|
"version": "0.3.23",
|
||||||
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
|
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
|
||||||
@@ -5375,6 +5517,15 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lazy-cache": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lazy-cache/-/lazy-cache-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
@@ -5707,6 +5858,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/merge-deep": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/merge-deep/-/merge-deep-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"arr-union": "^3.1.0",
|
||||||
|
"clone-deep": "^0.2.4",
|
||||||
|
"kind-of": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@@ -6255,6 +6420,28 @@
|
|||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/mixin-object": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mixin-object/-/mixin-object-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"for-in": "^0.1.3",
|
||||||
|
"is-extendable": "^0.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mixin-object/node_modules/for-in": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/for-in/-/for-in-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mkdirp-classic": {
|
"node_modules/mkdirp-classic": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
@@ -7024,6 +7211,30 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright-extra": {
|
||||||
|
"version": "4.3.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/playwright-extra/-/playwright-extra-4.3.6.tgz",
|
||||||
|
"integrity": "sha512-q2rVtcE8V8K3vPVF1zny4pvwZveHLH8KBuVU2MoE3Jw4OKVoBWsHI9CH9zPydovHHOCDxjGN2Vg+2m644q3ijA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright": "*",
|
||||||
|
"playwright-core": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"playwright-core": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||||
@@ -7349,6 +7560,147 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/debug": "^4.1.0",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"merge-deep": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=9.11.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright-extra": "*",
|
||||||
|
"puppeteer-extra": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright-extra": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"puppeteer-extra": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-stealth": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"puppeteer-extra-plugin": "^3.2.3",
|
||||||
|
"puppeteer-extra-plugin-user-preferences": "^2.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright-extra": "*",
|
||||||
|
"puppeteer-extra": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright-extra": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"puppeteer-extra": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-user-data-dir": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"fs-extra": "^10.0.0",
|
||||||
|
"puppeteer-extra-plugin": "^3.2.3",
|
||||||
|
"rimraf": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright-extra": "*",
|
||||||
|
"puppeteer-extra": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright-extra": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"puppeteer-extra": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/fs-extra": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/jsonfile": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/universalify": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/puppeteer-extra-plugin-user-preferences": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"puppeteer-extra-plugin": "^3.2.3",
|
||||||
|
"puppeteer-extra-plugin-user-data-dir": "^2.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"playwright-extra": "*",
|
||||||
|
"puppeteer-extra": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"playwright-extra": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"puppeteer-extra": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/querystringify": {
|
"node_modules/querystringify": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
@@ -8021,7 +8373,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
@@ -8038,7 +8389,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
@@ -8267,6 +8617,42 @@
|
|||||||
"sha.js": "bin.js"
|
"sha.js": "bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shallow-clone": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/shallow-clone/-/shallow-clone-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extendable": "^0.1.1",
|
||||||
|
"kind-of": "^2.0.1",
|
||||||
|
"lazy-cache": "^0.2.3",
|
||||||
|
"mixin-object": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shallow-clone/node_modules/kind-of": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-buffer": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shallow-clone/node_modules/lazy-cache": {
|
||||||
|
"version": "0.2.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lazy-cache/-/lazy-cache-0.2.7.tgz",
|
||||||
|
"integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -8389,6 +8775,44 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/smart-buffer": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks": {
|
||||||
|
"version": "2.8.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.7.tgz",
|
||||||
|
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ip-address": "^10.0.1",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks-proxy-agent": {
|
||||||
|
"version": "8.0.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
|
||||||
|
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"socks": "^2.8.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sonic-boom": {
|
"node_modules/sonic-boom": {
|
||||||
"version": "3.8.1",
|
"version": "3.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
|
||||||
@@ -9568,9 +9992,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/user-agents": {
|
"node_modules/user-agents": {
|
||||||
"version": "1.1.362",
|
"version": "1.1.655",
|
||||||
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.362.tgz",
|
"resolved": "https://registry.npmmirror.com/user-agents/-/user-agents-1.1.655.tgz",
|
||||||
"integrity": "sha512-mwDzPkR3IZswVYotnQJU4t/H56K0QBWGlkr3eDPHPzMYiUkxZCFICU1n4H3OHUN2QHVHsdlRpDMbsi39hhvvMg==",
|
"integrity": "sha512-3zdmOqszMxPoqTzOAtOacV6L4N+g5+n4NStyZh+PSoYf4A/i5ZPQQNlW5tEkLaDLtu/XjQCaii2BZt//ZgaLrw==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash.clonedeep": "^4.5.0"
|
"lodash.clonedeep": "^4.5.0"
|
||||||
|
|||||||
+9
-1
@@ -9,13 +9,17 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "PORT=3013 next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
"build:docker": "docker build . --platform linux/amd64 -t registry.cn-shanghai.aliyuncs.com/easyaigc/suno-api:latest",
|
||||||
|
"docker:push": "docker push registry.cn-shanghai.aliyuncs.com/easyaigc/suno-api:latest",
|
||||||
|
"deploy": "npm build:docker && npm docker:push",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@2captcha/captcha-solver": "^1.3.0",
|
"@2captcha/captcha-solver": "^1.3.0",
|
||||||
|
"@playwright/browser-chromium": "^1.49.1",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
"bufferutil": "^4.0.8",
|
"bufferutil": "^4.0.8",
|
||||||
@@ -23,15 +27,19 @@
|
|||||||
"cookie": "^1.0.2",
|
"cookie": "^1.0.2",
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
"ghost-cursor-playwright": "^2.1.0",
|
"ghost-cursor-playwright": "^2.1.0",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"next-swagger-doc": "^0.4.0",
|
"next-swagger-doc": "^0.4.0",
|
||||||
"pino": "^8.19.0",
|
"pino": "^8.19.0",
|
||||||
"pino-pretty": "^11.0.0",
|
"pino-pretty": "^11.0.0",
|
||||||
|
"playwright-extra": "^4.3.6",
|
||||||
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"rebrowser-playwright-core": "^1.49.1",
|
"rebrowser-playwright-core": "^1.49.1",
|
||||||
|
"socks-proxy-agent": "^8.0.5",
|
||||||
"swagger-ui-react": "^5.18.2",
|
"swagger-ui-react": "^5.18.2",
|
||||||
"tough-cookie": "^4.1.4",
|
"tough-cookie": "^4.1.4",
|
||||||
"user-agents": "^1.1.156",
|
"user-agents": "^1.1.156",
|
||||||
|
|||||||
Generated
-5345
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
import { sunoApi } from "@/lib/SunoApi";
|
||||||
|
import { corsHeaders } from "@/lib/utils";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
try {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
const personaId = url.searchParams.get('id');
|
||||||
|
const page = url.searchParams.get('page');
|
||||||
|
|
||||||
|
if (personaId == null) {
|
||||||
|
return new NextResponse(JSON.stringify({ error: 'Missing parameter id' }), {
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...corsHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageNumber = page ? parseInt(page) : 1;
|
||||||
|
const personaInfo = await (await sunoApi()).getPersonaPaginated(personaId, pageNumber);
|
||||||
|
|
||||||
|
return new NextResponse(JSON.stringify(personaInfo), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...corsHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching persona:', error);
|
||||||
|
|
||||||
|
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...corsHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new NextResponse('Method Not Allowed', {
|
||||||
|
headers: {
|
||||||
|
Allow: 'GET',
|
||||||
|
...corsHeaders
|
||||||
|
},
|
||||||
|
status: 405
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function OPTIONS(request: Request) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 200,
|
||||||
|
headers: corsHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ export default function Docs() {
|
|||||||
- \`/api/get_aligned_lyrics\`: Get list of timestamps for each word in the lyrics
|
- \`/api/get_aligned_lyrics\`: Get list of timestamps for each word in the lyrics
|
||||||
- \`/api/clip\`: Get clip information based on ID passed as query parameter \`id\`
|
- \`/api/clip\`: Get clip information based on ID passed as query parameter \`id\`
|
||||||
- \`/api/concat\`: Generate the whole song from extensions
|
- \`/api/concat\`: Generate the whole song from extensions
|
||||||
|
- \`/api/persona\`: Get persona information and clips based on ID and page number
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
Feel free to explore the detailed API parameters and conduct tests on this page.
|
Feel free to explore the detailed API parameters and conduct tests on this page.
|
||||||
|
|||||||
@@ -588,6 +588,149 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api/persona": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Get persona information and clips.",
|
||||||
|
"description": "Retrieve persona information, including associated clips and pagination data.",
|
||||||
|
"tags": ["default"],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true,
|
||||||
|
"description": "Persona ID",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"description": "Page number (defaults to 1)",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"persona": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Persona ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Persona name"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Persona description"
|
||||||
|
},
|
||||||
|
"image_s3_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Persona image URL"
|
||||||
|
},
|
||||||
|
"root_clip_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Root clip ID"
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Root clip information"
|
||||||
|
},
|
||||||
|
"persona_clips": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"clip": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Clip information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is_suno_persona": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether this is a Suno official persona"
|
||||||
|
},
|
||||||
|
"is_public": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether this persona is public"
|
||||||
|
},
|
||||||
|
"upvote_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of upvotes"
|
||||||
|
},
|
||||||
|
"clip_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of clips"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_results": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Total number of results"
|
||||||
|
},
|
||||||
|
"current_page": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Current page number"
|
||||||
|
},
|
||||||
|
"is_following": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the current user is following this persona"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Missing parameter id",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Missing parameter id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
+153
-36
@@ -2,15 +2,19 @@ import axios, { AxiosInstance } from 'axios';
|
|||||||
import UserAgent from 'user-agents';
|
import UserAgent from 'user-agents';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
import yn from 'yn';
|
import yn from 'yn';
|
||||||
import { sleep, isPage } from '@/lib/utils';
|
import { isPage, sleep, waitForRequests } from '@/lib/utils';
|
||||||
import * as cookie from 'cookie';
|
import * as cookie from 'cookie';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { Solver } from '@2captcha/captcha-solver';
|
import { Solver } from '@2captcha/captcha-solver';
|
||||||
import { BrowserContext, Page, Locator, chromium, firefox } from 'rebrowser-playwright-core';
|
|
||||||
import { createCursor, Cursor } from 'ghost-cursor-playwright';
|
|
||||||
import { paramsCoordinates } from '@2captcha/captcha-solver/dist/structs/2captcha';
|
import { paramsCoordinates } from '@2captcha/captcha-solver/dist/structs/2captcha';
|
||||||
|
import { BrowserContext, Page,chromium, Locator, firefox } from 'rebrowser-playwright-core';
|
||||||
|
import { createCursor, Cursor } from 'ghost-cursor-playwright';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||||
|
// import { chromium } from 'playwright-extra'; // Import from playwright-extra
|
||||||
|
// import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
||||||
|
|
||||||
|
|
||||||
// sunoApi instance caching
|
// sunoApi instance caching
|
||||||
const globalForSunoApi = global as unknown as { sunoApiCache?: Map<string, SunoApi> };
|
const globalForSunoApi = global as unknown as { sunoApiCache?: Map<string, SunoApi> };
|
||||||
@@ -39,6 +43,34 @@ export interface AudioInfo {
|
|||||||
error_message?: string; // Error message if any
|
error_message?: string; // Error message if any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PersonaResponse {
|
||||||
|
persona: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
image_s3_id: string;
|
||||||
|
root_clip_id: string;
|
||||||
|
clip: any; // You can define a more specific type if needed
|
||||||
|
user_display_name: string;
|
||||||
|
user_handle: string;
|
||||||
|
user_image_url: string;
|
||||||
|
persona_clips: Array<{
|
||||||
|
clip: any; // You can define a more specific type if needed
|
||||||
|
}>;
|
||||||
|
is_suno_persona: boolean;
|
||||||
|
is_trashed: boolean;
|
||||||
|
is_owned: boolean;
|
||||||
|
is_public: boolean;
|
||||||
|
is_public_approved: boolean;
|
||||||
|
is_loved: boolean;
|
||||||
|
upvote_count: number;
|
||||||
|
clip_count: number;
|
||||||
|
};
|
||||||
|
total_results: number;
|
||||||
|
current_page: number;
|
||||||
|
is_following: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
class SunoApi {
|
class SunoApi {
|
||||||
private static BASE_URL: string = 'https://studio-api.prod.suno.com';
|
private static BASE_URL: string = 'https://studio-api.prod.suno.com';
|
||||||
private static CLERK_BASE_URL: string = 'https://clerk.suno.com';
|
private static CLERK_BASE_URL: string = 'https://clerk.suno.com';
|
||||||
@@ -58,6 +90,29 @@ class SunoApi {
|
|||||||
this.userAgent = new UserAgent(/Macintosh/).random().toString(); // Usually Mac systems get less amount of CAPTCHAs
|
this.userAgent = new UserAgent(/Macintosh/).random().toString(); // Usually Mac systems get less amount of CAPTCHAs
|
||||||
this.cookies = cookie.parse(cookies);
|
this.cookies = cookie.parse(cookies);
|
||||||
this.deviceId = this.cookies.ajs_anonymous_id || randomUUID();
|
this.deviceId = this.cookies.ajs_anonymous_id || randomUUID();
|
||||||
|
const proxyUrl = process.env.PROXY_URL;
|
||||||
|
let extraConfig: Record<string, any> = {};
|
||||||
|
if (proxyUrl) {
|
||||||
|
if (proxyUrl.startsWith('socks')) {
|
||||||
|
// SOCKS5 代理
|
||||||
|
const agent = new SocksProxyAgent(proxyUrl);
|
||||||
|
extraConfig = {
|
||||||
|
httpAgent: agent,
|
||||||
|
httpsAgent: agent,
|
||||||
|
proxy: false // 一定要关掉 axios 自带的 proxy
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// HTTP/HTTPS 代理
|
||||||
|
const url = new URL(proxyUrl);
|
||||||
|
extraConfig = {
|
||||||
|
proxy: {
|
||||||
|
protocol: url.protocol.replace(':', ''), // 去掉末尾冒号
|
||||||
|
host: url.hostname,
|
||||||
|
port: Number(url.port)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -69,7 +124,8 @@ class SunoApi {
|
|||||||
'sec-ch-ua-mobile': '?1',
|
'sec-ch-ua-mobile': '?1',
|
||||||
'sec-ch-ua-platform': '"Android"',
|
'sec-ch-ua-platform': '"Android"',
|
||||||
'User-Agent': this.userAgent
|
'User-Agent': this.userAgent
|
||||||
}
|
},
|
||||||
|
...extraConfig,
|
||||||
});
|
});
|
||||||
this.client.interceptors.request.use(config => {
|
this.client.interceptors.request.use(config => {
|
||||||
if (this.currentToken && !config.headers.Authorization)
|
if (this.currentToken && !config.headers.Authorization)
|
||||||
@@ -180,7 +236,6 @@ class SunoApi {
|
|||||||
ctype: 'generation'
|
ctype: 'generation'
|
||||||
});
|
});
|
||||||
logger.info(resp.data);
|
logger.info(resp.data);
|
||||||
// await sleep(10);
|
|
||||||
return resp.data.required;
|
return resp.data.required;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +286,7 @@ class SunoApi {
|
|||||||
* @returns {BrowserContext}
|
* @returns {BrowserContext}
|
||||||
*/
|
*/
|
||||||
private async launchBrowser(): Promise<BrowserContext> {
|
private async launchBrowser(): Promise<BrowserContext> {
|
||||||
const browser = await this.getBrowserType().launch({
|
const args = [
|
||||||
args: [
|
|
||||||
'--disable-blink-features=AutomationControlled',
|
'--disable-blink-features=AutomationControlled',
|
||||||
'--disable-web-security',
|
'--disable-web-security',
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
@@ -241,10 +295,29 @@ class SunoApi {
|
|||||||
'--disable-features=IsolateOrigins',
|
'--disable-features=IsolateOrigins',
|
||||||
'--disable-extensions',
|
'--disable-extensions',
|
||||||
'--disable-infobars'
|
'--disable-infobars'
|
||||||
],
|
];
|
||||||
headless: yn(process.env.BROWSER_HEADLESS, { default: true })
|
// Check for GPU acceleration, as it is recommended to turn it off for Docker
|
||||||
});
|
if (yn(process.env.BROWSER_DISABLE_GPU, { default: false }))
|
||||||
const context = await browser.newContext({ userAgent: this.userAgent, locale: process.env.BROWSER_LOCALE, viewport: null });
|
args.push('--enable-unsafe-swiftshader',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--disable-setuid-sandbox');
|
||||||
|
try {
|
||||||
|
// chromium.use(StealthPlugin())
|
||||||
|
// const browser = await this.getBrowserType().launch({
|
||||||
|
// args,
|
||||||
|
// headless: yn(process.env.BROWSER_HEADLESS, { default: true }),
|
||||||
|
// ...(process.env.PROXY_URL &&{ proxy: {
|
||||||
|
// server: process.env.PROXY_URL,
|
||||||
|
// }})
|
||||||
|
// })
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
args,
|
||||||
|
headless: yn(process.env.BROWSER_HEADLESS, { default: true }),
|
||||||
|
...(process.env.PROXY_URL &&{ proxy: {
|
||||||
|
server: process.env.PROXY_URL,
|
||||||
|
}})
|
||||||
|
})
|
||||||
|
const context = await browser.newContext({ userAgent: this.userAgent, locale: process.env.BROWSER_LOCALE, viewport: { width: 1920, height: 1080 } });
|
||||||
const cookies = [];
|
const cookies = [];
|
||||||
const lax: 'Lax' | 'Strict' | 'None' = 'Lax';
|
const lax: 'Lax' | 'Strict' | 'None' = 'Lax';
|
||||||
cookies.push({
|
cookies.push({
|
||||||
@@ -252,7 +325,7 @@ class SunoApi {
|
|||||||
value: this.currentToken+'',
|
value: this.currentToken+'',
|
||||||
domain: '.suno.com',
|
domain: '.suno.com',
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: lax
|
sameSite: lax,
|
||||||
});
|
});
|
||||||
for (const key in this.cookies) {
|
for (const key in this.cookies) {
|
||||||
cookies.push({
|
cookies.push({
|
||||||
@@ -260,11 +333,16 @@ class SunoApi {
|
|||||||
value: this.cookies[key]+'',
|
value: this.cookies[key]+'',
|
||||||
domain: '.suno.com',
|
domain: '.suno.com',
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: lax
|
sameSite: lax,
|
||||||
|
secure: true, // Cookies from real browsers are often secure
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await context.addCookies(cookies);
|
await context.addCookies(cookies);
|
||||||
return context;
|
return context;
|
||||||
|
}catch ( e){
|
||||||
|
console.log(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -278,50 +356,59 @@ class SunoApi {
|
|||||||
logger.info('CAPTCHA required. Launching browser...')
|
logger.info('CAPTCHA required. Launching browser...')
|
||||||
const browser = await this.launchBrowser();
|
const browser = await this.launchBrowser();
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
// 1. 在 page.goto 之前设置监听器
|
||||||
|
page.on('request', request => {
|
||||||
|
// 检查请求 URL 是否包含关键词
|
||||||
|
console.log('>> Found project API request:', request.url());
|
||||||
|
// if (request.url().includes('studio-api.prod.suno.com/api/project/default')) {
|
||||||
|
// console.log('>> Found project API request:', request.url());
|
||||||
|
// }
|
||||||
|
});
|
||||||
await page.goto('https://suno.com/create', { referer: 'https://www.google.com/', waitUntil: 'domcontentloaded', timeout: 0 });
|
await page.goto('https://suno.com/create', { referer: 'https://www.google.com/', waitUntil: 'domcontentloaded', timeout: 0 });
|
||||||
|
//弹出cloudfare验证
|
||||||
|
|
||||||
|
|
||||||
logger.info('Waiting for Suno interface to load');
|
logger.info('Waiting for Suno interface to load');
|
||||||
//await page.locator('.react-aria-GridList').waitFor({ timeout: 60000 });
|
// await page.locator('.react-aria-GridList').waitFor({ timeout: 60000 });
|
||||||
await page.waitForResponse('**/api/feed/v2**', { timeout: 60000 }); // wait for song list API call
|
await page.waitForResponse('**/api/project/**\\?**', { timeout: 60000 }); // wait for song list API call
|
||||||
|
|
||||||
if (this.ghostCursorEnabled)
|
if (this.ghostCursorEnabled)
|
||||||
this.cursor = await createCursor(page);
|
this.cursor = await createCursor(page);
|
||||||
|
|
||||||
logger.info('Triggering the CAPTCHA');
|
logger.info('Triggering the CAPTCHA');
|
||||||
await this.click(page, { x: 318, y: 13 }); // close all popups
|
try {
|
||||||
|
await page.getByLabel('Close').click({ timeout: 2000 }); // close all popups
|
||||||
|
// await this.click(page, { x: 318, y: 13 });
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
const textarea = page.locator('.custom-textarea');
|
const textarea = page.locator('.custom-textarea');
|
||||||
await this.click(textarea);
|
await this.click(textarea);
|
||||||
await textarea.pressSequentially('Lorem ipsum', { delay: 80 });
|
await textarea.pressSequentially('Lorem ipsum', { delay: 80 });
|
||||||
|
|
||||||
const button = page.locator('button[aria-label="Create"]').locator('div.flex');
|
const button = page.locator('button[aria-label="Create"]').locator('div.flex');
|
||||||
await this.click(button);
|
this.click(button);
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
new Promise<void>(async (resolve, reject) => {
|
new Promise<void>(async (resolve, reject) => {
|
||||||
const frame = page.frameLocator('iframe[title*="hCaptcha"]');
|
const frame = page.frameLocator('iframe[title*="hCaptcha"]');
|
||||||
const challenge = frame.locator('.challenge-container');
|
const challenge = frame.locator('.challenge-container');
|
||||||
|
try {
|
||||||
|
let wait = true;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
if (wait)
|
||||||
await page.waitForResponse('https://img**.hcaptcha.com/**', { timeout: 60000 }); // wait for hCaptcha image to load
|
await waitForRequests(page, controller.signal);
|
||||||
while (true) { // wait for all requests to finish
|
|
||||||
try {
|
|
||||||
await page.waitForResponse('https://img**.hcaptcha.com/**', { timeout: 1000 });
|
|
||||||
} catch(e) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const drag = (await challenge.locator('.prompt-text').first().innerText()).toLowerCase().includes('drag');
|
const drag = (await challenge.locator('.prompt-text').first().innerText()).toLowerCase().includes('drag');
|
||||||
let captcha: any;
|
let captcha: any;
|
||||||
for (let j = 0; j < 3; j++) { // try several times because sometimes 2Captcha could send an error
|
for (let j = 0; j < 3; j++) { // try several times because sometimes 2Captcha could return an error
|
||||||
try {
|
try {
|
||||||
logger.info('Sending the CAPTCHA to 2Captcha');
|
logger.info('Sending the CAPTCHA to 2Captcha');
|
||||||
const payload: paramsCoordinates = {
|
const payload: paramsCoordinates = {
|
||||||
body: (await challenge.screenshot()).toString('base64'),
|
body: (await challenge.screenshot({ timeout: 5000 })).toString('base64'),
|
||||||
lang: process.env.BROWSER_LOCALE
|
lang: process.env.BROWSER_LOCALE
|
||||||
};
|
};
|
||||||
if (drag) {
|
if (drag) {
|
||||||
// Say to the worker that he needs to click
|
// Say to the worker that he needs to click
|
||||||
payload.textinstructions = '! Instead of dragging, CLICK on the shapes as shown in the image above !';
|
payload.textinstructions = 'CLICK on the shapes at their edge or center as shown above—please be precise!';
|
||||||
payload.imginstructions = (await fs.readFile(path.join(process.cwd(), 'public', 'drag-instructions.jpg'))).toString('base64');
|
payload.imginstructions = (await fs.readFile(path.join(process.cwd(), 'public', 'drag-instructions.jpg'))).toString('base64');
|
||||||
}
|
}
|
||||||
captcha = await this.solver.coordinates(payload);
|
captcha = await this.solver.coordinates(payload);
|
||||||
@@ -338,6 +425,12 @@ class SunoApi {
|
|||||||
const challengeBox = await challenge.boundingBox();
|
const challengeBox = await challenge.boundingBox();
|
||||||
if (challengeBox == null)
|
if (challengeBox == null)
|
||||||
throw new Error('.challenge-container boundingBox is null!');
|
throw new Error('.challenge-container boundingBox is null!');
|
||||||
|
if (captcha.data.length % 2) {
|
||||||
|
logger.info('Solution does not have even amount of points required for dragging. Requesting new solution...');
|
||||||
|
this.solver.badReport(captcha.id);
|
||||||
|
wait = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (let i = 0; i < captcha.data.length; i += 2) {
|
for (let i = 0; i < captcha.data.length; i += 2) {
|
||||||
const data1 = captcha.data[i];
|
const data1 = captcha.data[i];
|
||||||
const data2 = captcha.data[i+1];
|
const data2 = captcha.data[i+1];
|
||||||
@@ -348,22 +441,27 @@ class SunoApi {
|
|||||||
await page.mouse.move(challengeBox.x + +data2.x, challengeBox.y + +data2.y, { steps: 30 });
|
await page.mouse.move(challengeBox.x + +data2.x, challengeBox.y + +data2.y, { steps: 30 });
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
}
|
}
|
||||||
|
wait = true;
|
||||||
} else {
|
} else {
|
||||||
for (const data of captcha.data) {
|
for (const data of captcha.data) {
|
||||||
logger.info(data);
|
logger.info(data);
|
||||||
await this.click(challenge, { x: +data.x, y: +data.y });
|
await this.click(challenge, { x: +data.x, y: +data.y });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/*await*/ this.click(frame.locator('.button-submit')); // await is commented because we need to call waitForResponse at the same time
|
this.click(frame.locator('.button-submit')).catch(e => {
|
||||||
} catch(e: any) {
|
if (e.message.includes('viewport')) // when hCaptcha window has been closed due to inactivity,
|
||||||
if (e.message.includes('viewport') || e.message.includes('timeout')) // when hCaptcha window has been closed due to inactivity,
|
|
||||||
this.click(button); // click the Create button again to trigger the CAPTCHA
|
this.click(button); // click the Create button again to trigger the CAPTCHA
|
||||||
else if (e.message.includes('been closed')) // catch error when closing the browser
|
else
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(e: any) {
|
||||||
|
if (e.message.includes('been closed') // catch error when closing the browser
|
||||||
|
|| e.message == 'AbortError') // catch error when waitForRequests is aborted
|
||||||
resolve();
|
resolve();
|
||||||
else
|
else
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
browser.browser()?.close();
|
browser.browser()?.close();
|
||||||
throw e;
|
throw e;
|
||||||
@@ -374,6 +472,7 @@ class SunoApi {
|
|||||||
logger.info('hCaptcha token received. Closing browser');
|
logger.info('hCaptcha token received. Closing browser');
|
||||||
route.abort();
|
route.abort();
|
||||||
browser.browser()?.close();
|
browser.browser()?.close();
|
||||||
|
controller.abort();
|
||||||
const request = route.request();
|
const request = route.request();
|
||||||
this.currentToken = request.headers().authorization.split('Bearer ').pop();
|
this.currentToken = request.headers().authorization.split('Bearer ').pop();
|
||||||
resolve(request.postDataJSON().token);
|
resolve(request.postDataJSON().token);
|
||||||
@@ -409,7 +508,7 @@ class SunoApi {
|
|||||||
): Promise<AudioInfo[]> {
|
): Promise<AudioInfo[]> {
|
||||||
await this.keepAlive(false);
|
await this.keepAlive(false);
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const audios = this.generateSongs(
|
const audios = await this.generateSongs(
|
||||||
prompt,
|
prompt,
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -785,10 +884,28 @@ class SunoApi {
|
|||||||
monthly_usage: response.data.monthly_usage
|
monthly_usage: response.data.monthly_usage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getPersonaPaginated(personaId: string, page: number = 1): Promise<PersonaResponse> {
|
||||||
|
await this.keepAlive(false);
|
||||||
|
|
||||||
|
const url = `${SunoApi.BASE_URL}/api/persona/get-persona-paginated/${personaId}/?page=${page}`;
|
||||||
|
|
||||||
|
logger.info(`Fetching persona data: ${url}`);
|
||||||
|
|
||||||
|
const response = await this.client.get(url, {
|
||||||
|
timeout: 10000 // 10 seconds timeout
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Error response: ' + response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sunoApi = async (cookie?: string) => {
|
export const sunoApi = async (cookie?: string) => {
|
||||||
const resolvedCookie = cookie || process.env.SUNO_COOKIE;
|
const resolvedCookie = cookie && cookie.includes('__client') ? cookie : process.env.SUNO_COOKIE; // Check for bad `Cookie` header (It's too expensive to actually parse the cookies *here*)
|
||||||
if (!resolvedCookie) {
|
if (!resolvedCookie) {
|
||||||
logger.info('No cookie provided! Aborting...\nPlease provide a cookie either in the .env file or in the Cookie header of your request.')
|
logger.info('No cookie provided! Aborting...\nPlease provide a cookie either in the .env file or in the Cookie header of your request.')
|
||||||
throw new Error('Please provide a cookie either in the .env file or in the Cookie header of your request.');
|
throw new Error('Please provide a cookie either in the .env file or in the Cookie header of your request.');
|
||||||
|
|||||||
@@ -29,6 +29,87 @@ export const isPage = (target: any): target is Page => {
|
|||||||
return target.constructor.name === 'Page';
|
return target.constructor.name === 'Page';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for an hCaptcha image requests and then waits for all of them to end
|
||||||
|
* @param page
|
||||||
|
* @param signal `const controller = new AbortController(); controller.status`
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export const waitForRequests = (page: Page, signal: AbortSignal): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const urlPattern = /^https:\/\/img[a-zA-Z0-9]*\.hcaptcha\.com\/.*$/;
|
||||||
|
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||||
|
let activeRequestCount = 0;
|
||||||
|
let requestOccurred = false;
|
||||||
|
|
||||||
|
const cleanupListeners = () => {
|
||||||
|
page.off('request', onRequest);
|
||||||
|
page.off('requestfinished', onRequestFinished);
|
||||||
|
page.off('requestfailed', onRequestFinished);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTimeout = () => {
|
||||||
|
if (timeoutHandle)
|
||||||
|
clearTimeout(timeoutHandle);
|
||||||
|
if (activeRequestCount === 0) {
|
||||||
|
timeoutHandle = setTimeout(() => {
|
||||||
|
cleanupListeners();
|
||||||
|
resolve();
|
||||||
|
}, 1000); // 1 second of no requests
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRequest = (request: { url: () => string }) => {
|
||||||
|
if (urlPattern.test(request.url())) {
|
||||||
|
requestOccurred = true;
|
||||||
|
activeRequestCount++;
|
||||||
|
if (timeoutHandle)
|
||||||
|
clearTimeout(timeoutHandle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRequestFinished = (request: { url: () => string }) => {
|
||||||
|
if (urlPattern.test(request.url())) {
|
||||||
|
activeRequestCount--;
|
||||||
|
resetTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for an hCaptcha request for up to 1 minute
|
||||||
|
const initialTimeout = setTimeout(() => {
|
||||||
|
if (!requestOccurred) {
|
||||||
|
page.off('request', onRequest);
|
||||||
|
cleanupListeners();
|
||||||
|
reject(new Error('No hCaptcha request occurred within 1 minute.'));
|
||||||
|
} else {
|
||||||
|
// Start waiting for no hCaptcha requests
|
||||||
|
resetTimeout();
|
||||||
|
}
|
||||||
|
}, 60000); // 1 minute timeout
|
||||||
|
|
||||||
|
page.on('request', onRequest);
|
||||||
|
page.on('requestfinished', onRequestFinished);
|
||||||
|
page.on('requestfailed', onRequestFinished);
|
||||||
|
|
||||||
|
// Cleanup the initial timeout if an hCaptcha request occurs
|
||||||
|
page.on('request', (request: { url: () => string }) => {
|
||||||
|
if (urlPattern.test(request.url())) {
|
||||||
|
clearTimeout(initialTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onAbort = () => {
|
||||||
|
cleanupListeners();
|
||||||
|
clearTimeout(initialTimeout);
|
||||||
|
if (timeoutHandle)
|
||||||
|
clearTimeout(timeoutHandle);
|
||||||
|
signal.removeEventListener('abort', onAbort);
|
||||||
|
reject(new Error('AbortError'));
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.addEventListener('abort', onAbort, { once: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const corsHeaders = {
|
export const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
|||||||
@@ -0,0 +1,332 @@
|
|||||||
|
import { chromium } from 'playwright-extra'; // Import from playwright-extra
|
||||||
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
||||||
|
import yn from 'yn';
|
||||||
|
import UserAgent from 'user-agents';
|
||||||
|
// 把字符串转为对象数组
|
||||||
|
function parseCookies(cookieString:string, domain:string) {
|
||||||
|
return cookieString.split(';').map(c => {
|
||||||
|
const [name, ...rest] = c.trim().split('=');
|
||||||
|
const lax: 'Lax' | 'Strict' | 'None' = 'Lax';
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value: rest.join('='), // 防止 value 里有 "=" 的情况
|
||||||
|
domain, // 必须指定 domain
|
||||||
|
path: '/', // 一般都是根路径
|
||||||
|
httpOnly: false,
|
||||||
|
secure: true,
|
||||||
|
sameSite: lax,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_KEY = "475f30640c5860c432064a2c37f06fd6"; // 你的 2Captcha key
|
||||||
|
// 等待 Turnstile 渲染出来(最多 60 秒)
|
||||||
|
|
||||||
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||||
|
|
||||||
|
async function waitForSiteKey(page, { timeout = 90000, pollInterval = 500 } = {}) {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
|
||||||
|
// 1) 快速在主文档中寻找(data-sitekey、iframe src query、script 文本)
|
||||||
|
const mainCheck = await page.evaluate(() => {
|
||||||
|
const attrNames = ['data-sitekey','data-key','data-cf-turnstile-sitekey','data-hcaptcha-sitekey','data-captcha'];
|
||||||
|
for (const a of attrNames) {
|
||||||
|
const el = document.querySelector(`[${a}]`);
|
||||||
|
if (el) return { sitekey: el.getAttribute(a), source: 'dom', attr: a };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集 iframe src/srcdoc(只返回字符串,不触碰 frame 内部以避免跨域问题)
|
||||||
|
const iframes = Array.from(document.querySelectorAll('iframe')).map(f => ({ src: f.src || f.getAttribute('src') || '', srcdoc: f.srcdoc || '' }));
|
||||||
|
for (const f of iframes) {
|
||||||
|
if (f.src) {
|
||||||
|
try {
|
||||||
|
const url = new URL(f.src, location.href);
|
||||||
|
const qp = Object.fromEntries(url.searchParams.entries());
|
||||||
|
const possible = qp.sitekey || qp.k || qp['data-sitekey'] || qp.s || qp.key;
|
||||||
|
if (possible) return { sitekey: possible, source: 'iframe-src', iframeSrc: f.src };
|
||||||
|
} catch(e){}
|
||||||
|
}
|
||||||
|
if (f.srcdoc && f.srcdoc.includes('sitekey')) {
|
||||||
|
const m = f.srcdoc.match(/sitekey['"]?\s*[:=]\s*['"]([\w\-]{8,})['"]/i) || f.srcdoc.match(/k=([A-Za-z0-9_-]{8,})/i);
|
||||||
|
if (m) return { sitekey: m[1], source: 'iframe-srcdoc' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在内联 script 中查找 sitekey (有些站点把 sitekey 写在脚本里)
|
||||||
|
for (const s of Array.from(document.scripts)) {
|
||||||
|
const t = s.textContent || '';
|
||||||
|
if (!t) continue;
|
||||||
|
const m = t.match(/sitekey['"]?\s*[:=]\s*['"]([\w\-]{8,})['"]?/i) || t.match(/k=([A-Za-z0-9_-]{8,})/i);
|
||||||
|
if (m) return { sitekey: m[1], source: 'script' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// window 变量(有些实现会在 window 上挂载)
|
||||||
|
try {
|
||||||
|
const candidates = ['turnstile','__turnstile','hcaptcha','__hcaptcha'];
|
||||||
|
for (const k of candidates) {
|
||||||
|
// 访问 window[k] 可能为 undefined 或对象
|
||||||
|
if (window[k] && window[k].sitekey) return { sitekey: window[k].sitekey, source: 'window.' + k };
|
||||||
|
}
|
||||||
|
} catch(e){}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断页面是否已经进入登录/应用界面
|
||||||
|
const isLoginPage = await page.$('input[type="email"], input[name="username"], button:has-text("Sign in"), button:has-text("Log in")');
|
||||||
|
if (isLoginPage) {
|
||||||
|
// console.log('已经是登录界面,不需要验证码');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainCheck && mainCheck.sitekey) return mainCheck;
|
||||||
|
|
||||||
|
// 2) 逐 frame 检查(对于可访问的 frame 直接 evaluate;对于跨域,解析 frame.url)
|
||||||
|
const frames = page.frames();
|
||||||
|
for (const f of frames) {
|
||||||
|
try {
|
||||||
|
// 可能跨域,evaluate 会抛错,如果可访问就能直接从 frame DOM 找到
|
||||||
|
const res = await f.evaluate(() => {
|
||||||
|
const attrNames = ['data-sitekey','data-key','data-cf-turnstile-sitekey','data-hcaptcha-sitekey'];
|
||||||
|
for (const a of attrNames) {
|
||||||
|
const el = document.querySelector(`[${a}]`);
|
||||||
|
if (el) return { sitekey: el.getAttribute(a), source: 'frame-dom', attr: a };
|
||||||
|
}
|
||||||
|
for (const s of Array.from(document.scripts)) {
|
||||||
|
const t = s.textContent || '';
|
||||||
|
if (!t) continue;
|
||||||
|
const m = t.match(/sitekey['"]?\s*[:=]\s*['"]([\w\-]{8,})['"]/i) || t.match(/k=([A-Za-z0-9_-]{8,})/i);
|
||||||
|
if (m) return { sitekey: m[1], source: 'frame-script' };
|
||||||
|
}
|
||||||
|
// 也检查 meta 等可见位置(可扩展)
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (res && res.sitekey) return { ...res, frameUrl: f.url() };
|
||||||
|
} catch (err) {
|
||||||
|
// 跨域 frame:不能 evaluate,改为解析 frame.url(常见 sitekey 在 query中)
|
||||||
|
try {
|
||||||
|
const fu = f.url();
|
||||||
|
if (fu && (fu.includes('turnstile') || fu.includes('hcaptcha') || fu.includes('challenges.cloudflare.com') || fu.includes('hcaptcha.com'))) {
|
||||||
|
const u = new URL(fu);
|
||||||
|
const qp = u.searchParams;
|
||||||
|
const possible = qp.get('sitekey') || qp.get('k') || qp.get('s') || qp.get('key');
|
||||||
|
if (possible) return { sitekey: possible, source: 'frame-url', frameUrl: fu };
|
||||||
|
}
|
||||||
|
} catch(e){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 如果 Cloudflare 仍在“Checking your browser”,继续等
|
||||||
|
const checking = await page.$('text="Checking your browser"') || await page.$('text=Checking') || null;
|
||||||
|
if (checking) {
|
||||||
|
// 仅作日志,继续等待
|
||||||
|
// console.log('Cloudflare still checking your browser...');
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(pollInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 超时
|
||||||
|
}
|
||||||
|
|
||||||
|
async function create2captchaTurnstileTask(apiKey, websiteURL, websiteKey) {
|
||||||
|
const createRes = await fetch('https://api.2captcha.com/createTask', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
clientKey: apiKey,
|
||||||
|
task: {
|
||||||
|
type: 'TurnstileTaskProxyless',
|
||||||
|
websiteURL,
|
||||||
|
websiteKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const json = await createRes.json();
|
||||||
|
if (json.errorId && json.errorId !== 0) throw new Error('createTask error: ' + JSON.stringify(json));
|
||||||
|
return json.taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get2captchaResult(apiKey, taskId, { timeout = 120000, pollInterval = 5000 } = {}) {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
await sleep(pollInterval);
|
||||||
|
const res = await fetch('https://api.2captcha.com/getTaskResult', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ clientKey: apiKey, taskId })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.errorId && json.errorId !== 0) throw new Error('getTaskResult error: ' + JSON.stringify(json));
|
||||||
|
if (json.status === 'ready' && json.solution && json.solution.token) return json.solution.token;
|
||||||
|
// else keep polling
|
||||||
|
}
|
||||||
|
throw new Error('2Captcha getTaskResult timeout');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function injectTurnstileToken(page, token) {
|
||||||
|
// 多种注入方式以提高兼容性
|
||||||
|
await page.evaluate((t) => {
|
||||||
|
// 常见隐藏字段
|
||||||
|
const selectors = [
|
||||||
|
'textarea[name="cf-turnstile-response"]',
|
||||||
|
'input[name="cf-turnstile-response"]',
|
||||||
|
'textarea[name="cf_captcha_token"]',
|
||||||
|
'input[name="cf_captcha_token"]'
|
||||||
|
];
|
||||||
|
let injected = false;
|
||||||
|
for (const sel of selectors) {
|
||||||
|
const el = document.querySelector(sel);
|
||||||
|
if (el) {
|
||||||
|
el.value = t;
|
||||||
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
injected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有直接字段,寻找任意 textarea 并设置(降级方案)
|
||||||
|
if (!injected) {
|
||||||
|
const ta = document.querySelector('textarea');
|
||||||
|
if (ta) {
|
||||||
|
ta.value = t;
|
||||||
|
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
injected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用可能挂载的回调(site 端可能传 callback 名称)
|
||||||
|
try {
|
||||||
|
if (window.turnstile && typeof window.turnstile.renderResponse === 'function') {
|
||||||
|
window.turnstile.renderResponse(t);
|
||||||
|
}
|
||||||
|
if (window.tsCallback && typeof window.tsCallback === 'function') {
|
||||||
|
window.tsCallback(t);
|
||||||
|
}
|
||||||
|
// 查找 data-callback 属性并触发
|
||||||
|
document.querySelectorAll('[data-sitekey]').forEach(el => {
|
||||||
|
const cb = el.getAttribute('data-callback');
|
||||||
|
if (cb && window[cb] && typeof window[cb] === 'function') {
|
||||||
|
try { window[cb](t); } catch(e) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(e){}
|
||||||
|
}, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function solveTurnstile(page, apiKey=API_KEY) {
|
||||||
|
const found = await waitForSiteKey(page, { timeout: 90000, pollInterval: 700 });
|
||||||
|
if (!found) {
|
||||||
|
console.warn('⚠️ 超时未检测到 Turnstile sitekey,建议开启调试快照(screenshot / html)进行排查');
|
||||||
|
// 调试输出(保存快照/HTML)
|
||||||
|
try {
|
||||||
|
await page.screenshot({ path: 'turnstile-debug.png', fullPage: true });
|
||||||
|
const html = await page.content();
|
||||||
|
require('fs').writeFileSync('turnstile-debug.html', html);
|
||||||
|
console.log('已保存 turnstile-debug.png 和 turnstile-debug.html 用于排查(当前目录)');
|
||||||
|
} catch(e) { console.error('保存调试文件失败', e); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('找到 sitekey ->', found);
|
||||||
|
|
||||||
|
// 调用 2Captcha
|
||||||
|
const pageUrl = page.url();
|
||||||
|
const taskId = await create2captchaTurnstileTask(apiKey, pageUrl, found.sitekey);
|
||||||
|
console.log('2Captcha createTask 返回 taskId:', taskId);
|
||||||
|
|
||||||
|
const token = await get2captchaResult(apiKey, taskId, { timeout: 180000, pollInterval: 5000 });
|
||||||
|
console.log('2Captcha 返回 token (长度):', token?.length);
|
||||||
|
|
||||||
|
await injectTurnstileToken(page, token);
|
||||||
|
console.log('token 注入完成,等待站点验证或跳转');
|
||||||
|
|
||||||
|
// 站点通常会在 token 注入后提交表单或自动验证,给点时间
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = async () => {
|
||||||
|
const args = [
|
||||||
|
'--disable-blink-features=AutomationControlled',
|
||||||
|
'--disable-web-security',
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-features=site-per-process',
|
||||||
|
'--disable-features=IsolateOrigins',
|
||||||
|
'--disable-extensions',
|
||||||
|
'--disable-infobars'
|
||||||
|
];
|
||||||
|
chromium.use(StealthPlugin())
|
||||||
|
// const browser = await this.getBrowserType().launch({
|
||||||
|
// args,
|
||||||
|
// headless: yn(process.env.BROWSER_HEADLESS, { default: true }),
|
||||||
|
// ...(process.env.PROXY_URL &&{ proxy: {
|
||||||
|
// server: process.env.PROXY_URL,
|
||||||
|
// }})
|
||||||
|
// })
|
||||||
|
const userAgent = new UserAgent(/Macintosh/).random().toString();
|
||||||
|
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
args,
|
||||||
|
headless:false,
|
||||||
|
proxy: {
|
||||||
|
server: 'http://127.0.0.1:12334',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const context = await browser.newContext({ userAgent, locale:'en', viewport: { width: 1920, height: 1080 } });
|
||||||
|
const cookiesStr = '_gcl_au=1.1.1122395686.1756802685; _ga=GA1.1.785768328.1756802695; _axwrt=e01097fa-f771-4c63-a49d-78538080b57a; singular_device_id=822605c1-fd96-4312-8e7f-b27653bdcbaf; ajs_anonymous_id=8240937f-6930-4d54-ae11-830db6ef8262; _tt_enable_cookie=1; _ttp=01K44SN90TAWV4SBPRAYF3SEN1_.tt.1; _fbp=fb.1.1756802753737.293820645972563697; afUserId=ef07b62f-6c47-44f3-b92a-269f61235b6f-p; AF_SYNC=1756802770762; _clck=fqr409%5E2%5Efz4%5E0%5E2071; __client=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNsaWVudF8zMkp6djBjV1U1MGozTW1rdHAzUHdnZ1JJbVYiLCJyb3RhdGluZ190b2tlbiI6Im1iZWd1bHc0aHJ2aDA3Z3praGNmaXA0M2o1enVkMDgxYzczNjhwMW0ifQ.GfLgVl0x0OC7yA7HxSneUpsKnvcMvNSqBonmNuxwURAPhBjJx8MoGy5sojC9Kx8zmO36kaahB4uTDiOj-9VTRZaob01iSk7lzciCjW_iEM5gDZfTeQbYW62JmXk8ymr84twVBNy1xmYAQQKb9TTEnCZHbGjgf3yfPnJOndEgRLeMqTLmGTCz0Vi-1OBx-zOSxS0CwPmPUlmRDhTzrw76x9puzh9PQeP3mbVF8uJe4ZvCA-BSWLjURt8VzcpLW0BYWO_yGjuk-kI8dJ6eUmDOfjCXSsh2SLqqwnE_bWirBHM4ce7JLc09iok6LUsDI2O0g2RQxQW0HwfCtuQxtDV7Rg; __client_uat=1757235449; __client_uat_U9tcbTPE=1757235449; __stripe_mid=f1915dd0-feed-4d1e-8402-e52760e3237b4face4; __cf_bm=Wxfi2Ww131iNPv2i27d32fnSJ2ee7aGUtY3Nz7nkpLk-1757392628-1.0.1.1-hwovJvwM_F.ejTX6HkfZd4F5PSugqHgOtI7X56t3p.FS1sUCrUOuGg4YZmwjNBZ4RCZA_HT7j6AAAnF55rF7K9r9T9pVz5FHDy..P6JSBko; _cfuvid=awR2fvj9zdJbloFbbUfGfhWFS51WZtQu_.7_uBwLKJA-1757392628089-0.0.1.1-604800000; __stripe_sid=c848008e-a8a1-423c-9c31-0b74682bd6a3e48eaf; _ga_7B0KEDD7XP=GS2.1.s1757392637$o11$g1$t1757392720$j46$l0$h0; _uetsid=ac2191508d3611f0afd391282bdea7cd|1phqefk|2|fz6|0|2078; ax_visitor=%7B%22firstVisitTs%22%3A1756802706366%2C%22lastVisitTs%22%3A1757317725011%2C%22currentVisitStartTs%22%3A1757392645129%2C%22ts%22%3A1757392720941%2C%22visitCount%22%3A9%7D; ttcsid=1757392640440::ZYVwpHKeEnpgc8y37liU.10.1757392723928; ttcsid_CT67HURC77UB52N3JFBG=1757392640440::rKUF-WAAJUhQ9nYMbwOe.10.1757392724155; _uetvid=1ecc230087d911f08b445db6fd2178ad|yluce0|1757392724319|4|1|bat.bing.com/p/conversions/c/a'
|
||||||
|
const cookies = parseCookies(cookiesStr, '.suno.com');
|
||||||
|
// cookies.push({
|
||||||
|
// name: '__session',
|
||||||
|
// value: this.currentToken+'',
|
||||||
|
// domain: '.suno.com',
|
||||||
|
// path: '/',
|
||||||
|
// httpOnly: true,
|
||||||
|
// secure: true,
|
||||||
|
// sameSite: 'Lax',
|
||||||
|
// });
|
||||||
|
// await context.addCookies(cookies);
|
||||||
|
const page = await browser.newPage();
|
||||||
|
console.log('Testing the stealth plugin..')
|
||||||
|
await page.goto('https://www.suno.com/create', { waitUntil: 'networkidle' })
|
||||||
|
await solveTurnstile(page);
|
||||||
|
// const frame = page
|
||||||
|
// .frames()
|
||||||
|
// .find((f) => f.url().includes("hcaptcha.com") || f.url().includes("challenges.cloudflare.com"));
|
||||||
|
//
|
||||||
|
// if (frame) {
|
||||||
|
// console.log("检测到 Cloudflare 验证组件,开始调用 2Captcha...");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 点击 Google 登录按钮
|
||||||
|
const googleBtn = await page.waitForSelector("button.cl-button__google");
|
||||||
|
await googleBtn.click();
|
||||||
|
// 等待跳转到 Google 登录页
|
||||||
|
await page.waitForURL(/accounts\.google\.com/);
|
||||||
|
|
||||||
|
// 输入邮箱
|
||||||
|
await page.fill('input[type="email"]', 'easyai202502@gmail.com');
|
||||||
|
await page.click('#identifierNext');
|
||||||
|
|
||||||
|
// 等待密码输入框
|
||||||
|
await page.waitForSelector('input[type="password"]', { timeout: 15000 });
|
||||||
|
await page.fill('input[type="password"]', 'easyai@2025');
|
||||||
|
await page.click('#passwordNext');
|
||||||
|
|
||||||
|
// 登录成功后会跳转回 suno.com
|
||||||
|
await page.waitForURL(/suno\.com/, { timeout: 60000 });
|
||||||
|
|
||||||
|
// const frame = page.mainFrame();
|
||||||
|
// const captchaEl = await frame.$("iframe[src*='hcaptcha.com'], iframe[src*='challenges.cloudflare.com']");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log('All done, check the screenshot. ✨')
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
test()
|
||||||
Reference in New Issue
Block a user