n8n-nodes-eranol 0.2.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/credentials/EranolApi.credentials.d.ts +2 -3
  2. package/dist/credentials/EranolApi.credentials.js +7 -9
  3. package/dist/nodes/Eranol/Eranol.node.js +202 -294
  4. package/dist/nodes/Eranol/operations.d.ts +30 -0
  5. package/dist/nodes/Eranol/operations.js +543 -0
  6. package/package.json +1 -1
  7. package/dist/nodes/Eranol/resources/audio/denoise.d.ts +0 -2
  8. package/dist/nodes/Eranol/resources/audio/denoise.js +0 -83
  9. package/dist/nodes/Eranol/resources/audio/highlights.d.ts +0 -2
  10. package/dist/nodes/Eranol/resources/audio/highlights.js +0 -79
  11. package/dist/nodes/Eranol/resources/audio/index.d.ts +0 -2
  12. package/dist/nodes/Eranol/resources/audio/index.js +0 -43
  13. package/dist/nodes/Eranol/resources/audio/removeSilence.d.ts +0 -2
  14. package/dist/nodes/Eranol/resources/audio/removeSilence.js +0 -88
  15. package/dist/nodes/Eranol/resources/compose/composeVideo.d.ts +0 -2
  16. package/dist/nodes/Eranol/resources/compose/composeVideo.js +0 -110
  17. package/dist/nodes/Eranol/resources/compose/concat.d.ts +0 -2
  18. package/dist/nodes/Eranol/resources/compose/concat.js +0 -76
  19. package/dist/nodes/Eranol/resources/compose/index.d.ts +0 -2
  20. package/dist/nodes/Eranol/resources/compose/index.js +0 -43
  21. package/dist/nodes/Eranol/resources/compose/merge.d.ts +0 -2
  22. package/dist/nodes/Eranol/resources/compose/merge.js +0 -185
  23. package/dist/nodes/Eranol/resources/convert/audioToMp3.d.ts +0 -2
  24. package/dist/nodes/Eranol/resources/convert/audioToMp3.js +0 -48
  25. package/dist/nodes/Eranol/resources/convert/audioToWav.d.ts +0 -2
  26. package/dist/nodes/Eranol/resources/convert/audioToWav.js +0 -48
  27. package/dist/nodes/Eranol/resources/convert/imageToJpg.d.ts +0 -2
  28. package/dist/nodes/Eranol/resources/convert/imageToJpg.js +0 -48
  29. package/dist/nodes/Eranol/resources/convert/imageToWebp.d.ts +0 -2
  30. package/dist/nodes/Eranol/resources/convert/imageToWebp.js +0 -48
  31. package/dist/nodes/Eranol/resources/convert/index.d.ts +0 -2
  32. package/dist/nodes/Eranol/resources/convert/index.js +0 -67
  33. package/dist/nodes/Eranol/resources/convert/videoToMp4.d.ts +0 -2
  34. package/dist/nodes/Eranol/resources/convert/videoToMp4.js +0 -48
  35. package/dist/nodes/Eranol/resources/convert/videoToWebm.d.ts +0 -2
  36. package/dist/nodes/Eranol/resources/convert/videoToWebm.js +0 -48
  37. package/dist/nodes/Eranol/resources/image/generateImage.d.ts +0 -2
  38. package/dist/nodes/Eranol/resources/image/generateImage.js +0 -93
  39. package/dist/nodes/Eranol/resources/image/imageStatus.d.ts +0 -2
  40. package/dist/nodes/Eranol/resources/image/imageStatus.js +0 -20
  41. package/dist/nodes/Eranol/resources/image/index.d.ts +0 -2
  42. package/dist/nodes/Eranol/resources/image/index.js +0 -35
  43. package/dist/nodes/Eranol/resources/job/deleteJob.d.ts +0 -2
  44. package/dist/nodes/Eranol/resources/job/deleteJob.js +0 -20
  45. package/dist/nodes/Eranol/resources/job/getResult.d.ts +0 -2
  46. package/dist/nodes/Eranol/resources/job/getResult.js +0 -20
  47. package/dist/nodes/Eranol/resources/job/getStatus.d.ts +0 -2
  48. package/dist/nodes/Eranol/resources/job/getStatus.js +0 -20
  49. package/dist/nodes/Eranol/resources/job/index.d.ts +0 -2
  50. package/dist/nodes/Eranol/resources/job/index.js +0 -51
  51. package/dist/nodes/Eranol/resources/job/verify.d.ts +0 -2
  52. package/dist/nodes/Eranol/resources/job/verify.js +0 -4
  53. package/dist/nodes/Eranol/resources/notify/index.d.ts +0 -2
  54. package/dist/nodes/Eranol/resources/notify/index.js +0 -27
  55. package/dist/nodes/Eranol/resources/notify/sendEmail.d.ts +0 -2
  56. package/dist/nodes/Eranol/resources/notify/sendEmail.js +0 -82
  57. package/dist/nodes/Eranol/resources/video/addIntro.d.ts +0 -2
  58. package/dist/nodes/Eranol/resources/video/addIntro.js +0 -64
  59. package/dist/nodes/Eranol/resources/video/addOutro.d.ts +0 -2
  60. package/dist/nodes/Eranol/resources/video/addOutro.js +0 -64
  61. package/dist/nodes/Eranol/resources/video/caption.d.ts +0 -2
  62. package/dist/nodes/Eranol/resources/video/caption.js +0 -120
  63. package/dist/nodes/Eranol/resources/video/extractAudio.d.ts +0 -2
  64. package/dist/nodes/Eranol/resources/video/extractAudio.js +0 -72
  65. package/dist/nodes/Eranol/resources/video/extractImages.d.ts +0 -2
  66. package/dist/nodes/Eranol/resources/video/extractImages.js +0 -102
  67. package/dist/nodes/Eranol/resources/video/generateGif.d.ts +0 -2
  68. package/dist/nodes/Eranol/resources/video/generateGif.js +0 -109
  69. package/dist/nodes/Eranol/resources/video/index.d.ts +0 -2
  70. package/dist/nodes/Eranol/resources/video/index.js +0 -115
  71. package/dist/nodes/Eranol/resources/video/overlay.d.ts +0 -2
  72. package/dist/nodes/Eranol/resources/video/overlay.js +0 -149
  73. package/dist/nodes/Eranol/resources/video/progressBar.d.ts +0 -2
  74. package/dist/nodes/Eranol/resources/video/progressBar.js +0 -109
  75. package/dist/nodes/Eranol/resources/video/reframe.d.ts +0 -2
  76. package/dist/nodes/Eranol/resources/video/reframe.js +0 -102
  77. package/dist/nodes/Eranol/resources/video/thumbnail.d.ts +0 -2
  78. package/dist/nodes/Eranol/resources/video/thumbnail.js +0 -126
  79. package/dist/nodes/Eranol/resources/video/trim.d.ts +0 -2
  80. package/dist/nodes/Eranol/resources/video/trim.js +0 -79
  81. package/dist/nodes/Eranol/resources/video/watermark.d.ts +0 -2
  82. package/dist/nodes/Eranol/resources/video/watermark.js +0 -117
