mirror of
https://github.com/n8n-io/n8n-nodes-starter.git
synced 2025-10-29 06:22:24 -05:00
169 lines
7.9 KiB
TypeScript
169 lines
7.9 KiB
TypeScript
import type {
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
ITriggerFunctions,
|
|
ITriggerResponse,
|
|
ICredentialDataDecryptedObject, // Added
|
|
} from 'n8n-workflow';
|
|
import { NodeOperationError } from 'n8n-workflow'; // Added
|
|
|
|
import {
|
|
pollJobStatus,
|
|
listPreviousSongs,
|
|
loginWithCredentials,
|
|
} from '../../utils/sunoApi';
|
|
|
|
export class SunoTrigger implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Suno Trigger',
|
|
name: 'sunoTrigger',
|
|
icon: 'file:suno.svg',
|
|
group: ['trigger', 'ai'],
|
|
version: 1,
|
|
description: 'Triggers when a Suno AI event occurs (polling)',
|
|
defaults: {
|
|
name: 'Suno Trigger',
|
|
},
|
|
inputs: [],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'sunoApi',
|
|
required: true,
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Trigger Event',
|
|
name: 'triggerEvent',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Track Generation Complete',
|
|
value: 'trackGenerationComplete',
|
|
description: 'Triggers when a specific track finishes generation',
|
|
},
|
|
{
|
|
name: 'New Song Available',
|
|
value: 'newSongAvailable',
|
|
description: 'Triggers when any new song is available in the library (polling)',
|
|
},
|
|
],
|
|
default: 'trackGenerationComplete',
|
|
description: 'The Suno event that will trigger this node',
|
|
},
|
|
{
|
|
displayName: 'Track ID (for Track Generation Complete)',
|
|
name: 'trackId',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'The ID of the track to monitor for completion',
|
|
displayOptions: {
|
|
show: {
|
|
triggerEvent: ['trackGenerationComplete'],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
displayName: 'Polling Interval (minutes)',
|
|
name: 'pollingInterval',
|
|
type: 'number',
|
|
default: 5, // Default to 5 minutes
|
|
description: 'How often to check for events. n8n will manage the actual polling schedule based on this.',
|
|
// displayOptions: { // Not strictly needed to show for both, but can be kept
|
|
// show: {
|
|
// triggerEvent: ['newSongAvailable', 'trackGenerationComplete'],
|
|
// },
|
|
// },
|
|
},
|
|
],
|
|
};
|
|
|
|
/**
|
|
* The `trigger` method is called when the workflow is activated.
|
|
* For polling triggers using `manualTriggerFunction`, this method can be used
|
|
* for initial setup, like setting the polling interval or initial authentication.
|
|
*/
|
|
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse | undefined> {
|
|
const pollingIntervalMinutes = this.getNodeParameter('pollingInterval', 5) as number;
|
|
this.setPollingInterval(pollingIntervalMinutes * 60 * 1000); // Set n8n's polling interval
|
|
|
|
try {
|
|
const credentials = await this.getCredentials('sunoApi') as ICredentialDataDecryptedObject;
|
|
if (!credentials || !credentials.email || !credentials.password) {
|
|
throw new NodeOperationError(this.getNode(), 'Suno API credentials are not configured or incomplete.');
|
|
}
|
|
// Perform an initial login to ensure credentials are valid and API is reachable
|
|
await loginWithCredentials(credentials.email as string, credentials.password as string);
|
|
console.log('SunoTrigger: Initial authentication successful for polling setup.');
|
|
} catch (error) {
|
|
console.error('SunoTrigger: Initial authentication or setup failed.', error);
|
|
// Depending on how n8n handles this, we might throw or just log.
|
|
// For a trigger, throwing here might prevent it from starting.
|
|
if (error instanceof NodeOperationError) throw error;
|
|
throw new NodeOperationError(this.getNode(), `Initial setup failed: ${error.message || String(error)}`);
|
|
}
|
|
|
|
// For pure polling, manualTriggerFunction will handle the periodic checks.
|
|
// If specific cleanup is needed when the workflow deactivates, return a closeFunction.
|
|
return {
|
|
closeFunction: async () => {
|
|
console.log('SunoTrigger: Polling stopped.');
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* `manualTriggerFunction` is called by n8n at the defined polling interval.
|
|
* It should fetch data and emit items if new events are found.
|
|
*/
|
|
public async manualTrigger(this: ITriggerFunctions): Promise<void> {
|
|
const triggerEvent = this.getNodeParameter('triggerEvent') as string;
|
|
|
|
try {
|
|
const credentials = await this.getCredentials('sunoApi') as ICredentialDataDecryptedObject;
|
|
if (!credentials || !credentials.email || !credentials.password) {
|
|
console.error('SunoTrigger: Credentials not available for polling.');
|
|
// Optionally emit an error to the workflow execution log, but be careful not to flood it.
|
|
// this.emit([this.helpers.returnJsonArray([{ error: 'Credentials missing for polling' }])]);
|
|
return; // Stop further execution for this poll if creds are missing
|
|
}
|
|
|
|
// Ensure we are "logged in" for each poll execution, as the token state is managed in sunoApi.ts
|
|
// and might "expire" or the trigger instance could be new.
|
|
await loginWithCredentials(credentials.email as string, credentials.password as string);
|
|
|
|
if (triggerEvent === 'trackGenerationComplete') {
|
|
const trackId = this.getNodeParameter('trackId', '') as string;
|
|
if (!trackId) {
|
|
console.warn('SunoTrigger: Track ID not provided for "Track Generation Complete" event. Skipping poll.');
|
|
return;
|
|
}
|
|
const jobStatus = await pollJobStatus(trackId);
|
|
// Simple mock: emit if the job is 'complete' and has a trackId.
|
|
// A real implementation would need state to avoid re-emitting for the same completed track.
|
|
if (jobStatus && jobStatus.status === 'complete' && jobStatus.trackId) {
|
|
console.log(`SunoTrigger: Track ${jobStatus.trackId} (Job ID: ${jobStatus.id}) is complete. Emitting.`);
|
|
this.emit([this.helpers.returnJsonArray([jobStatus])]);
|
|
} else {
|
|
console.log(`SunoTrigger: Track ID ${trackId} status: ${jobStatus.status || 'unknown'}. Not emitting.`);
|
|
}
|
|
} else if (triggerEvent === 'newSongAvailable') {
|
|
const songs = await listPreviousSongs();
|
|
// Simple mock: if songs are found, emit the first one.
|
|
// A real implementation needs sophisticated state management to detect "new" songs
|
|
// (e.g., comparing against IDs seen in the previous poll).
|
|
if (songs && songs.length > 0) {
|
|
console.log('SunoTrigger: New songs found (mock implementation). Emitting the first song from the list.');
|
|
this.emit([this.helpers.returnJsonArray([songs[0]])]);
|
|
} else {
|
|
console.log('SunoTrigger: No new songs found (mock implementation).');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('SunoTrigger: Error during polling execution:', error);
|
|
// Optionally emit an error item to the workflow execution log
|
|
// this.emit([this.helpers.returnJsonArray([{ error: `Polling error: ${error.message || String(error)}` }])]);
|
|
}
|
|
}
|
|
}
|