Merge pull request #178 from WSeubring/feature/add-page-query-param-to-get
Feature: add page query param to get
This commit is contained in:
		
						commit
						9941933771
					
				
							
								
								
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "trailingComma": "none",
 | 
				
			||||||
 | 
					  "singleQuote": true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,20 +1,22 @@
 | 
				
			|||||||
import { NextResponse, NextRequest } from "next/server";
 | 
					import { NextResponse, NextRequest } from 'next/server';
 | 
				
			||||||
import { sunoApi } from "@/lib/SunoApi";
 | 
					import { sunoApi } from '@/lib/SunoApi';
 | 
				
			||||||
import { corsHeaders } from "@/lib/utils";
 | 
					import { corsHeaders } from '@/lib/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dynamic = "force-dynamic";
 | 
					export const dynamic = 'force-dynamic';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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');
 | 
				
			||||||
 | 
					      const page = url.searchParams.get('page');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      let audioInfo = [];
 | 
					      let audioInfo = [];
 | 
				
			||||||
      if (songIds && songIds.length > 0) {
 | 
					      if (songIds && songIds.length > 0) {
 | 
				
			||||||
        const idsArray = songIds.split(',');
 | 
					        const idsArray = songIds.split(',');
 | 
				
			||||||
        audioInfo = await (await sunoApi).get(idsArray);
 | 
					        audioInfo = await (await sunoApi).get(idsArray, page);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        audioInfo = await (await sunoApi).get();
 | 
					        audioInfo = await (await sunoApi).get(undefined, page);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return new NextResponse(JSON.stringify(audioInfo), {
 | 
					      return new NextResponse(JSON.stringify(audioInfo), {
 | 
				
			||||||
@ -27,13 +29,16 @@ export async function GET(req: NextRequest) {
 | 
				
			|||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      console.error('Error fetching audio:', error);
 | 
					      console.error('Error fetching audio:', error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
 | 
					      return new NextResponse(
 | 
				
			||||||
 | 
					        JSON.stringify({ error: 'Internal server error' }),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
          status: 500,
 | 
					          status: 500,
 | 
				
			||||||
          headers: {
 | 
					          headers: {
 | 
				
			||||||
            'Content-Type': 'application/json',
 | 
					            'Content-Type': 'application/json',
 | 
				
			||||||
            ...corsHeaders
 | 
					            ...corsHeaders
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
      });
 | 
					        }
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    return new NextResponse('Method Not Allowed', {
 | 
					    return new NextResponse('Method Not Allowed', {
 | 
				
			||||||
 | 
				
			|||||||
@ -307,6 +307,15 @@
 | 
				
			|||||||
            "schema": {
 | 
					            "schema": {
 | 
				
			||||||
              "type": "string"
 | 
					              "type": "string"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "in": "query",
 | 
				
			||||||
 | 
					            "name": "page",
 | 
				
			||||||
 | 
					            "description": "Page number",
 | 
				
			||||||
 | 
					            "required": false,
 | 
				
			||||||
 | 
					            "schema": {
 | 
				
			||||||
 | 
					              "type": "number"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "responses": {
 | 
					        "responses": {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,12 @@
 | 
				
			|||||||
import axios, { AxiosInstance } 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 { wrapper } from 'axios-cookiejar-support';
 | 
				
			||||||
import { CookieJar } from "tough-cookie";
 | 
					import { CookieJar } from 'tough-cookie';
 | 
				
			||||||
import { sleep } from "@/lib/utils";
 | 
					import { sleep } from '@/lib/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = pino();
 | 
					const logger = pino();
 | 
				
			||||||
export const DEFAULT_MODEL = "chirp-v3-5";
 | 
					export const DEFAULT_MODEL = 'chirp-v3-5';
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AudioInfo {
 | 
					export interface AudioInfo {
 | 
				
			||||||
  id: string; // Unique identifier for the audio
 | 
					  id: string; // Unique identifier for the audio
 | 
				
			||||||
@ -19,7 +18,7 @@ export interface AudioInfo {
 | 
				
			|||||||
  created_at: string; // Date and time when the audio was created
 | 
					  created_at: string; // Date and time when the audio was created
 | 
				
			||||||
  model_name: string; // Name of the model used for audio generation
 | 
					  model_name: string; // Name of the model used for audio generation
 | 
				
			||||||
  gpt_description_prompt?: string; // Prompt for GPT description
 | 
					  gpt_description_prompt?: string; // Prompt for GPT description
 | 
				
			||||||
  prompt?: string; // Prompt for audio generation 
 | 
					  prompt?: string; // Prompt for audio generation
 | 
				
			||||||
  status: string; // Status
 | 
					  status: string; // Status
 | 
				
			||||||
  type?: string;
 | 
					  type?: string;
 | 
				
			||||||
  tags?: string; // Genre of music.
 | 
					  tags?: string; // Genre of music.
 | 
				
			||||||
@ -41,16 +40,19 @@ class SunoApi {
 | 
				
			|||||||
  constructor(cookie: string) {
 | 
					  constructor(cookie: string) {
 | 
				
			||||||
    const cookieJar = new CookieJar();
 | 
					    const cookieJar = new CookieJar();
 | 
				
			||||||
    const randomUserAgent = new UserAgent(/Chrome/).random().toString();
 | 
					    const randomUserAgent = new UserAgent(/Chrome/).random().toString();
 | 
				
			||||||
    this.client = wrapper(axios.create({
 | 
					    this.client = wrapper(
 | 
				
			||||||
 | 
					      axios.create({
 | 
				
			||||||
        jar: cookieJar,
 | 
					        jar: cookieJar,
 | 
				
			||||||
        withCredentials: true,
 | 
					        withCredentials: true,
 | 
				
			||||||
        headers: {
 | 
					        headers: {
 | 
				
			||||||
          'User-Agent': randomUserAgent,
 | 
					          'User-Agent': randomUserAgent,
 | 
				
			||||||
        'Cookie': cookie
 | 
					          Cookie: cookie
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }))
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    this.client.interceptors.request.use((config) => {
 | 
					    this.client.interceptors.request.use((config) => {
 | 
				
			||||||
      if (this.currentToken) { // Use the current token status
 | 
					      if (this.currentToken) {
 | 
				
			||||||
 | 
					        // Use the current token status
 | 
				
			||||||
        config.headers['Authorization'] = `Bearer ${this.currentToken}`;
 | 
					        config.headers['Authorization'] = `Bearer ${this.currentToken}`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return config;
 | 
					      return config;
 | 
				
			||||||
@ -69,11 +71,13 @@ class SunoApi {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  private async getClerkLatestVersion() {
 | 
					  private async getClerkLatestVersion() {
 | 
				
			||||||
    // URL to get clerk version ID
 | 
					    // URL to get clerk version ID
 | 
				
			||||||
    const getClerkVersionUrl = `${SunoApi.JSDELIVR_BASE_URL}/v1/package/npm/@clerk/clerk-js`; 
 | 
					    const getClerkVersionUrl = `${SunoApi.JSDELIVR_BASE_URL}/v1/package/npm/@clerk/clerk-js`;
 | 
				
			||||||
    // Get clerk version ID
 | 
					    // Get clerk version ID
 | 
				
			||||||
    const versionListResponse = await this.client.get(getClerkVersionUrl);
 | 
					    const versionListResponse = await this.client.get(getClerkVersionUrl);
 | 
				
			||||||
    if (!versionListResponse?.data?.['tags']['latest']) {
 | 
					    if (!versionListResponse?.data?.['tags']['latest']) {
 | 
				
			||||||
      throw new Error("Failed to get clerk version info, Please try again later");
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'Failed to get clerk version info, Please try again later'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Save clerk version ID for auth
 | 
					    // Save clerk version ID for auth
 | 
				
			||||||
    this.clerkVersion = versionListResponse?.data?.['tags']['latest'];
 | 
					    this.clerkVersion = versionListResponse?.data?.['tags']['latest'];
 | 
				
			||||||
@ -84,11 +88,13 @@ class SunoApi {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  private async getAuthToken() {
 | 
					  private async getAuthToken() {
 | 
				
			||||||
    // URL to get session ID
 | 
					    // URL to get session ID
 | 
				
			||||||
    const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=${this.clerkVersion}`; 
 | 
					    const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=${this.clerkVersion}`;
 | 
				
			||||||
    // Get session ID
 | 
					    // Get session ID
 | 
				
			||||||
    const sessionResponse = await this.client.get(getSessionUrl);
 | 
					    const sessionResponse = await this.client.get(getSessionUrl);
 | 
				
			||||||
    if (!sessionResponse?.data?.response?.['last_active_session_id']) {
 | 
					    if (!sessionResponse?.data?.response?.['last_active_session_id']) {
 | 
				
			||||||
      throw new Error("Failed to get session id, you may need to update the SUNO_COOKIE");
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'Failed to get session id, you may need to update the SUNO_COOKIE'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Save session ID for later use
 | 
					    // Save session ID for later use
 | 
				
			||||||
    this.sid = sessionResponse.data.response['last_active_session_id'];
 | 
					    this.sid = sessionResponse.data.response['last_active_session_id'];
 | 
				
			||||||
@ -100,13 +106,13 @@ class SunoApi {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async keepAlive(isWait?: boolean): Promise<void> {
 | 
					  public async keepAlive(isWait?: boolean): Promise<void> {
 | 
				
			||||||
    if (!this.sid) {
 | 
					    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 to renew session token
 | 
					    // URL to renew session token
 | 
				
			||||||
    const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version==${this.clerkVersion}`; 
 | 
					    const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version==${this.clerkVersion}`;
 | 
				
			||||||
    // Renew session token
 | 
					    // Renew session token
 | 
				
			||||||
    const renewResponse = await this.client.post(renewUrl);
 | 
					    const renewResponse = await this.client.post(renewUrl);
 | 
				
			||||||
    logger.info("KeepAlive...\n");
 | 
					    logger.info('KeepAlive...\n');
 | 
				
			||||||
    if (isWait) {
 | 
					    if (isWait) {
 | 
				
			||||||
      await sleep(1, 2);
 | 
					      await sleep(1, 2);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -126,15 +132,22 @@ class SunoApi {
 | 
				
			|||||||
    prompt: string,
 | 
					    prompt: string,
 | 
				
			||||||
    make_instrumental: boolean = false,
 | 
					    make_instrumental: boolean = false,
 | 
				
			||||||
    model?: string,
 | 
					    model?: string,
 | 
				
			||||||
    wait_audio: boolean = false,
 | 
					    wait_audio: boolean = false
 | 
				
			||||||
 | 
					 | 
				
			||||||
  ): Promise<AudioInfo[]> {
 | 
					  ): Promise<AudioInfo[]> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    const startTime = Date.now();
 | 
					    const startTime = Date.now();
 | 
				
			||||||
    const audios = this.generateSongs(prompt, false, undefined, undefined, make_instrumental, model, wait_audio);
 | 
					    const audios = this.generateSongs(
 | 
				
			||||||
 | 
					      prompt,
 | 
				
			||||||
 | 
					      false,
 | 
				
			||||||
 | 
					      undefined,
 | 
				
			||||||
 | 
					      undefined,
 | 
				
			||||||
 | 
					      make_instrumental,
 | 
				
			||||||
 | 
					      model,
 | 
				
			||||||
 | 
					      wait_audio
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    const costTime = Date.now() - startTime;
 | 
					    const costTime = Date.now() - startTime;
 | 
				
			||||||
    logger.info("Generate Response:\n" + JSON.stringify(audios, null, 2));
 | 
					    logger.info('Generate Response:\n' + JSON.stringify(audios, null, 2));
 | 
				
			||||||
    logger.info("Cost time: " + costTime);
 | 
					    logger.info('Cost time: ' + costTime);
 | 
				
			||||||
    return audios;
 | 
					    return audios;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -152,11 +165,11 @@ class SunoApi {
 | 
				
			|||||||
      `${SunoApi.BASE_URL}/api/generate/concat/v2/`,
 | 
					      `${SunoApi.BASE_URL}/api/generate/concat/v2/`,
 | 
				
			||||||
      payload,
 | 
					      payload,
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        timeout: 10000, // 10 seconds timeout
 | 
					        timeout: 10000 // 10 seconds timeout
 | 
				
			||||||
      },
 | 
					      }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (response.status !== 200) {
 | 
					    if (response.status !== 200) {
 | 
				
			||||||
      throw new Error("Error response:" + response.statusText);
 | 
					      throw new Error('Error response:' + response.statusText);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return response.data;
 | 
					    return response.data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -172,22 +185,33 @@ class SunoApi {
 | 
				
			|||||||
   * @param negative_tags Negative tags that should not be included in the generated audio.
 | 
					   * @param negative_tags Negative tags that should not be included in the generated audio.
 | 
				
			||||||
   * @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 async custom_generate(
 | 
					  public async custom_generate(
 | 
				
			||||||
    prompt: string,
 | 
					    prompt: string,
 | 
				
			||||||
    tags: string,
 | 
					    tags: string,
 | 
				
			||||||
    title: string,
 | 
					    title: string,
 | 
				
			||||||
    make_instrumental: boolean = false,
 | 
					    make_instrumental: boolean = false,
 | 
				
			||||||
    model?: string,
 | 
					    model?: string,
 | 
				
			||||||
    wait_audio: boolean = false,
 | 
					    wait_audio: boolean = false,
 | 
				
			||||||
  negative_tags?: string,
 | 
					    negative_tags?: string
 | 
				
			||||||
): Promise<AudioInfo[]> {
 | 
					  ): Promise<AudioInfo[]> {
 | 
				
			||||||
    const startTime = Date.now();
 | 
					    const startTime = Date.now();
 | 
				
			||||||
  const audios = await this.generateSongs(prompt, true, tags, title, make_instrumental, model, wait_audio, negative_tags);
 | 
					    const audios = await this.generateSongs(
 | 
				
			||||||
 | 
					      prompt,
 | 
				
			||||||
 | 
					      true,
 | 
				
			||||||
 | 
					      tags,
 | 
				
			||||||
 | 
					      title,
 | 
				
			||||||
 | 
					      make_instrumental,
 | 
				
			||||||
 | 
					      model,
 | 
				
			||||||
 | 
					      wait_audio,
 | 
				
			||||||
 | 
					      negative_tags
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    const costTime = Date.now() - startTime;
 | 
					    const costTime = Date.now() - startTime;
 | 
				
			||||||
  logger.info("Custom Generate Response:\n" + JSON.stringify(audios, null, 2));
 | 
					    logger.info(
 | 
				
			||||||
  logger.info("Cost time: " + costTime);
 | 
					      'Custom Generate Response:\n' + JSON.stringify(audios, null, 2)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    logger.info('Cost time: ' + costTime);
 | 
				
			||||||
    return audios;
 | 
					    return audios;
 | 
				
			||||||
}
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Generates songs based on the provided parameters.
 | 
					   * Generates songs based on the provided parameters.
 | 
				
			||||||
@ -209,13 +233,13 @@ public async custom_generate(
 | 
				
			|||||||
    make_instrumental?: boolean,
 | 
					    make_instrumental?: boolean,
 | 
				
			||||||
    model?: string,
 | 
					    model?: string,
 | 
				
			||||||
    wait_audio: boolean = false,
 | 
					    wait_audio: boolean = false,
 | 
				
			||||||
    negative_tags?: string,
 | 
					    negative_tags?: string
 | 
				
			||||||
  ): Promise<AudioInfo[]> {
 | 
					  ): Promise<AudioInfo[]> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    const payload: any = {
 | 
					    const payload: any = {
 | 
				
			||||||
      make_instrumental: make_instrumental == true,
 | 
					      make_instrumental: make_instrumental == true,
 | 
				
			||||||
      mv: model || DEFAULT_MODEL,
 | 
					      mv: model || DEFAULT_MODEL,
 | 
				
			||||||
      prompt: "",
 | 
					      prompt: ''
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if (isCustom) {
 | 
					    if (isCustom) {
 | 
				
			||||||
      payload.tags = tags;
 | 
					      payload.tags = tags;
 | 
				
			||||||
@ -225,7 +249,10 @@ public async custom_generate(
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      payload.gpt_description_prompt = prompt;
 | 
					      payload.gpt_description_prompt = prompt;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    logger.info("generateSongs payload:\n" + JSON.stringify({
 | 
					    logger.info(
 | 
				
			||||||
 | 
					      'generateSongs payload:\n' +
 | 
				
			||||||
 | 
					        JSON.stringify(
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
            prompt: prompt,
 | 
					            prompt: prompt,
 | 
				
			||||||
            isCustom: isCustom,
 | 
					            isCustom: isCustom,
 | 
				
			||||||
            tags: tags,
 | 
					            tags: tags,
 | 
				
			||||||
@ -233,18 +260,24 @@ public async custom_generate(
 | 
				
			|||||||
            make_instrumental: make_instrumental,
 | 
					            make_instrumental: make_instrumental,
 | 
				
			||||||
            wait_audio: wait_audio,
 | 
					            wait_audio: wait_audio,
 | 
				
			||||||
            negative_tags: negative_tags,
 | 
					            negative_tags: negative_tags,
 | 
				
			||||||
      payload: payload,
 | 
					            payload: payload
 | 
				
			||||||
    }, null, 2));
 | 
					          },
 | 
				
			||||||
 | 
					          null,
 | 
				
			||||||
 | 
					          2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    const response = await this.client.post(
 | 
					    const response = await this.client.post(
 | 
				
			||||||
      `${SunoApi.BASE_URL}/api/generate/v2/`,
 | 
					      `${SunoApi.BASE_URL}/api/generate/v2/`,
 | 
				
			||||||
      payload,
 | 
					      payload,
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        timeout: 10000, // 10 seconds timeout
 | 
					        timeout: 10000 // 10 seconds timeout
 | 
				
			||||||
      },
 | 
					      }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    logger.info(
 | 
				
			||||||
 | 
					      'generateSongs Response:\n' + JSON.stringify(response.data, null, 2)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    logger.info("generateSongs Response:\n" + JSON.stringify(response.data, null, 2));
 | 
					 | 
				
			||||||
    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
 | 
				
			||||||
@ -255,11 +288,9 @@ public async custom_generate(
 | 
				
			|||||||
      while (Date.now() - startTime < 100000) {
 | 
					      while (Date.now() - startTime < 100000) {
 | 
				
			||||||
        const response = await this.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'
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        const allError = response.every(
 | 
					 | 
				
			||||||
          audio => audio.status === 'error'
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        const allError = response.every((audio) => audio.status === 'error');
 | 
				
			||||||
        if (allCompleted || allError) {
 | 
					        if (allCompleted || allError) {
 | 
				
			||||||
          return response;
 | 
					          return response;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -285,7 +316,7 @@ public async custom_generate(
 | 
				
			|||||||
        type: audio.metadata.type,
 | 
					        type: audio.metadata.type,
 | 
				
			||||||
        tags: audio.metadata.tags,
 | 
					        tags: audio.metadata.tags,
 | 
				
			||||||
        negative_tags: audio.metadata.negative_tags,
 | 
					        negative_tags: audio.metadata.negative_tags,
 | 
				
			||||||
        duration: audio.metadata.duration,
 | 
					        duration: audio.metadata.duration
 | 
				
			||||||
      }));
 | 
					      }));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -298,14 +329,21 @@ public async custom_generate(
 | 
				
			|||||||
  public async generateLyrics(prompt: string): Promise<string> {
 | 
					  public async generateLyrics(prompt: string): Promise<string> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    // Initiate lyrics generation
 | 
					    // Initiate lyrics generation
 | 
				
			||||||
    const generateResponse = await this.client.post(`${SunoApi.BASE_URL}/api/generate/lyrics/`, { prompt });
 | 
					    const generateResponse = await this.client.post(
 | 
				
			||||||
 | 
					      `${SunoApi.BASE_URL}/api/generate/lyrics/`,
 | 
				
			||||||
 | 
					      { prompt }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    const generateId = generateResponse.data.id;
 | 
					    const generateId = generateResponse.data.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Poll for lyrics completion
 | 
					    // Poll for lyrics completion
 | 
				
			||||||
    let lyricsResponse = await this.client.get(`${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`);
 | 
					    let lyricsResponse = await this.client.get(
 | 
				
			||||||
 | 
					      `${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    while (lyricsResponse?.data?.status !== 'complete') {
 | 
					    while (lyricsResponse?.data?.status !== 'complete') {
 | 
				
			||||||
      await sleep(2); // Wait for 2 seconds before polling again
 | 
					      await sleep(2); // Wait for 2 seconds before polling again
 | 
				
			||||||
      lyricsResponse = await this.client.get(`${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`);
 | 
					      lyricsResponse = await this.client.get(
 | 
				
			||||||
 | 
					        `${SunoApi.BASE_URL}/api/generate/lyrics/${generateId}`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Return the generated lyrics text
 | 
					    // Return the generated lyrics text
 | 
				
			||||||
@ -324,21 +362,24 @@ public async custom_generate(
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async extendAudio(
 | 
					  public async extendAudio(
 | 
				
			||||||
    audioId: string,
 | 
					    audioId: string,
 | 
				
			||||||
    prompt: string = "",
 | 
					    prompt: string = '',
 | 
				
			||||||
    continueAt: string = "0",
 | 
					    continueAt: string = '0',
 | 
				
			||||||
    tags: string = "",
 | 
					    tags: string = '',
 | 
				
			||||||
    title: string = "",
 | 
					    title: string = '',
 | 
				
			||||||
    model?: string,
 | 
					    model?: string
 | 
				
			||||||
  ): Promise<AudioInfo> {
 | 
					  ): Promise<AudioInfo> {
 | 
				
			||||||
    const response = await this.client.post(`${SunoApi.BASE_URL}/api/generate/v2/`, {
 | 
					    const response = await this.client.post(
 | 
				
			||||||
 | 
					      `${SunoApi.BASE_URL}/api/generate/v2/`,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
        continue_clip_id: audioId,
 | 
					        continue_clip_id: audioId,
 | 
				
			||||||
        continue_at: continueAt,
 | 
					        continue_at: continueAt,
 | 
				
			||||||
        mv: model || DEFAULT_MODEL,
 | 
					        mv: model || DEFAULT_MODEL,
 | 
				
			||||||
        prompt: prompt,
 | 
					        prompt: prompt,
 | 
				
			||||||
        tags: tags,
 | 
					        tags: tags,
 | 
				
			||||||
        title: title
 | 
					        title: title
 | 
				
			||||||
    });
 | 
					      }
 | 
				
			||||||
    console.log("response:\n", response);
 | 
					    );
 | 
				
			||||||
 | 
					    console.log('response:\n', response);
 | 
				
			||||||
    return response.data;
 | 
					    return response.data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -354,7 +395,7 @@ public async custom_generate(
 | 
				
			|||||||
    // The following implementation assumes that the lyrics are already separated by newlines.
 | 
					    // The following implementation assumes that the lyrics are already separated by newlines.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Split the lyrics using newline and ensure to remove empty lines.
 | 
					    // Split the lyrics using newline and ensure to remove empty lines.
 | 
				
			||||||
    const lines = prompt.split('\n').filter(line => line.trim() !== '');
 | 
					    const lines = prompt.split('\n').filter((line) => line.trim() !== '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Reassemble the processed lyrics lines into a single string, separated by newlines between each line.
 | 
					    // Reassemble the processed lyrics lines into a single string, separated by newlines between each line.
 | 
				
			||||||
    // Additional formatting logic can be added here, such as adding specific markers or handling special lines.
 | 
					    // Additional formatting logic can be added here, such as adding specific markers or handling special lines.
 | 
				
			||||||
@ -364,26 +405,36 @@ public async custom_generate(
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Retrieves audio information for the given song IDs.
 | 
					   * Retrieves audio information for the given song IDs.
 | 
				
			||||||
   * @param songIds An optional array of song IDs to retrieve information for.
 | 
					   * @param songIds An optional array of song IDs to retrieve information for.
 | 
				
			||||||
 | 
					   * @param page An optional page number to retrieve audio information from.
 | 
				
			||||||
   * @returns A promise that resolves to an array of AudioInfo objects.
 | 
					   * @returns A promise that resolves to an array of AudioInfo objects.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async get(songIds?: string[]): Promise<AudioInfo[]> {
 | 
					  public async get(
 | 
				
			||||||
 | 
					    songIds?: string[],
 | 
				
			||||||
 | 
					    page?: string | null
 | 
				
			||||||
 | 
					  ): Promise<AudioInfo[]> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    let url = `${SunoApi.BASE_URL}/api/feed/`;
 | 
					    let url = new URL(`${SunoApi.BASE_URL}/api/feed/`);
 | 
				
			||||||
    if (songIds) {
 | 
					    if (songIds) {
 | 
				
			||||||
      url = `${url}?ids=${songIds.join(',')}`;
 | 
					      url.searchParams.append('ids', songIds.join(','));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    logger.info("Get audio status: " + url);
 | 
					    if (page) {
 | 
				
			||||||
    const response = await this.client.get(url, {
 | 
					      url.searchParams.append('page', page);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    logger.info('Get audio status: ' + url.href);
 | 
				
			||||||
 | 
					    const response = await this.client.get(url.href, {
 | 
				
			||||||
      // 3 seconds timeout
 | 
					      // 3 seconds timeout
 | 
				
			||||||
      timeout: 3000
 | 
					      timeout: 3000
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const audios = response.data;
 | 
					    const audios = response.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return audios.map((audio: any) => ({
 | 
					    return audios.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,
 | 
				
			||||||
      lyric: audio.metadata.prompt ? this.parseLyrics(audio.metadata.prompt) : "",
 | 
					      lyric: audio.metadata.prompt
 | 
				
			||||||
 | 
					        ? this.parseLyrics(audio.metadata.prompt)
 | 
				
			||||||
 | 
					        : '',
 | 
				
			||||||
      audio_url: audio.audio_url,
 | 
					      audio_url: audio.audio_url,
 | 
				
			||||||
      video_url: audio.video_url,
 | 
					      video_url: audio.video_url,
 | 
				
			||||||
      created_at: audio.created_at,
 | 
					      created_at: audio.created_at,
 | 
				
			||||||
@ -394,7 +445,7 @@ public async custom_generate(
 | 
				
			|||||||
      type: audio.metadata.type,
 | 
					      type: audio.metadata.type,
 | 
				
			||||||
      tags: audio.metadata.tags,
 | 
					      tags: audio.metadata.tags,
 | 
				
			||||||
      duration: audio.metadata.duration,
 | 
					      duration: audio.metadata.duration,
 | 
				
			||||||
      error_message: audio.metadata.error_message,
 | 
					      error_message: audio.metadata.error_message
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -405,18 +456,22 @@ public async custom_generate(
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async getClip(clipId: string): Promise<object> {
 | 
					  public async getClip(clipId: string): Promise<object> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    const response = await this.client.get(`${SunoApi.BASE_URL}/api/clip/${clipId}`);
 | 
					    const response = await this.client.get(
 | 
				
			||||||
 | 
					      `${SunoApi.BASE_URL}/api/clip/${clipId}`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    return response.data;
 | 
					    return response.data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async get_credits(): Promise<object> {
 | 
					  public async get_credits(): Promise<object> {
 | 
				
			||||||
    await this.keepAlive(false);
 | 
					    await this.keepAlive(false);
 | 
				
			||||||
    const response = await this.client.get(`${SunoApi.BASE_URL}/api/billing/info/`);
 | 
					    const response = await this.client.get(
 | 
				
			||||||
 | 
					      `${SunoApi.BASE_URL}/api/billing/info/`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      credits_left: response.data.total_credits_left,
 | 
					      credits_left: response.data.total_credits_left,
 | 
				
			||||||
      period: response.data.period,
 | 
					      period: response.data.period,
 | 
				
			||||||
      monthly_limit: response.data.monthly_limit,
 | 
					      monthly_limit: response.data.monthly_limit,
 | 
				
			||||||
      monthly_usage: response.data.monthly_usage,
 | 
					      monthly_usage: response.data.monthly_usage
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -424,10 +479,10 @@ public async custom_generate(
 | 
				
			|||||||
const newSunoApi = async (cookie: string) => {
 | 
					const newSunoApi = async (cookie: string) => {
 | 
				
			||||||
  const sunoApi = new SunoApi(cookie);
 | 
					  const sunoApi = new SunoApi(cookie);
 | 
				
			||||||
  return await sunoApi.init();
 | 
					  return await sunoApi.init();
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (!process.env.SUNO_COOKIE) {
 | 
					if (!process.env.SUNO_COOKIE) {
 | 
				
			||||||
  console.log("Environment does not contain SUNO_COOKIE.", process.env)
 | 
					  console.log('Environment does not contain SUNO_COOKIE.', process.env);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sunoApi = newSunoApi(process.env.SUNO_COOKIE || '');
 | 
					export const sunoApi = newSunoApi(process.env.SUNO_COOKIE || '');
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user