n8n-nodes-userapi 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # n8n-nodes-userapi
2
+
3
+ Community node package for using UserApi.Ai: API layer to image generation tool endpoints in n8n workflows.
4
+
5
+ > Official package by UserApi.Ai.
6
+
7
+ ## Overview
8
+
9
+ Official resources:
10
+
11
+ - [Website](https://userapi.ai)
12
+ - [First steps: get your API key](https://userapi.ai/first-steps)
13
+
14
+ ## Features
15
+
16
+ - Supports all currently available UserApi.Ai methods in this node package.
17
+ - ✨ [Imagine](https://userapi.ai/doc#imagine)
18
+ - ⬆️ [Upscale](https://userapi.ai/doc#upscale)
19
+ - 🔄 [Variation](https://userapi.ai/doc#variation)
20
+ - 🔀 [Blend](https://userapi.ai/doc#blend)
21
+ - 📝 [Describe](https://userapi.ai/doc#describe)
22
+ - 🎬 [Animate](https://userapi.ai/doc#animate)
23
+ - 🖌️ [Inpaint](https://userapi.ai/doc#inpaint)
24
+ - And more: [See full docs](https://userapi.ai/doc)
25
+
26
+ ## Installation
27
+
28
+ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
29
+
30
+ ## Quick Install
31
+
32
+ ```text
33
+ # In n8n
34
+ # Go to Settings > Community Nodes
35
+ # Install: n8n-nodes-userapi
36
+ ```
37
+
38
+ ## Credentials
39
+
40
+ Follow the [First steps guide to get your API key](https://userapi.ai/first-steps).
41
+
42
+ Create `UserAPI` credentials in n8n and set:
43
+
44
+ - `API Key`: your UserAPI key
45
+
46
+ The node sends required headers automatically:
47
+
48
+ - `api-key: <your_api_key>`
49
+ - `Content-Type: application/json` (for JSON requests)
50
+
51
+ ## Example workflow
52
+
53
+ Basic generation flow:
54
+
55
+ 1. `UserAPI` -> `Imagine` (provide prompt)
56
+ 2. `UserAPI` -> `Status` (poll using returned hash)
57
+ or set `webhook_url` in `Imagine` and receive callbacks in an n8n `Webhook` node (or your own webhook endpoint)
58
+
59
+ ## Compatibility
60
+
61
+ - n8n version: `1.80.0` and above
62
+ - Node.js: `18.10` or higher
63
+
64
+ ## Support
65
+
66
+ - Issues and bug reports: GitHub Issues in this repository
67
+ - Telegram support: `https://t.me/userapiai`
68
+ - API onboarding and key setup: `https://userapi.ai/first-steps`
69
+ - UserAPI website: `https://userapi.ai`
70
+
71
+ ## Contributing
72
+
73
+ Contributions are welcome. Please open an issue first for major changes and include clear reproduction steps for bug reports.
74
+
75
+ ## License
76
+
77
+ MIT
78
+
79
+ ## Disclaimer
80
+
81
+ This is an official UserApi.Ai package for integrating UserAPI endpoints in n8n workflows.
82
+
@@ -0,0 +1,24 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class UserApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ authenticate: {
7
+ type: "generic";
8
+ properties: {
9
+ baseURL: string;
10
+ headers: {
11
+ 'api-key': string;
12
+ 'Content-Type': string;
13
+ };
14
+ };
15
+ };
16
+ properties: INodeProperties[];
17
+ test: {
18
+ request: {
19
+ baseURL: string;
20
+ url: string;
21
+ method: "GET";
22
+ };
23
+ };
24
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserApi = void 0;
4
+ class UserApi {
5
+ constructor() {
6
+ this.name = 'userApi';
7
+ this.displayName = 'UserAPI';
8
+ this.documentationUrl = 'https://ru.userapi.ai/first-steps';
9
+ this.authenticate = {
10
+ type: 'generic',
11
+ properties: {
12
+ baseURL: 'https://api.userapi.ai',
13
+ headers: {
14
+ 'api-key': '={{$credentials.apiKey}}',
15
+ 'Content-Type': 'application/json',
16
+ },
17
+ },
18
+ };
19
+ this.properties = [
20
+ {
21
+ displayName: 'API Key',
22
+ name: 'apiKey',
23
+ type: 'string',
24
+ typeOptions: {
25
+ password: true,
26
+ },
27
+ default: '',
28
+ required: true,
29
+ description: 'Get API key at https://userapi.ai. Sent in header: api-key',
30
+ },
31
+ ];
32
+ this.test = {
33
+ request: {
34
+ baseURL: 'https://api.userapi.ai',
35
+ url: '/user/check-connection',
36
+ method: 'GET',
37
+ },
38
+ };
39
+ }
40
+ }
41
+ exports.UserApi = UserApi;
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class UserApi implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,503 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserApi = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const webhookTypeOptions = [
6
+ { name: 'Progress', value: 'progress' },
7
+ { name: 'Result', value: 'result' },
8
+ ];
9
+ const operationOptions = [
10
+ { name: 'Imagine', value: 'imagine', description: 'POST /midjourney/v2/imagine' },
11
+ { name: 'Imagine Instant', value: 'imagineInstant', description: 'POST /midjourney/v2/imagine-instant' },
12
+ { name: 'Describe', value: 'describe', description: 'POST /midjourney/v2/describe' },
13
+ { name: 'Info', value: 'info', description: 'POST /midjourney/v2/info' },
14
+ { name: 'Upscale', value: 'upscale', description: 'POST /midjourney/v2/upscale' },
15
+ { name: 'Pan', value: 'pan', description: 'POST /midjourney/v2/pan' },
16
+ { name: 'Variation', value: 'variation', description: 'POST /midjourney/v2/variation' },
17
+ { name: 'Vary Subtle', value: 'varySubtle', description: 'POST /midjourney/v2/vary-subtle' },
18
+ { name: 'Vary Strong', value: 'varyStrong', description: 'POST /midjourney/v2/vary-strong' },
19
+ { name: 'Reroll', value: 'reroll', description: 'POST /midjourney/v2/reroll' },
20
+ { name: 'Upsample', value: 'upsample', description: 'POST /midjourney/v2/upsample' },
21
+ { name: 'Animate', value: 'animate', description: 'POST /midjourney/v2/animate' },
22
+ { name: 'Inpaint', value: 'inpaint', description: 'POST /midjourney/v2/inpaint' },
23
+ { name: 'Zoom', value: 'zoom', description: 'POST /midjourney/v2/zoom' },
24
+ { name: 'Blend', value: 'blend', description: 'POST /midjourney/v2/blend' },
25
+ { name: 'Toggle Remix Mode', value: 'prefer', description: 'POST /midjourney/v2/prefer' },
26
+ { name: 'Speed', value: 'speed', description: 'POST /midjourney/v2/speed' },
27
+ { name: 'Upload', value: 'upload', description: 'POST /midjourney/v2/upload' },
28
+ { name: 'Seed', value: 'seed', description: 'POST /midjourney/v2/seed' },
29
+ { name: 'Status', value: 'status', description: 'GET /midjourney/v2/status?hash=...' },
30
+ ];
31
+ function withDisplay(operation, properties) {
32
+ return properties.map((p) => {
33
+ var _a, _b;
34
+ return ({
35
+ ...p,
36
+ displayOptions: {
37
+ show: {
38
+ ...((_b = (_a = p.displayOptions) === null || _a === void 0 ? void 0 : _a.show) !== null && _b !== void 0 ? _b : {}),
39
+ operation: [operation],
40
+ },
41
+ },
42
+ });
43
+ });
44
+ }
45
+ const commonWebhookFields = [
46
+ { displayName: 'Webhook URL', name: 'webhook_url', type: 'string', default: '', description: 'Optional callback URL' },
47
+ {
48
+ displayName: 'Webhook Type',
49
+ name: 'webhook_type',
50
+ type: 'options',
51
+ default: 'result',
52
+ description: 'Allowed values: progress, result',
53
+ options: webhookTypeOptions,
54
+ },
55
+ ];
56
+ const uuidHashField = {
57
+ displayName: 'Hash',
58
+ name: 'hash',
59
+ type: 'string',
60
+ required: true,
61
+ default: '',
62
+ description: 'Required UUID hash',
63
+ };
64
+ const optionalAccountHashDescription = 'Optional Discord account hash. If omitted, available accounts are rotated to distribute load.';
65
+ const requiredAccountHashDescription = 'Required Discord account hash from your UserAPI dashboard.';
66
+ class UserApi {
67
+ constructor() {
68
+ this.description = {
69
+ displayName: 'UserAPI',
70
+ name: 'userApi',
71
+ icon: 'file:userapi.svg',
72
+ group: ['transform'],
73
+ version: 1,
74
+ subtitle: '={{$parameter["operation"]}}',
75
+ description: 'UserApi.Ai - API layer to Midjourney (unofficial)',
76
+ defaults: { name: 'UserAPI' },
77
+ inputs: ['main'],
78
+ outputs: ['main'],
79
+ credentials: [{ name: 'userApi', required: true }],
80
+ properties: [
81
+ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, default: 'imagine', options: operationOptions },
82
+ ...withDisplay('imagine', [
83
+ { displayName: 'Prompt', name: 'prompt', type: 'string', required: true, default: '', description: 'Required prompt text' },
84
+ ...commonWebhookFields,
85
+ { displayName: 'Callback ID', name: 'callback_id', type: 'string', default: '', description: 'Optional callback identifier (max 72 chars)' },
86
+ {
87
+ displayName: 'Account Hash',
88
+ name: 'account_hash',
89
+ type: 'string',
90
+ default: '',
91
+ description: optionalAccountHashDescription,
92
+ },
93
+ { displayName: 'Disable Prefilter', name: 'is_disable_prefilter', type: 'boolean', default: false },
94
+ ]),
95
+ ...withDisplay('imagineInstant', [
96
+ { displayName: 'Prompt', name: 'prompt', type: 'string', required: true, default: '', description: 'Required prompt text' },
97
+ {
98
+ displayName: 'Choice', name: 'choice', type: 'options', required: true, default: 'nothing',
99
+ options: [{ name: 'Nothing', value: 'nothing' }, { name: 'Random', value: 'random' }, { name: 'All (Seed)', value: 'all' }, { name: '1', value: '1' }, { name: '2', value: '2' }, { name: '3', value: '3' }, { name: '4', value: '4' }],
100
+ },
101
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', default: '', description: optionalAccountHashDescription },
102
+ ]),
103
+ ...withDisplay('describe', [
104
+ { displayName: 'URL', name: 'url', type: 'string', required: true, default: '', description: 'Required image URL (http/https)' },
105
+ ...commonWebhookFields,
106
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', default: '', description: optionalAccountHashDescription },
107
+ ]),
108
+ ...withDisplay('info', [
109
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', required: true, default: '', description: requiredAccountHashDescription },
110
+ { displayName: 'Webhook URL', name: 'webhook_url', type: 'string', default: '' },
111
+ { displayName: 'Callback ID', name: 'callback_id', type: 'string', default: '', description: 'Optional callback identifier (max 72 chars)' },
112
+ ]),
113
+ ...withDisplay('upscale', [
114
+ uuidHashField,
115
+ {
116
+ displayName: 'Choice',
117
+ name: 'choice',
118
+ type: 'options',
119
+ required: true,
120
+ default: 1,
121
+ options: [{ name: '1', value: 1 }, { name: '2', value: 2 }, { name: '3', value: 3 }, { name: '4', value: 4 }],
122
+ },
123
+ ...commonWebhookFields,
124
+ ]),
125
+ ...withDisplay('pan', [
126
+ uuidHashField,
127
+ { displayName: 'Direction', name: 'choice', type: 'options', required: true, default: 'right', options: [{ name: 'Right', value: 'right' }, { name: 'Left', value: 'left' }, { name: 'Up', value: 'up' }, { name: 'Down', value: 'down' }] },
128
+ { displayName: 'Prompt', name: 'prompt', type: 'string', default: '', description: 'Optional. Used in Remix mode.' },
129
+ ...commonWebhookFields,
130
+ ]),
131
+ ...withDisplay('variation', [
132
+ uuidHashField,
133
+ {
134
+ displayName: 'Choice',
135
+ name: 'choice',
136
+ type: 'options',
137
+ required: true,
138
+ default: 1,
139
+ options: [{ name: '1', value: 1 }, { name: '2', value: 2 }, { name: '3', value: 3 }, { name: '4', value: 4 }],
140
+ },
141
+ { displayName: 'Prompt', name: 'prompt', type: 'string', default: '', description: 'Optional. Used in Remix mode.' },
142
+ ...commonWebhookFields,
143
+ ]),
144
+ ...withDisplay('varySubtle', [uuidHashField, { displayName: 'Prompt', name: 'prompt', type: 'string', default: '', description: 'Optional. Used in Remix mode.' }, ...commonWebhookFields]),
145
+ ...withDisplay('varyStrong', [uuidHashField, { displayName: 'Prompt', name: 'prompt', type: 'string', default: '', description: 'Optional. Used in Remix mode.' }, ...commonWebhookFields]),
146
+ ...withDisplay('reroll', [uuidHashField, { displayName: 'Prompt', name: 'prompt', type: 'string', default: '', description: 'Optional. Used in Remix mode.' }, ...commonWebhookFields]),
147
+ ...withDisplay('upsample', [
148
+ uuidHashField,
149
+ {
150
+ displayName: 'Choice', name: 'choice', type: 'options', required: true, default: 'v6_2x_subtle',
151
+ options: [{ name: 'v6_2x_subtle', value: 'v6_2x_subtle' }, { name: 'v6_2x_creative', value: 'v6_2x_creative' }, { name: 'v6r1_2x_subtle', value: 'v6r1_2x_subtle' }, { name: 'v6r1_2x_creative', value: 'v6r1_2x_creative' }, { name: 'v5_2x', value: 'v5_2x' }, { name: 'v5_4x', value: 'v5_4x' }, { name: 'v7_2x_subtle', value: 'v7_2x_subtle' }, { name: 'v7_2x_creative', value: 'v7_2x_creative' }],
152
+ },
153
+ ...commonWebhookFields,
154
+ ]),
155
+ ...withDisplay('animate', [
156
+ uuidHashField,
157
+ { displayName: 'Choice', name: 'choice', type: 'options', required: true, default: 'high', options: [{ name: 'High', value: 'high' }, { name: 'Low', value: 'low' }] },
158
+ ...commonWebhookFields,
159
+ ]),
160
+ ...withDisplay('inpaint', [uuidHashField, { displayName: 'Mask (Base64)', name: 'mask', type: 'string', required: true, default: '' }, { displayName: 'Prompt', name: 'prompt', type: 'string', required: true, default: '' }, ...commonWebhookFields]),
161
+ ...withDisplay('zoom', [
162
+ uuidHashField,
163
+ { displayName: 'Zoom Mode', name: 'zoom_mode', type: 'options', default: 'choice', options: [{ name: 'By Choice', value: 'choice' }, { name: 'By Prompt', value: 'prompt' }] },
164
+ { displayName: 'Choice', name: 'choice', type: 'options', required: true, default: 50, options: [{ name: '50', value: 50 }, { name: '75', value: 75 }], displayOptions: { show: { operation: ['zoom'], zoom_mode: ['choice'] } } },
165
+ { displayName: 'Prompt', name: 'prompt', type: 'string', required: true, default: '', displayOptions: { show: { operation: ['zoom'], zoom_mode: ['prompt'] } } },
166
+ ...commonWebhookFields,
167
+ ]),
168
+ ...withDisplay('blend', [
169
+ {
170
+ displayName: 'URLs',
171
+ name: 'urls',
172
+ type: 'fixedCollection',
173
+ typeOptions: { multipleValues: true },
174
+ default: { values: [{ url: '' }, { url: '' }] },
175
+ options: [{ displayName: 'URL Entry', name: 'values', values: [{ displayName: 'URL', name: 'url', type: 'string', required: true, default: '' }] }],
176
+ description: 'From 2 to 5 image URLs',
177
+ },
178
+ ...commonWebhookFields,
179
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', default: '', description: optionalAccountHashDescription },
180
+ ]),
181
+ ...withDisplay('prefer', [
182
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', required: true, default: '', description: requiredAccountHashDescription },
183
+ { displayName: 'Webhook URL', name: 'webhook_url', type: 'string', default: '' },
184
+ { displayName: 'Callback ID', name: 'callback_id', type: 'string', default: '', description: 'Optional callback identifier (max 72 chars)' },
185
+ { displayName: 'Is Async', name: 'is_async', type: 'boolean', default: false, description: 'On: return hash immediately. Off: wait for final result.' },
186
+ ]),
187
+ ...withDisplay('speed', [
188
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', required: true, default: '', description: requiredAccountHashDescription },
189
+ {
190
+ displayName: 'Choice',
191
+ name: 'choice',
192
+ type: 'options',
193
+ required: true,
194
+ default: 'relax',
195
+ options: [
196
+ { name: 'relax', value: 'relax' },
197
+ { name: 'fast', value: 'fast' },
198
+ { name: 'turbo', value: 'turbo' },
199
+ ],
200
+ description: 'Speed mode',
201
+ },
202
+ { displayName: 'Webhook URL', name: 'webhook_url', type: 'string', default: '' },
203
+ { displayName: 'Callback ID', name: 'callback_id', type: 'string', default: '', description: 'Optional callback identifier (max 72 chars)' },
204
+ { displayName: 'Is Async', name: 'is_async', type: 'boolean', default: false, description: 'On: return hash immediately. Off: wait for final result.' },
205
+ ]),
206
+ ...withDisplay('upload', [
207
+ {
208
+ displayName: 'Image URL',
209
+ name: 'url',
210
+ type: 'string',
211
+ required: true,
212
+ default: '',
213
+ },
214
+ { displayName: 'Account Hash', name: 'account_hash', type: 'string', default: '', description: optionalAccountHashDescription },
215
+ ]),
216
+ ...withDisplay('seed', [
217
+ uuidHashField,
218
+ { displayName: 'Is Async', name: 'is_async', type: 'boolean', default: false, description: 'On: return hash immediately. Off: wait for final result.' },
219
+ { displayName: 'Webhook URL', name: 'webhook_url', type: 'string', default: '' },
220
+ { displayName: 'Callback ID', name: 'callback_id', type: 'string', default: '', description: 'Optional callback identifier (max 72 chars)' },
221
+ ]),
222
+ ...withDisplay('status', [uuidHashField]),
223
+ ],
224
+ };
225
+ }
226
+ async execute() {
227
+ var _a;
228
+ const items = this.getInputData();
229
+ const returnData = [];
230
+ for (let i = 0; i < items.length; i++) {
231
+ try {
232
+ const operation = this.getNodeParameter('operation', i);
233
+ const requestOptions = { method: 'GET', url: '/', json: true };
234
+ let body = {};
235
+ let query = {};
236
+ switch (operation) {
237
+ case 'imagine':
238
+ requestOptions.method = 'POST';
239
+ requestOptions.url = '/midjourney/v2/imagine';
240
+ body = { prompt: this.getNodeParameter('prompt', i), is_disable_prefilter: this.getNodeParameter('is_disable_prefilter', i) };
241
+ addWebhookFields(this, i, body);
242
+ addOptionalString(body, 'callback_id', this.getNodeParameter('callback_id', i));
243
+ addOptionalString(body, 'account_hash', this.getNodeParameter('account_hash', i));
244
+ break;
245
+ case 'imagineInstant':
246
+ requestOptions.method = 'POST';
247
+ requestOptions.url = '/midjourney/v2/imagine-instant';
248
+ body = { prompt: this.getNodeParameter('prompt', i), choice: this.getNodeParameter('choice', i) };
249
+ addOptionalString(body, 'account_hash', this.getNodeParameter('account_hash', i));
250
+ break;
251
+ case 'describe':
252
+ requestOptions.method = 'POST';
253
+ requestOptions.url = '/midjourney/v2/describe';
254
+ body = { url: this.getNodeParameter('url', i) };
255
+ if (!isValidHttpUrl(body.url)) {
256
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'URL must be a valid http/https image URL' });
257
+ }
258
+ addWebhookFields(this, i, body);
259
+ addOptionalString(body, 'account_hash', this.getNodeParameter('account_hash', i));
260
+ break;
261
+ case 'info':
262
+ requestOptions.method = 'POST';
263
+ requestOptions.url = '/midjourney/v2/info';
264
+ body = { account_hash: this.getNodeParameter('account_hash', i) };
265
+ {
266
+ const webhookUrl = this.getNodeParameter('webhook_url', i);
267
+ if (webhookUrl !== '') {
268
+ if (!isValidHttpUrl(webhookUrl)) {
269
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Webhook URL must be a valid http/https URL' });
270
+ }
271
+ body.webhook_url = webhookUrl;
272
+ }
273
+ }
274
+ addOptionalString(body, 'callback_id', this.getNodeParameter('callback_id', i));
275
+ break;
276
+ case 'upscale':
277
+ requestOptions.method = 'POST';
278
+ requestOptions.url = '/midjourney/v2/upscale';
279
+ body = { hash: this.getNodeParameter('hash', i), choice: this.getNodeParameter('choice', i) };
280
+ if (!isUuidV4Like(body.hash))
281
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
282
+ addWebhookFields(this, i, body);
283
+ break;
284
+ case 'pan':
285
+ requestOptions.method = 'POST';
286
+ requestOptions.url = '/midjourney/v2/pan';
287
+ body = { hash: this.getNodeParameter('hash', i), choice: this.getNodeParameter('choice', i) };
288
+ if (!isUuidV4Like(body.hash))
289
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
290
+ addOptionalString(body, 'prompt', this.getNodeParameter('prompt', i));
291
+ addWebhookFields(this, i, body);
292
+ break;
293
+ case 'variation':
294
+ requestOptions.method = 'POST';
295
+ requestOptions.url = '/midjourney/v2/variation';
296
+ body = { hash: this.getNodeParameter('hash', i), choice: this.getNodeParameter('choice', i) };
297
+ if (!isUuidV4Like(body.hash))
298
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
299
+ addOptionalString(body, 'prompt', this.getNodeParameter('prompt', i));
300
+ addWebhookFields(this, i, body);
301
+ break;
302
+ case 'varySubtle':
303
+ requestOptions.method = 'POST';
304
+ requestOptions.url = '/midjourney/v2/vary-subtle';
305
+ body = { hash: this.getNodeParameter('hash', i) };
306
+ if (!isUuidV4Like(body.hash))
307
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
308
+ addOptionalString(body, 'prompt', this.getNodeParameter('prompt', i));
309
+ addWebhookFields(this, i, body);
310
+ break;
311
+ case 'varyStrong':
312
+ requestOptions.method = 'POST';
313
+ requestOptions.url = '/midjourney/v2/vary-strong';
314
+ body = { hash: this.getNodeParameter('hash', i) };
315
+ if (!isUuidV4Like(body.hash))
316
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
317
+ addOptionalString(body, 'prompt', this.getNodeParameter('prompt', i));
318
+ addWebhookFields(this, i, body);
319
+ break;
320
+ case 'reroll':
321
+ requestOptions.method = 'POST';
322
+ requestOptions.url = '/midjourney/v2/reroll';
323
+ body = { hash: this.getNodeParameter('hash', i) };
324
+ if (!isUuidV4Like(body.hash))
325
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
326
+ addOptionalString(body, 'prompt', this.getNodeParameter('prompt', i));
327
+ addWebhookFields(this, i, body);
328
+ break;
329
+ case 'upsample':
330
+ requestOptions.method = 'POST';
331
+ requestOptions.url = '/midjourney/v2/upsample';
332
+ body = { hash: this.getNodeParameter('hash', i), choice: this.getNodeParameter('choice', i) };
333
+ if (!isUuidV4Like(body.hash))
334
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
335
+ addWebhookFields(this, i, body);
336
+ break;
337
+ case 'animate':
338
+ requestOptions.method = 'POST';
339
+ requestOptions.url = '/midjourney/v2/animate';
340
+ body = { hash: this.getNodeParameter('hash', i), choice: this.getNodeParameter('choice', i) };
341
+ if (!isUuidV4Like(body.hash))
342
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
343
+ addWebhookFields(this, i, body);
344
+ break;
345
+ case 'inpaint':
346
+ requestOptions.method = 'POST';
347
+ requestOptions.url = '/midjourney/v2/inpaint';
348
+ body = { hash: this.getNodeParameter('hash', i), mask: this.getNodeParameter('mask', i), prompt: this.getNodeParameter('prompt', i) };
349
+ if (!isUuidV4Like(body.hash))
350
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
351
+ addWebhookFields(this, i, body);
352
+ break;
353
+ case 'zoom': {
354
+ requestOptions.method = 'POST';
355
+ requestOptions.url = '/midjourney/v2/zoom';
356
+ body = { hash: this.getNodeParameter('hash', i) };
357
+ if (!isUuidV4Like(body.hash))
358
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
359
+ const mode = this.getNodeParameter('zoom_mode', i);
360
+ if (mode === 'choice')
361
+ body.choice = this.getNodeParameter('choice', i);
362
+ else
363
+ body.prompt = this.getNodeParameter('prompt', i);
364
+ addWebhookFields(this, i, body);
365
+ break;
366
+ }
367
+ case 'blend': {
368
+ requestOptions.method = 'POST';
369
+ requestOptions.url = '/midjourney/v2/blend';
370
+ const urlsRaw = this.getNodeParameter('urls.values', i, []);
371
+ if (urlsRaw.length < 2 || urlsRaw.length > 5)
372
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Blend requires 2 to 5 URLs' });
373
+ for (const entry of urlsRaw) {
374
+ if (!isValidHttpUrl(entry.url)) {
375
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Each Blend URL must be a valid http/https URL' });
376
+ }
377
+ }
378
+ body = { urls: urlsRaw.map((u) => ({ url: u.url })) };
379
+ addWebhookFields(this, i, body);
380
+ addOptionalString(body, 'account_hash', this.getNodeParameter('account_hash', i));
381
+ break;
382
+ }
383
+ case 'prefer':
384
+ requestOptions.method = 'POST';
385
+ requestOptions.url = '/midjourney/v2/prefer';
386
+ body = { account_hash: this.getNodeParameter('account_hash', i), choice: 'remix', is_async: this.getNodeParameter('is_async', i) };
387
+ {
388
+ const webhookUrl = this.getNodeParameter('webhook_url', i);
389
+ if (webhookUrl !== '') {
390
+ if (!isValidHttpUrl(webhookUrl)) {
391
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Webhook URL must be a valid http/https URL' });
392
+ }
393
+ body.webhook_url = webhookUrl;
394
+ }
395
+ }
396
+ addOptionalString(body, 'callback_id', this.getNodeParameter('callback_id', i));
397
+ break;
398
+ case 'speed':
399
+ requestOptions.method = 'POST';
400
+ requestOptions.url = '/midjourney/v2/speed';
401
+ body = {
402
+ account_hash: this.getNodeParameter('account_hash', i),
403
+ choice: this.getNodeParameter('choice', i),
404
+ is_async: this.getNodeParameter('is_async', i),
405
+ };
406
+ {
407
+ const webhookUrl = this.getNodeParameter('webhook_url', i);
408
+ if (webhookUrl !== '') {
409
+ if (!isValidHttpUrl(webhookUrl)) {
410
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Webhook URL must be a valid http/https URL' });
411
+ }
412
+ body.webhook_url = webhookUrl;
413
+ }
414
+ }
415
+ addOptionalString(body, 'callback_id', this.getNodeParameter('callback_id', i));
416
+ break;
417
+ case 'upload':
418
+ requestOptions.method = 'POST';
419
+ requestOptions.url = '/midjourney/v2/upload';
420
+ {
421
+ const url = this.getNodeParameter('url', i);
422
+ if (!isValidHttpUrl(url)) {
423
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Image URL must be a valid http/https URL' });
424
+ }
425
+ body = { url };
426
+ addOptionalString(body, 'account_hash', this.getNodeParameter('account_hash', i));
427
+ }
428
+ break;
429
+ case 'seed':
430
+ requestOptions.method = 'POST';
431
+ requestOptions.url = '/midjourney/v2/seed';
432
+ body = { hash: this.getNodeParameter('hash', i), is_async: this.getNodeParameter('is_async', i) };
433
+ if (!isUuidV4Like(body.hash))
434
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
435
+ {
436
+ const webhookUrl = this.getNodeParameter('webhook_url', i);
437
+ if (webhookUrl !== '') {
438
+ if (!isValidHttpUrl(webhookUrl)) {
439
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Webhook URL must be a valid http/https URL' });
440
+ }
441
+ body.webhook_url = webhookUrl;
442
+ }
443
+ }
444
+ break;
445
+ case 'status':
446
+ requestOptions.method = 'GET';
447
+ requestOptions.url = '/midjourney/v2/status';
448
+ query = { hash: this.getNodeParameter('hash', i) };
449
+ if (!isUuidV4Like(query.hash))
450
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'Hash must be a valid UUID' });
451
+ break;
452
+ default:
453
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: `Unsupported operation: ${operation}` });
454
+ }
455
+ if (requestOptions.json !== false) {
456
+ requestOptions.headers = { ...((_a = requestOptions.headers) !== null && _a !== void 0 ? _a : {}), 'Content-Type': 'application/json' };
457
+ }
458
+ if (Object.keys(body).length > 0)
459
+ requestOptions.body = body;
460
+ if (Object.keys(query).length > 0)
461
+ requestOptions.qs = query;
462
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'userApi', requestOptions);
463
+ const json = typeof response === 'object' && response !== null ? response : { value: response };
464
+ returnData.push({ json, pairedItem: { item: i } });
465
+ }
466
+ catch (error) {
467
+ if (this.continueOnFail()) {
468
+ returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
469
+ continue;
470
+ }
471
+ throw error;
472
+ }
473
+ }
474
+ return [returnData];
475
+ }
476
+ }
477
+ exports.UserApi = UserApi;
478
+ function addOptionalString(target, key, value) {
479
+ if (value !== '')
480
+ target[key] = value;
481
+ }
482
+ function addWebhookFields(context, itemIndex, target) {
483
+ const webhookUrl = context.getNodeParameter('webhook_url', itemIndex);
484
+ if (webhookUrl === '')
485
+ return;
486
+ if (!isValidHttpUrl(webhookUrl)) {
487
+ throw new n8n_workflow_1.NodeApiError(context.getNode(), { message: 'Webhook URL must be a valid http/https URL' });
488
+ }
489
+ target.webhook_url = webhookUrl;
490
+ target.webhook_type = context.getNodeParameter('webhook_type', itemIndex);
491
+ }
492
+ function isValidHttpUrl(value) {
493
+ try {
494
+ const u = new URL(value);
495
+ return u.protocol === 'http:' || u.protocol === 'https:';
496
+ }
497
+ catch {
498
+ return false;
499
+ }
500
+ }
501
+ function isUuidV4Like(value) {
502
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
503
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "n8n-nodes-userapi",
3
+ "version": "0.1.0",
4
+ "description": "n8n community nodes for UserAPI Midjourney endpoints",
5
+ "license": "MIT",
6
+ "homepage": "https://api.userapi.ai",
7
+ "author": {
8
+ "name": "UserAPI"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/userapi-ai/n8n-nodes-userapi.git"
13
+ },
14
+ "keywords": [
15
+ "n8n-community-node-package",
16
+ "n8n",
17
+ "userapi",
18
+ "midjourney"
19
+ ],
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "main": "dist/nodes/UserApi/UserApi.node.js",
24
+ "types": "dist/nodes/UserApi/UserApi.node.d.ts",
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "dev": "tsc -w -p tsconfig.json",
28
+ "lint": "echo \"Add ESLint config if needed\""
29
+ },
30
+ "peerDependencies": {
31
+ "n8n-workflow": "*"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.13.4",
35
+ "typescript": "^5.7.3"
36
+ },
37
+ "n8n": {
38
+ "n8nNodesApiVersion": 1,
39
+ "credentials": [
40
+ "dist/credentials/UserApi.credentials.js"
41
+ ],
42
+ "nodes": [
43
+ "dist/nodes/UserApi/UserApi.node.js"
44
+ ]
45
+ }
46
+ }