feat: optimize code
This commit is contained in:
parent
2a309f87a7
commit
d3160ce5f9
2
.gitignore
vendored
2
.gitignore
vendored
@ -34,3 +34,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
.idea
|
5676
package-lock.json
generated
Normal file
5676
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,17 +16,20 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
"axios-cookiejar-support": "^5.0.0",
|
||||||
"next": "14.1.4",
|
"next": "14.1.4",
|
||||||
"pino": "^8.19.0",
|
"pino": "^8.19.0",
|
||||||
"pino-pretty": "^11.0.0",
|
"pino-pretty": "^11.0.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"tough-cookie": "^4.1.3",
|
||||||
"user-agents": "^1.1.156"
|
"user-agents": "^1.1.156"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/tough-cookie": "^4.0.5",
|
||||||
"@types/user-agents": "^1.0.4",
|
"@types/user-agents": "^1.0.4",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
|
@ -1,50 +1,50 @@
|
|||||||
import { NextResponse, NextRequest } from "next/server";
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
import SunoApi from '@/lib/sunoApi';
|
import { sunoApi } from "@/lib/SunoApi";
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { prompt, tags, title, make_instrumental, wait_audio } = body;
|
const { prompt, tags, title, make_instrumental, wait_audio } = body;
|
||||||
|
|
||||||
// 校验输入参数
|
// 校验输入参数
|
||||||
if (!prompt || !tags || !title) {
|
if (!prompt || !tags || !title) {
|
||||||
return new NextResponse(JSON.stringify({ error: 'Prompt, tags, and title are required' }), {
|
return new NextResponse(JSON.stringify({ error: 'Prompt, tags, and title are required' }), {
|
||||||
status: 400,
|
status: 400,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用 SunoApi.custom_generate 方法生成定制音频
|
|
||||||
const audioInfo = await SunoApi.custom_generate(
|
|
||||||
prompt, tags, title,
|
|
||||||
make_instrumental == true,
|
|
||||||
wait_audio == true
|
|
||||||
);
|
|
||||||
|
|
||||||
// 使用 NextResponse 构建成功响应
|
|
||||||
return new NextResponse(JSON.stringify(audioInfo), {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error generating custom audio:', error.response.data);
|
|
||||||
if (error.response.status === 402) {
|
|
||||||
return new NextResponse(JSON.stringify({ error: error.response.data.detail }), {
|
|
||||||
status: 402,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 使用 NextResponse 构建错误响应
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new NextResponse('Method Not Allowed', {
|
|
||||||
headers: { Allow: 'POST' },
|
|
||||||
status: 405
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 SunoApi.custom_generate 方法生成定制音频
|
||||||
|
const audioInfo = await (await sunoApi).custom_generate(
|
||||||
|
prompt, tags, title,
|
||||||
|
make_instrumental == true,
|
||||||
|
wait_audio == true
|
||||||
|
);
|
||||||
|
|
||||||
|
// 使用 NextResponse 构建成功响应
|
||||||
|
return new NextResponse(JSON.stringify(audioInfo), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error generating custom audio:', error.response.data);
|
||||||
|
if (error.response.status === 402) {
|
||||||
|
return new NextResponse(JSON.stringify({ error: error.response.data.detail }), {
|
||||||
|
status: 402,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 使用 NextResponse 构建错误响应
|
||||||
|
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return new NextResponse('Method Not Allowed', {
|
||||||
|
headers: { Allow: 'POST' },
|
||||||
|
status: 405
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,46 +1,46 @@
|
|||||||
import { NextResponse, NextRequest } from "next/server";
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
import SunoApi from '@/lib/sunoApi';
|
import { sunoApi } from "@/lib/SunoApi";
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { prompt, make_instrumental, wait_audio } = body;
|
const { prompt, make_instrumental, wait_audio } = body;
|
||||||
|
|
||||||
// 校验输入参数
|
// 校验输入参数
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
return new NextResponse(JSON.stringify({ error: 'Prompt is required' }), {
|
return new NextResponse(JSON.stringify({ error: 'Prompt is required' }), {
|
||||||
status: 400,
|
status: 400,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用 SunoApi.generate 方法生成音频
|
|
||||||
const audioInfo = await SunoApi.generate(prompt, make_instrumental == true, wait_audio == true);
|
|
||||||
|
|
||||||
// 使用 NextResponse 构建成功响应
|
|
||||||
return new NextResponse(JSON.stringify(audioInfo), {
|
|
||||||
status: 200,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error generating custom audio:', JSON.stringify(error.response.data));
|
|
||||||
if (error.response.status === 402) {
|
|
||||||
return new NextResponse(JSON.stringify({ error: error.response.data.detail }), {
|
|
||||||
status: 402,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 使用 NextResponse 构建错误响应
|
|
||||||
return new NextResponse(JSON.stringify({ error: 'Internal server error: ' + JSON.stringify(error.response.data.detail) }), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new NextResponse('Method Not Allowed', {
|
|
||||||
headers: { Allow: 'POST' },
|
|
||||||
status: 405
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 SunoApi.generate 方法生成音频
|
||||||
|
const audioInfo = await (await sunoApi).generate(prompt, make_instrumental == true, wait_audio == true);
|
||||||
|
|
||||||
|
// 使用 NextResponse 构建成功响应
|
||||||
|
return new NextResponse(JSON.stringify(audioInfo), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error generating custom audio:', JSON.stringify(error.response.data));
|
||||||
|
if (error.response.status === 402) {
|
||||||
|
return new NextResponse(JSON.stringify({ error: error.response.data.detail }), {
|
||||||
|
status: 402,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 使用 NextResponse 构建错误响应
|
||||||
|
return new NextResponse(JSON.stringify({ error: 'Internal server error: ' + JSON.stringify(error.response.data.detail) }), {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return new NextResponse('Method Not Allowed', {
|
||||||
|
headers: { Allow: 'POST' },
|
||||||
|
status: 405
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,38 +1,38 @@
|
|||||||
import { NextResponse, NextRequest } from "next/server";
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
import SunoApi from '@/lib/sunoApi';
|
import { sunoApi } from "@/lib/SunoApi";
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
try {
|
try {
|
||||||
// 修复了获取查询参数的方式
|
// 修复了获取查询参数的方式
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const songIds = url.searchParams.get('ids');
|
const songIds = url.searchParams.get('ids');
|
||||||
let audioInfo = [];
|
let audioInfo = [];
|
||||||
if (songIds && songIds.length > 0) {
|
if (songIds && songIds.length > 0) {
|
||||||
const idsArray = songIds.split(',');
|
const idsArray = songIds.split(',');
|
||||||
// 调用 SunoApi.get 方法获取音频信息
|
// 调用 SunoApi.get 方法获取音频信息
|
||||||
audioInfo = await SunoApi.get(idsArray);
|
audioInfo = await (await sunoApi).get(idsArray);
|
||||||
} else {
|
} else {
|
||||||
audioInfo = await SunoApi.get();
|
audioInfo = await (await sunoApi).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 NextResponse 构建成功响应
|
// 使用 NextResponse 构建成功响应
|
||||||
return new NextResponse(JSON.stringify(audioInfo), {
|
return new NextResponse(JSON.stringify(audioInfo), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching audio:', error);
|
console.error('Error fetching audio:', error);
|
||||||
// 使用 NextResponse 构建错误响应
|
// 使用 NextResponse 构建错误响应
|
||||||
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new NextResponse('Method Not Allowed', {
|
|
||||||
headers: { Allow: 'GET' },
|
|
||||||
status: 405
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return new NextResponse('Method Not Allowed', {
|
||||||
|
headers: { Allow: 'GET' },
|
||||||
|
status: 405
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,29 +1,29 @@
|
|||||||
import { NextResponse, NextRequest } from "next/server";
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
import SunoApi from '@/lib/sunoApi';
|
import { sunoApi } from "@/lib/SunoApi";
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
try {
|
try {
|
||||||
// 调用 SunoApi.get_limit 方法获取剩余的信用额度
|
// 调用 SunoApi.get_limit 方法获取剩余的信用额度
|
||||||
const limit = await SunoApi.get_credits();
|
const limit = await (await sunoApi).get_credits();
|
||||||
|
|
||||||
// 使用 NextResponse 构建成功响应
|
// 使用 NextResponse 构建成功响应
|
||||||
return new NextResponse(JSON.stringify(limit), {
|
return new NextResponse(JSON.stringify(limit), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching limit:', error);
|
console.error('Error fetching limit:', error);
|
||||||
// 使用 NextResponse 构建错误响应
|
// 使用 NextResponse 构建错误响应
|
||||||
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new NextResponse('Method Not Allowed', {
|
|
||||||
headers: { Allow: 'GET' },
|
|
||||||
status: 405
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return new NextResponse('Method Not Allowed', {
|
||||||
|
headers: { Allow: 'GET' },
|
||||||
|
status: 405
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import UserAgent from 'user-agents';
|
import UserAgent from 'user-agents';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
|
import { wrapper } from "axios-cookiejar-support";
|
||||||
|
import { CookieJar } from "tough-cookie";
|
||||||
|
|
||||||
const logger = pino();
|
const logger = pino();
|
||||||
|
|
||||||
|
|
||||||
@ -20,6 +23,7 @@ interface AudioInfo {
|
|||||||
tags?: string;
|
tags?: string;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停指定的秒数。
|
* 暂停指定的秒数。
|
||||||
* @param x 最小秒数。
|
* @param x 最小秒数。
|
||||||
@ -39,71 +43,75 @@ const sleep = (x: number, y?: number): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SunoApi {
|
class SunoApi {
|
||||||
private static baseUrl: string = 'https://studio-api.suno.ai';
|
private static BASE_URL: string = 'https://studio-api.suno.ai';
|
||||||
private static clerkBaseUrl: string = 'https://clerk.suno.ai';
|
private static CLERK_BASE_URL: string = 'https://clerk.suno.ai';
|
||||||
private static cookie: string = process.env.SUNO_COOKIE || '';
|
|
||||||
private static userAgent: string = new UserAgent().toString();
|
|
||||||
private static sid: string | null = null;
|
|
||||||
|
|
||||||
private static async getAuthToken(): Promise<string> {
|
private readonly client: AxiosInstance;
|
||||||
|
private sid?: string;
|
||||||
|
|
||||||
|
constructor(cookie: string) {
|
||||||
|
const cookieJar = new CookieJar();
|
||||||
|
const randomUserAgent = new UserAgent(/Chrome/).random().toString();
|
||||||
|
this.client = wrapper(axios.create({
|
||||||
|
jar: cookieJar,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': randomUserAgent,
|
||||||
|
'Cookie': cookie
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init(): Promise<SunoApi> {
|
||||||
|
const token = await this.getAuthToken();
|
||||||
|
this.client.interceptors.request.use(function (config) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
|
return config;
|
||||||
|
})
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async getAuthToken(): Promise<string> {
|
||||||
// 获取会话ID的URL
|
// 获取会话ID的URL
|
||||||
const getSessionUrl = `${SunoApi.clerkBaseUrl}/v1/client?_clerk_js_version=4.70.5`;
|
const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.70.5`;
|
||||||
// 交换令牌的URL模板
|
// 交换令牌的URL模板
|
||||||
const exchangeTokenUrlTemplate = `${SunoApi.clerkBaseUrl}/v1/client/sessions/{sid}/tokens/api?_clerk_js_version=4.70.0`;
|
const exchangeTokenUrlTemplate = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/{sid}/tokens/api?_clerk_js_version=4.70.0`;
|
||||||
|
|
||||||
// 获取会话ID
|
// 获取会话ID
|
||||||
const sessionResponse = await axios.get(getSessionUrl, {
|
const sessionResponse = await this.client.get(getSessionUrl);
|
||||||
headers: {
|
const sid = sessionResponse.data.response['last_active_session_id'];
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
'Cookie': SunoApi.cookie,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const sid = sessionResponse.data.response?.last_active_session_id;
|
|
||||||
if (!sid) {
|
if (!sid) {
|
||||||
throw new Error("Failed to get session id");
|
throw new Error("Failed to get session id");
|
||||||
}
|
}
|
||||||
SunoApi.sid = sid; // 保存会话ID以备后用
|
// 保存会话ID以备后用
|
||||||
|
this.sid = sid;
|
||||||
|
|
||||||
// 使用会话ID获取JWT令牌
|
// 使用会话ID获取JWT令牌
|
||||||
const exchangeTokenUrl = exchangeTokenUrlTemplate.replace('{sid}', sid);
|
const exchangeTokenUrl = exchangeTokenUrlTemplate.replace('{sid}', sid);
|
||||||
const tokenResponse = await axios.post(
|
const tokenResponse = await this.client.post(exchangeTokenUrl);
|
||||||
exchangeTokenUrl,
|
return tokenResponse.data['jwt'];
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
'Cookie': SunoApi.cookie,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return tokenResponse.data.jwt;
|
|
||||||
}
|
}
|
||||||
public static async KeepAlive(): Promise<void> {
|
|
||||||
if (!SunoApi.sid) {
|
public async KeepAlive(): Promise<void> {
|
||||||
|
if (!this.sid) {
|
||||||
throw new Error("Session ID is not set. Cannot renew token.");
|
throw new Error("Session ID is not set. Cannot renew token.");
|
||||||
}
|
}
|
||||||
// 续订会话令牌的URL
|
// 续订会话令牌的URL
|
||||||
const renewUrl = `${SunoApi.clerkBaseUrl}/v1/client/sessions/${SunoApi.sid}/tokens/api?_clerk_js_version=4.70.0`;
|
const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens/api?_clerk_js_version=4.70.0`;
|
||||||
// 续订会话令牌
|
// 续订会话令牌
|
||||||
const renewResponse = await axios.post(
|
const renewResponse = await this.client.post(renewUrl);
|
||||||
renewUrl,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
'Cookie': SunoApi.cookie,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
logger.info("KeepAlive...\n");
|
logger.info("KeepAlive...\n");
|
||||||
await sleep(1, 2);
|
await sleep(1, 2);
|
||||||
const newToken = renewResponse.data.jwt;
|
const newToken = renewResponse.data['jwt'];
|
||||||
// 更新请求头中的Authorization字段,使用新的JWT令牌
|
// 更新请求头中的Authorization字段,使用新的JWT令牌
|
||||||
axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
|
this.client.interceptors.request.use(function (config) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${newToken}`
|
||||||
|
return config;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async generate(
|
public async generate(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
make_instrumental: boolean = false,
|
make_instrumental: boolean = false,
|
||||||
wait_audio: boolean = false,
|
wait_audio: boolean = false,
|
||||||
@ -118,7 +126,7 @@ class SunoApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates custom audio based on provided parameters.
|
* Generates custom audio based on provided parameters.
|
||||||
*
|
*
|
||||||
* @param prompt The text prompt to generate audio from.
|
* @param prompt The text prompt to generate audio from.
|
||||||
* @param tags Tags to categorize the generated audio.
|
* @param tags Tags to categorize the generated audio.
|
||||||
* @param title The title for the generated audio.
|
* @param title The title for the generated audio.
|
||||||
@ -126,7 +134,7 @@ class SunoApi {
|
|||||||
* @param wait_audio Indicates if the method should wait for the audio file to be fully generated before returning.
|
* @param wait_audio Indicates if the method should wait for the audio file to be fully generated before returning.
|
||||||
* @returns A promise that resolves to an array of AudioInfo objects representing the generated audios.
|
* @returns A promise that resolves to an array of AudioInfo objects representing the generated audios.
|
||||||
*/
|
*/
|
||||||
public static async custom_generate(
|
public async custom_generate(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
tags: string,
|
tags: string,
|
||||||
title: string,
|
title: string,
|
||||||
@ -143,7 +151,7 @@ class SunoApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates songs based on the provided parameters.
|
* Generates songs based on the provided parameters.
|
||||||
*
|
*
|
||||||
* @param prompt The text prompt to generate songs from.
|
* @param prompt The text prompt to generate songs from.
|
||||||
* @param isCustom Indicates if the generation should consider custom parameters like tags and title.
|
* @param isCustom Indicates if the generation should consider custom parameters like tags and title.
|
||||||
* @param tags Optional tags to categorize the song, used only if isCustom is true.
|
* @param tags Optional tags to categorize the song, used only if isCustom is true.
|
||||||
@ -152,7 +160,7 @@ class SunoApi {
|
|||||||
* @param wait_audio Indicates if the method should wait for the audio file to be fully generated before returning.
|
* @param wait_audio Indicates if the method should wait for the audio file to be fully generated before returning.
|
||||||
* @returns A promise that resolves to an array of AudioInfo objects representing the generated songs.
|
* @returns A promise that resolves to an array of AudioInfo objects representing the generated songs.
|
||||||
*/
|
*/
|
||||||
private static async generateSongs(
|
private async generateSongs(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
isCustom: boolean,
|
isCustom: boolean,
|
||||||
tags?: string,
|
tags?: string,
|
||||||
@ -182,14 +190,10 @@ class SunoApi {
|
|||||||
wait_audio: wait_audio,
|
wait_audio: wait_audio,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
});
|
});
|
||||||
const response = await axios.post(
|
const response = await this.client.post(
|
||||||
`${SunoApi.baseUrl}/api/generate/v2/`,
|
`${SunoApi.BASE_URL}/api/generate/v2/`,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${authToken}`,
|
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
},
|
|
||||||
timeout: 10000, // 10 seconds timeout
|
timeout: 10000, // 10 seconds timeout
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -197,14 +201,14 @@ class SunoApi {
|
|||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error("Error response:" + response.statusText);
|
throw new Error("Error response:" + response.statusText);
|
||||||
}
|
}
|
||||||
const songIds = response.data.clips.map((audio: any) => audio.id);
|
const songIds = response.data['clips'].map((audio: any) => audio.id);
|
||||||
//Want to wait for music file generation
|
//Want to wait for music file generation
|
||||||
if (wait_audio === true) {
|
if (wait_audio) {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let lastResponse: AudioInfo[] = [];
|
let lastResponse: AudioInfo[] = [];
|
||||||
await sleep(5, 5);
|
await sleep(5, 5);
|
||||||
while (Date.now() - startTime < 100000) {
|
while (Date.now() - startTime < 100000) {
|
||||||
const response = await SunoApi.get(songIds);
|
const response = await this.get(songIds);
|
||||||
const allCompleted = response.every(
|
const allCompleted = response.every(
|
||||||
audio => audio.status === 'streaming' || audio.status === 'complete'
|
audio => audio.status === 'streaming' || audio.status === 'complete'
|
||||||
);
|
);
|
||||||
@ -213,12 +217,12 @@ class SunoApi {
|
|||||||
}
|
}
|
||||||
lastResponse = response;
|
lastResponse = response;
|
||||||
await sleep(3, 6);
|
await sleep(3, 6);
|
||||||
this.KeepAlive();
|
await this.KeepAlive();
|
||||||
}
|
}
|
||||||
return lastResponse;
|
return lastResponse;
|
||||||
} else {
|
} else {
|
||||||
this.KeepAlive();
|
await this.KeepAlive();
|
||||||
return response.data.clips.map((audio: any) => ({
|
return response.data['clips'].map((audio: any) => ({
|
||||||
id: audio.id,
|
id: audio.id,
|
||||||
title: audio.title,
|
title: audio.title,
|
||||||
image_url: audio.image_url,
|
image_url: audio.image_url,
|
||||||
@ -236,12 +240,13 @@ class SunoApi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the lyrics (prompt) from the audio metadata into a more readable format.
|
* Processes the lyrics (prompt) from the audio metadata into a more readable format.
|
||||||
* @param prompt The original lyrics text.
|
* @param prompt The original lyrics text.
|
||||||
* @returns The processed lyrics text.
|
* @returns The processed lyrics text.
|
||||||
*/
|
*/
|
||||||
private static parseLyrics(prompt: string): string {
|
private parseLyrics(prompt: string): string {
|
||||||
// Assuming the original lyrics are separated by a specific delimiter (e.g., newline), we can convert it into a more readable format.
|
// Assuming the original lyrics are separated by a specific delimiter (e.g., newline), we can convert it into a more readable format.
|
||||||
// The implementation here can be adjusted according to the actual lyrics format.
|
// The implementation here can be adjusted according to the actual lyrics format.
|
||||||
// For example, if the lyrics exist as continuous text, it might be necessary to split them based on specific markers (such as periods, commas, etc.).
|
// For example, if the lyrics exist as continuous text, it might be necessary to split them based on specific markers (such as periods, commas, etc.).
|
||||||
@ -262,19 +267,16 @@ class SunoApi {
|
|||||||
* @param songIds An optional array of song IDs to retrieve information for.
|
* @param songIds An optional array of song IDs to retrieve information for.
|
||||||
* @returns A promise that resolves to an array of AudioInfo objects.
|
* @returns A promise that resolves to an array of AudioInfo objects.
|
||||||
*/
|
*/
|
||||||
public static async get(songIds?: string[]): Promise<AudioInfo[]> {
|
public async get(songIds?: string[]): Promise<AudioInfo[]> {
|
||||||
const authToken = await this.getAuthToken();
|
const authToken = await this.getAuthToken();
|
||||||
let url = `${SunoApi.baseUrl}/api/feed/`;
|
let url = `${SunoApi.BASE_URL}/api/feed/`;
|
||||||
if (songIds) {
|
if (songIds) {
|
||||||
url = `${url}?ids=${songIds.join(',')}`;
|
url = `${url}?ids=${songIds.join(',')}`;
|
||||||
}
|
}
|
||||||
logger.info("Get audio status: ", url);
|
logger.info("Get audio status: ", url);
|
||||||
const response = await axios.get(url, {
|
const response = await this.client.get(url, {
|
||||||
headers: {
|
// 3 seconds timeout
|
||||||
'Authorization': `Bearer ${authToken}`,
|
timeout: 3000
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
},
|
|
||||||
timeout: 3000, // 3 seconds timeout
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const audios = response.data;
|
const audios = response.data;
|
||||||
@ -296,14 +298,9 @@ class SunoApi {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async get_credits(): Promise<object> {
|
public async get_credits(): Promise<object> {
|
||||||
const authToken = await this.getAuthToken();
|
const authToken = await this.getAuthToken();
|
||||||
const response = await axios.get(`${SunoApi.baseUrl}/api/billing/info/`, {
|
const response = await this.client.get(`${SunoApi.BASE_URL}/api/billing/info/`);
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${authToken}`,
|
|
||||||
'User-Agent': SunoApi.userAgent,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
credits_left: response.data.total_credits_left,
|
credits_left: response.data.total_credits_left,
|
||||||
period: response.data.period,
|
period: response.data.period,
|
||||||
@ -313,4 +310,10 @@ class SunoApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SunoApi;
|
const newSunoApi = async (cookie: string) => {
|
||||||
|
const sunoApi = new SunoApi(cookie);
|
||||||
|
await sunoApi.init();
|
||||||
|
return sunoApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sunoApi = newSunoApi(process.env.SUNO_COOKIE || '');
|
Loading…
Reference in New Issue
Block a user