@@ -1,10 +1,9 @@
1
- import type { IAuthenticateGeneric, ICredentialType, INodeProperties, Icon, ICredentialTestRequest } from 'n8n-workflow';
1
+ import type { ICredentialType, INodeProperties, Icon, ICredentialTestRequest } from 'n8n-workflow';
2
2
  export declare class EranolApi implements ICredentialType {
3
3
  name: string;
4
4
  displayName: string;
5
5
  documentationUrl: string;
6
6
  icon: Icon;
7
- test: ICredentialTestRequest;
8
7
  properties: INodeProperties[];
9
- authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
10
9
  }
@@ -7,12 +7,6 @@ class EranolApi {
7
7
  this.displayName = 'Eranol API';
8
8
  this.documentationUrl = 'https://www.eranol.com/documentation';
9
9
  this.icon = 'file:../nodes/Eranol/eranol.svg';
10
- this.test = {
11
- request: {
12
- baseURL: 'https://eranol.com/api/v1',
13
- url: '/me',
14
- },
15
- };
16
10
  this.properties = [
17
11
  {
18
12
  displayName: 'API Key',
@@ -24,9 +18,13 @@ class EranolApi {
24
18
  description: 'Your Eranol API key',
25
19
  },
26
20
  ];
27
- this.authenticate = {
28
- type: 'generic',
29
- properties: {
21
+ // No generic `authenticate` block: the node attaches the x-api-key header
22
+ // itself in execute(). Omitting it also stops n8n from injecting a
23
+ // "Custom API Call" operation into the node's Operation dropdown.
24
+ this.test = {
25
+ request: {
26
+ baseURL: 'https://eranol.com/api/v1',
27
+ url: '/verify',
30
28
  headers: {
31
29
  'x-api-key': '={{$credentials.apiKey}}',
32
30
  },
@@ -2,240 +2,69 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Eranol = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
- const video_1 = require("./resources/video");
6
- const audio_1 = require("./resources/audio");
7
- const convert_1 = require("./resources/convert");
8
- const compose_1 = require("./resources/compose");
9
- const job_1 = require("./resources/job");
10
- const image_1 = require("./resources/image");
11
- const notify_1 = require("./resources/notify");
5
+ const operations_1 = require("./operations");
12
6
  const BASE_URL = 'https://eranol.com/api/v1';
13
- const OPERATION_MAP = {
14
- // video
15
- addIntro: { method: 'POST', url: '/ffmpeg/video/add-intro' },
16
- addOutro: { method: 'POST', url: '/ffmpeg/video/add-outro' },
17
- caption: { method: 'POST', url: '/ffmpeg/video/caption' },
18
- extractAudio: { method: 'POST', url: '/ffmpeg/video/extract/audio' },
19
- extractImages: { method: 'POST', url: '/ffmpeg/video/extract/images' },
20
- generateGif: { method: 'POST', url: '/ffmpeg/video/extract/gif' },
21
- overlay: { method: 'POST', url: '/ffmpeg/video/overlay' },
22
- progressBar: { method: 'POST', url: '/ffmpeg/video/progress-bar' },
23
- reframe: { method: 'POST', url: '/ffmpeg/video/reframe' },
24
- thumbnail: { method: 'POST', url: '/ffmpeg/video/thumbnail' },
25
- trim: { method: 'POST', url: '/ffmpeg/video/trim' },
26
- watermark: { method: 'POST', url: '/ffmpeg/video/watermark' },
27
- // audio
28
- denoise: { method: 'POST', url: '/ffmpeg/audio/denoise' },
29
- highlights: { method: 'POST', url: '/ffmpeg/audio/highlights' },
30
- removeSilence: { method: 'POST', url: '/ffmpeg/audio/remove-silence' },
31
- // convert
32
- audioToMp3: { method: 'POST', url: '/ffmpeg/convert/audio/to/mp3' },
33
- audioToWav: { method: 'POST', url: '/ffmpeg/convert/audio/to/wav' },
34
- imageToJpg: { method: 'POST', url: '/ffmpeg/convert/image/to/jpg' },
35
- imageToWebp: { method: 'POST', url: '/ffmpeg/convert/image/to/webp' },
36
- videoToMp4: { method: 'POST', url: '/ffmpeg/convert/video/to/mp4' },
37
- videoToWebm: { method: 'POST', url: '/ffmpeg/convert/video/to/webm' },
38
- // compose
39
- concat: { method: 'POST', url: '/ffmpeg/video/concat' },
40
- composeVideo: { method: 'POST', url: '/ffmpeg/video/compose' },
41
- merge: { method: 'POST', url: '/ffmpeg/merge' },
42
- // image
43
- generateImage: { method: 'POST', url: '/image' },
44
- // notify
45
- sendEmail: { method: 'POST', url: '/notifications/email' },
46
- };
47
- function buildBody(operation, p) {
48
- var _a, _b, _c, _d;
49
- const additional = p('additionalFields', {});
50
- switch (operation) {
51
- // ── video ──────────────────────────────────────────────────────────
52
- case 'trim':
53
- return { url: p('url'), start_sec: p('startSec'), end_sec: p('endSec') };
54
- case 'addIntro':
55
- return { url: p('url'), intro_url: p('introUrl') };
56
- case 'addOutro':
57
- return { url: p('url'), outro_url: p('outroUrl') };
58
- case 'caption': {
59
- const body = { url: p('url') };
60
- if (additional.fontColor !== undefined)
61
- body.font_color = additional.fontColor;
62
- if (additional.fontSize !== undefined)
63
- body.font_size = additional.fontSize;
64
- if (additional.maxSegmentDuration !== undefined)
65
- body.max_segment_duration = additional.maxSegmentDuration;
66
- if (additional.maxWordsPerLine !== undefined)
67
- body.max_words_per_line = additional.maxWordsPerLine;
68
- if (additional.outlineColor !== undefined)
69
- body.outline_color = additional.outlineColor;
70
- if (additional.outlineWidth !== undefined)
71
- body.outline_width = additional.outlineWidth;
72
- if (additional.position !== undefined)
73
- body.position = additional.position;
74
- return body;
75
- }
76
- case 'extractAudio': {
77
- const body = { url: p('url') };
78
- if (additional.mono !== undefined)
79
- body.mono = additional.mono;
80
- return body;
81
- }
82
- case 'extractImages': {
83
- const body = { url: p('url'), start_sec: p('startSec'), end_sec: p('endSec') };
84
- if (additional.fps !== undefined)
85
- body.fps = additional.fps;
86
- return body;
87
- }
88
- case 'generateGif': {
89
- const body = { url: p('url'), start_sec: p('startSec'), end_sec: p('endSec') };
90
- if (additional.fps !== undefined)
91
- body.fps = additional.fps;
92
- if (additional.width !== undefined)
93
- body.width = additional.width;
94
- return body;
95
- }
96
- case 'overlay': {
97
- const raw = p('overlays', { overlayValues: [] });
98
- return { url: p('url'), overlays: (_a = raw.overlayValues) !== null && _a !== void 0 ? _a : [] };
99
- }
100
- case 'progressBar': {
101
- const body = { url: p('url') };
102
- if (additional.color !== undefined)
103
- body.color = additional.color;
104
- if (additional.height !== undefined)
105
- body.height = additional.height;
106
- if (additional.opacity !== undefined)
107
- body.opacity = additional.opacity;
108
- if (additional.position !== undefined)
109
- body.position = additional.position;
110
- if (additional.style !== undefined)
111
- body.style = additional.style;
112
- return body;
113
- }
114
- case 'reframe': {
115
- const body = { url: p('url'), width: p('width'), height: p('height') };
116
- if (additional.bgColor !== undefined)
117
- body.bg_color = additional.bgColor;
118
- return body;
119
- }
120
- case 'thumbnail': {
121
- const body = { url: p('url') };
122
- if (additional.bgColor !== undefined)
123
- body.bg_color = additional.bgColor;
124
- if (additional.fontColor !== undefined)
125
- body.font_color = additional.fontColor;
126
- if (additional.fontSize !== undefined)
127
- body.font_size = additional.fontSize;
128
- if (additional.height !== undefined)
129
- body.height = additional.height;
130
- if (additional.position !== undefined)
131
- body.position = additional.position;
132
- if (additional.text !== undefined)
133
- body.text = additional.text;
134
- if (additional.timeSec !== undefined)
135
- body.time_sec = additional.timeSec;
136
- if (additional.width !== undefined)
137
- body.width = additional.width;
138
- return body;
139
- }
140
- case 'watermark': {
141
- const body = { url: p('url'), watermark_url: p('watermarkUrl') };
142
- if (additional.position !== undefined)
143
- body.position = additional.position;
144
- if (additional.scale !== undefined)
145
- body.scale = additional.scale;
146
- if (additional.opacity !== undefined)
147
- body.opacity = additional.opacity;
148
- if (additional.margin !== undefined)
149
- body.margin = additional.margin;
150
- return body;
151
- }
152
- // ── audio ──────────────────────────────────────────────────────────
153
- case 'denoise': {
154
- const body = { url: p('url') };
155
- if (additional.method !== undefined)
156
- body.method = additional.method;
157
- if (additional.noiseReduction !== undefined)
158
- body.noise_reduction = additional.noiseReduction;
159
- return body;
160
- }
161
- case 'highlights': {
162
- const body = { url: p('url') };
163
- if (additional.topN !== undefined)
164
- body.top_n = additional.topN;
165
- if (additional.clipDuration !== undefined)
166
- body.clip_duration = additional.clipDuration;
167
- return body;
168
- }
169
- case 'removeSilence': {
170
- const body = { url: p('url') };
171
- if (additional.silenceThreshDb !== undefined)
172
- body.silence_thresh_db = additional.silenceThreshDb;
173
- if (additional.minSilenceDuration !== undefined)
174
- body.min_silence_duration = additional.minSilenceDuration;
175
- if (additional.padding !== undefined)
176
- body.padding = additional.padding;
177
- return body;
178
- }
179
- // ── convert ────────────────────────────────────────────────────────
180
- case 'audioToMp3':
181
- case 'audioToWav':
182
- case 'imageToJpg':
183
- case 'imageToWebp':
184
- case 'videoToMp4':
185
- case 'videoToWebm':
186
- return { url: p('url') };
187
- // ── compose ────────────────────────────────────────────────────────
188
- case 'concat': {
189
- const raw = p('clips', { clipValues: [] });
190
- return { clips: (_b = raw.clipValues) !== null && _b !== void 0 ? _b : [] };
191
- }
192
- case 'composeVideo': {
193
- const raw = p('overlays', { overlayValues: [] });
194
- return { main_video_url: p('mainVideoUrl'), overlays: (_c = raw.overlayValues) !== null && _c !== void 0 ? _c : [] };
7
+ /**
8
+ * Coerce an HTTP response into a JSON-safe object for n8n output.
9
+ *
10
+ * `httpRequest` normally returns the parsed body, but if the response is a full
11
+ * wrapper (status/headers/body with circular socket refs) we extract `body`,
12
+ * and otherwise fall back to a plain string/value wrapper. This prevents
13
+ * "Converting circular structure to JSON" errors.
14
+ */
15
+ function toJsonSafe(data) {
16
+ if (data === null || data === undefined) {
17
+ return { success: true };
18
+ }
19
+ if (typeof data === 'object') {
20
+ const obj = data;
21
+ // Full-response wrapper: surface just the body.
22
+ if ('body' in obj && ('headers' in obj || 'statusCode' in obj)) {
23
+ return toJsonSafe(obj.body);
195
24
  }
196
- case 'merge': {
197
- const raw = p('images', { imageValues: [] });
198
- const body = { images: (_d = raw.imageValues) !== null && _d !== void 0 ? _d : [], audio_url: p('audioUrl') };
199
- if (additional.audioMode !== undefined)
200
- body.audio_mode = additional.audioMode;
201
- if (additional.bgAudioUrl !== undefined)
202
- body.bg_audio_url = additional.bgAudioUrl;
203
- if (additional.bgAudioVolume !== undefined)
204
- body.bg_audio_volume = additional.bgAudioVolume;
205
- if (additional.fadeSecs !== undefined)
206
- body.fade_secs = additional.fadeSecs;
207
- if (additional.fit !== undefined)
208
- body.fit = additional.fit;
209
- if (additional.fps !== undefined)
210
- body.fps = additional.fps;
211
- if (additional.height !== undefined)
212
- body.height = additional.height;
213
- if (additional.transition !== undefined)
214
- body.transition = additional.transition;
215
- if (additional.width !== undefined)
216
- body.width = additional.width;
217
- return body;
25
+ try {
26
+ // Round-trip to drop any non-serialisable (circular) properties.
27
+ return JSON.parse(JSON.stringify(obj));
218
28
  }
219
- // ── image ──────────────────────────────────────────────────────────
220
- case 'generateImage': {
221
- const body = { prompt: p('prompt') };
222
- if (additional.height !== undefined)
223
- body.height = additional.height;
224
- if (additional.negativePrompt !== undefined)
225
- body.negative_prompt = additional.negativePrompt;
226
- if (additional.seed !== undefined)
227
- body.seed = additional.seed;
228
- if (additional.width !== undefined)
229
- body.width = additional.width;
230
- return body;
29
+ catch {
30
+ return { result: String(data) };
231
31
  }
232
- // ── notify ─────────────────────────────────────────────────────────
233
- case 'sendEmail':
234
- return { to: p('to'), subject: p('subject'), message: p('message') };
235
- default:
236
- return {};
237
32
  }
33
+ return { result: data };
238
34
  }
35
+ /**
36
+ * Universal body UI. For each operation, shown only when that operation is
37
+ * selected: a slim notice linking to the operation's documentation page (n8n
38
+ * auto-links the bare URL), then an empty "Body (JSON)" editor. The docs page
39
+ * lists the exact payload fields and examples to paste in.
40
+ */
41
+ const universalBodyFields = operations_1.OPERATIONS.flatMap((op) => [
42
+ {
43
+ displayName: `See the <a href="${op.docs}" target="_blank">${op.name} payload reference</a>.`,
44
+ name: `docs_${op.value}`,
45
+ type: 'notice',
46
+ default: '',
47
+ displayOptions: {
48
+ show: {
49
+ resource: ['universal'],
50
+ eranolAction: [op.value],
51
+ },
52
+ },
53
+ },
54
+ {
55
+ displayName: 'Body (JSON)',
56
+ name: `body_${op.value}`,
57
+ type: 'json',
58
+ default: '{}',
59
+ description: 'Request body sent to the Eranol API. See the payload reference link above.',
60
+ displayOptions: {
61
+ show: {
62
+ resource: ['universal'],
63
+ eranolAction: [op.value],
64
+ },
65
+ },
66
+ },
67
+ ]);
239
68
  class Eranol {
240
69
  constructor() {
241
70
  this.description = {
@@ -244,8 +73,8 @@ class Eranol {
244
73
  icon: 'file:eranol.svg',
245
74
  group: ['transform'],
246
75
  version: 1,
247
- subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
248
- description: 'Interact with the Eranol FFmpeg media processing API',
76
+ subtitle: '={{ $parameter["resource"] === "job" ? $parameter["operation"] : $parameter["eranolAction"] }}',
77
+ description: 'Interact with the Eranol media processing API',
249
78
  defaults: {
250
79
  name: 'Eranol',
251
80
  },
@@ -265,125 +94,204 @@ class Eranol {
265
94
  noDataExpression: true,
266
95
  options: [
267
96
  {
268
- name: 'Audio',
269
- value: 'audio',
97
+ name: 'Universal',
98
+ value: 'universal',
99
+ description: 'Run any Eranol API operation with a JSON body',
270
100
  },
271
101
  {
272
- name: 'Compose',
273
- value: 'compose',
102
+ name: 'Job',
103
+ value: 'job',
104
+ description: 'Check job status, fetch results, or verify your API key',
274
105
  },
106
+ ],
107
+ default: 'universal',
108
+ },
109
+ // ── Universal ──────────────────────────────────────────────────────
110
+ // A single-option "operation" so n8n renders exactly ONE "Universal"
111
+ // tile in the nodes panel (n8n generates one tile per option of the
112
+ // parameter literally named "operation"). It is hidden inside the node
113
+ // so users only see the real "Operation" picker (eranolAction) below.
114
+ {
115
+ displayName: 'Operation',
116
+ name: 'operation',
117
+ type: 'hidden',
118
+ noDataExpression: true,
119
+ options: [
275
120
  {
276
- name: 'Convert',
277
- value: 'convert',
121
+ name: 'Universal',
122
+ value: 'universal',
123
+ action: 'Universal',
124
+ description: 'Run any Eranol API operation with a JSON body',
278
125
  },
279
- {
280
- name: 'Image',
281
- value: 'image',
126
+ ],
127
+ default: 'universal',
128
+ displayOptions: {
129
+ show: {
130
+ resource: ['universal'],
282
131
  },
132
+ },
133
+ },
134
+ // The actual operation picker shown inside the Universal node.
135
+ {
136
+ displayName: 'Operation',
137
+ name: 'eranolAction',
138
+ type: 'options',
139
+ noDataExpression: true,
140
+ displayOptions: {
141
+ show: {
142
+ resource: ['universal'],
143
+ },
144
+ },
145
+ options: operations_1.OPERATION_OPTIONS,
146
+ default: 'trim',
147
+ },
148
+ ...universalBodyFields,
149
+ // ── Job ────────────────────────────────────────────────────────────
150
+ {
151
+ displayName: 'Operation',
152
+ name: 'operation',
153
+ type: 'options',
154
+ noDataExpression: true,
155
+ displayOptions: {
156
+ show: {
157
+ resource: ['job'],
158
+ },
159
+ },
160
+ options: [
283
161
  {
284
- name: 'Job',
285
- value: 'job',
162
+ name: 'Get Status',
163
+ value: 'getStatus',
164
+ action: 'Get job status',
165
+ description: 'Retrieve current job status, progress, and completion data',
286
166
  },
287
167
  {
288
- name: 'Notify',
289
- value: 'notify',
168
+ name: 'Get Result',
169
+ value: 'getResult',
170
+ action: 'Get job result',
171
+ description: 'Get the result of a completed job',
290
172
  },
291
173
  {
292
- name: 'Video',
293
- value: 'video',
174
+ name: 'Delete',
175
+ value: 'deleteJob',
176
+ action: 'Delete a job',
177
+ description: 'Remove a job and its associated output file',
294
178
  },
295
179
  ],
296
- default: 'video',
180
+ default: 'getStatus',
181
+ },
182
+ {
183
+ displayName: 'Job ID',
184
+ name: 'jobId',
185
+ type: 'string',
186
+ default: '',
187
+ required: true,
188
+ description: 'The ID of the job to act on',
189
+ displayOptions: {
190
+ show: {
191
+ resource: ['job'],
192
+ },
193
+ },
297
194
  },
298
- ...video_1.videoDescription,
299
- ...audio_1.audioDescription,
300
- ...convert_1.convertDescription,
301
- ...compose_1.composeDescription,
302
- ...image_1.imageDescription,
303
- ...notify_1.notifyDescription,
304
- ...job_1.jobDescription,
305
195
  ],
306
196
  usableAsTool: true,
307
197
  };
308
198
  }
309
199
  async execute() {
310
- const request = this.helpers.httpRequestWithAuthentication.bind(this);
200
+ const credentials = await this.getCredentials('eranolApi');
201
+ const apiKey = credentials.apiKey;
202
+ const authHeaders = { 'x-api-key': apiKey };
203
+ const httpRequest = this.helpers.httpRequest.bind(this);
204
+ const node = this.getNode();
205
+ // Wrap httpRequest so a failed request surfaces the API's status and body
206
+ // as a clean message instead of a circular response object.
207
+ const request = async (options) => {
208
+ var _a, _b, _c, _d, _e, _f;
209
+ try {
210
+ return await httpRequest(options);
211
+ }
212
+ catch (error) {
213
+ const err = error;
214
+ const status = (_a = err.statusCode) !== null && _a !== void 0 ? _a : (_b = err.response) === null || _b === void 0 ? void 0 : _b.statusCode;
215
+ const bodyText = typeof ((_c = err.response) === null || _c === void 0 ? void 0 : _c.body) === 'string'
216
+ ? err.response.body
217
+ : JSON.stringify((_e = (_d = err.response) === null || _d === void 0 ? void 0 : _d.body) !== null && _e !== void 0 ? _e : '');
218
+ const detail = bodyText && bodyText !== '""' ? ` — ${bodyText}` : '';
219
+ throw new n8n_workflow_1.NodeOperationError(node, `Eranol API request failed${status ? ` (HTTP ${status})` : ''}${detail || `: ${(_f = err.message) !== null && _f !== void 0 ? _f : 'Unknown error'}`}`);
220
+ }
221
+ };
311
222
  const items = this.getInputData();
312
223
  const returnData = [];
313
224
  for (let i = 0; i < items.length; i++) {
314
225
  const resource = this.getNodeParameter('resource', i);
315
- const operation = this.getNodeParameter('operation', i);
316
- const useJsonBody = this.getNodeParameter('useJsonBody', i, false);
317
- const p = (name, fallback) => {
318
- try {
319
- return this.getNodeParameter(name, i);
320
- }
321
- catch {
322
- return fallback;
323
- }
324
- };
325
226
  let responseData;
326
- // ── GET/DELETE operations (no body) ────────────────────────────
327
227
  if (resource === 'job') {
328
- const jobId = operation !== 'verify' ? p('jobId') : '';
228
+ const operation = this.getNodeParameter('operation', i);
229
+ const jobId = this.getNodeParameter('jobId', i);
329
230
  if (operation === 'getStatus') {
330
- responseData = await request('eranolApi', {
231
+ responseData = await request({
331
232
  method: 'GET',
332
233
  url: `${BASE_URL}/ffmpeg/status/${jobId}`,
333
- headers: { Accept: 'application/json' },
234
+ headers: { Accept: 'application/json', ...authHeaders },
235
+ json: true,
334
236
  });
335
237
  }
336
238
  else if (operation === 'getResult') {
337
- responseData = await request('eranolApi', {
239
+ responseData = await request({
338
240
  method: 'GET',
339
241
  url: `${BASE_URL}/ffmpeg/result/${jobId}`,
340
- headers: { Accept: 'application/json' },
242
+ headers: { Accept: 'application/json', ...authHeaders },
243
+ json: true,
341
244
  });
342
245
  }
343
246
  else if (operation === 'deleteJob') {
344
- responseData = await request('eranolApi', {
247
+ responseData = await request({
345
248
  method: 'DELETE',
346
249
  url: `${BASE_URL}/ffmpeg/jobs/${jobId}`,
347
- headers: { Accept: 'application/json' },
250
+ headers: { Accept: 'application/json', ...authHeaders },
251
+ json: true,
348
252
  });
349
253
  }
350
- else if (operation === 'verify') {
351
- responseData = await request('eranolApi', {
352
- method: 'GET',
353
- url: `${BASE_URL}/verify`,
354
- headers: { Accept: 'application/json' },
355
- });
254
+ else {
255
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown job operation: ${operation}`);
356
256
  }
357
257
  }
358
- else if (resource === 'image' && operation === 'imageStatus') {
359
- const jobId = p('jobId');
360
- responseData = await request('eranolApi', {
361
- method: 'GET',
362
- url: `${BASE_URL}/image/status/${jobId}`,
363
- headers: { Accept: 'application/json' },
364
- });
365
- }
366
258
  else {
367
- // ── POST operations ────────────────────────────────────────
368
- const route = OPERATION_MAP[operation];
259
+ // ── Universal: POST with a user-supplied JSON body ───────────────
260
+ const eranolAction = this.getNodeParameter('eranolAction', i);
261
+ const route = operations_1.OPERATION_BY_VALUE[eranolAction];
369
262
  if (!route) {
370
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
263
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${eranolAction}`);
264
+ }
265
+ const rawBody = this.getNodeParameter(`body_${eranolAction}`, i, '{}');
266
+ let body;
267
+ if (typeof rawBody === 'string') {
268
+ try {
269
+ body = JSON.parse(rawBody || '{}');
270
+ }
271
+ catch (error) {
272
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Body is not valid JSON: ${error.message}`, { itemIndex: i });
273
+ }
274
+ }
275
+ else {
276
+ body = rawBody;
371
277
  }
372
- const body = useJsonBody
373
- ? JSON.parse(p('jsonBody', '{}'))
374
- : buildBody(operation, p);
375
- responseData = await request('eranolApi', {
278
+ responseData = await request({
376
279
  method: route.method,
377
280
  url: `${BASE_URL}${route.url}`,
378
281
  headers: {
379
282
  Accept: 'application/json',
380
283
  'Content-Type': 'application/json',
284
+ ...authHeaders,
381
285
  },
382
286
  body,
383
287
  json: true,
384
288
  });
385
289
  }
386
- const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
290
+ // Normalise to a JSON-safe value. httpRequest can hand back a full
291
+ // response wrapper (with circular socket refs) when the body is not
292
+ // JSON; strip it down to the parsed body or a plain wrapper.
293
+ const safeData = toJsonSafe(responseData);
294
+ const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(safeData), { itemData: { item: i } });
387
295
  returnData.push(...executionData);
388
296
  }
389
297
  return [returnData];
@@ -0,0 +1,30 @@
1
+ import type { INodePropertyOptions } from 'n8n-workflow';
2
+ /**
3
+ * Authoritative table of every Eranol POST operation exposed by the Universal
4
+ * resource. Each entry carries the exact display name used in the Eranol
5
+ * documentation, the HTTP route, a link to its documentation page, and a set of
6
+ * copy-paste-ready payload examples taken verbatim from the Eranol docs.
7
+ *
8
+ * Source: https://www.eranol.com/documentation (per-endpoint pages).
9
+ */
10
+ export interface EranolExample {
11
+ /** Short label shown in the Example dropdown, e.g. "Basic" or "Styled". */
12
+ label: string;
13
+ /** Copy-paste-ready request body for this use case. */
14
+ body: Record<string, unknown>;
15
+ }
16
+ export interface EranolOperation {
17
+ name: string;
18
+ value: string;
19
+ description: string;
20
+ method: 'POST';
21
+ url: string;
22
+ /** Full documentation page for this operation. */
23
+ docs: string;
24
+ examples: EranolExample[];
25
+ }
26
+ export declare const OPERATIONS: EranolOperation[];
27
+ /** Map keyed by operation value for fast route lookup at execution time. */
28
+ export declare const OPERATION_BY_VALUE: Record<string, EranolOperation>;
29
+ /** Dropdown options for the Universal Operation field, sorted alphabetically. */
30
+ export declare const OPERATION_OPTIONS: INodePropertyOptions[];