n8n-nodes-sotoros-gotenberg 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # n8n-nodes-gotenberg
1
+ # n8n-nodes-sotoros-gotenberg
2
2
 
3
3
  Custom n8n node for integrating with Gotenberg API to convert documents to PDF.
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { Gotenberg } from './Gotenberg.node';
2
- export { Gotenberg };
2
+ export default Gotenberg;
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Gotenberg = void 0;
4
3
  const Gotenberg_node_1 = require("./Gotenberg.node");
5
- Object.defineProperty(exports, "Gotenberg", { enumerable: true, get: function () { return Gotenberg_node_1.Gotenberg; } });
4
+ exports.default = Gotenberg_node_1.Gotenberg;
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Gotenberg implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Gotenberg = void 0;
7
+ const n8n_workflow_1 = require("n8n-workflow");
8
+ const n8n_workflow_2 = require("n8n-workflow");
9
+ const form_data_1 = __importDefault(require("form-data"));
10
+ class Gotenberg {
11
+ constructor() {
12
+ this.description = {
13
+ displayName: 'Gotenberg',
14
+ name: 'gotenberg',
15
+ icon: 'file:gotenberg.svg',
16
+ group: ['transform'],
17
+ version: 1,
18
+ subtitle: '={{$parameter["operation"]}}',
19
+ description: 'Convert documents to PDF using Gotenberg',
20
+ defaults: {
21
+ name: 'Gotenberg',
22
+ },
23
+ inputs: ['main'],
24
+ outputs: ['main'],
25
+ properties: [
26
+ {
27
+ displayName: 'Gotenberg URL',
28
+ name: 'gotenbergUrl',
29
+ type: 'string',
30
+ default: 'http://localhost:3000',
31
+ placeholder: 'http://localhost:3000',
32
+ description: 'Base URL of the Gotenberg server',
33
+ required: true,
34
+ },
35
+ {
36
+ displayName: 'Operation',
37
+ name: 'operation',
38
+ type: 'options',
39
+ noDataExpression: true,
40
+ options: [
41
+ {
42
+ name: 'Convert HTML',
43
+ value: 'html',
44
+ action: 'Convert html to PDF',
45
+ description: 'Convert HTML to PDF',
46
+ },
47
+ {
48
+ name: 'Convert Markdown',
49
+ value: 'markdown',
50
+ action: 'Convert markdown to PDF',
51
+ description: 'Convert Markdown to PDF',
52
+ },
53
+ {
54
+ name: 'Convert Office Documents',
55
+ value: 'office',
56
+ action: 'Convert office documents to PDF',
57
+ description: 'Convert Office documents (Word, Excel, PowerPoint) to PDF',
58
+ },
59
+ {
60
+ name: 'Convert URL',
61
+ value: 'url',
62
+ action: 'Convert url to PDF',
63
+ description: 'Convert URL to PDF',
64
+ },
65
+ {
66
+ name: 'Merge PDFs',
67
+ value: 'merge',
68
+ action: 'Merge multiple PDF files into one',
69
+ description: 'Merge multiple PDF files into one',
70
+ },
71
+ ],
72
+ default: 'office',
73
+ required: true,
74
+ },
75
+ {
76
+ displayName: 'Binary Property',
77
+ name: 'binaryPropertyName',
78
+ type: 'string',
79
+ default: '',
80
+ description: 'Name of the binary property that contains the file(s) to convert. If empty, all binary properties from the previous node will be processed.',
81
+ },
82
+ {
83
+ displayName: 'Output Binary Property',
84
+ name: 'outputBinaryPropertyName',
85
+ type: 'string',
86
+ default: 'data',
87
+ description: 'Name of the binary property to save the converted PDF',
88
+ },
89
+ {
90
+ displayName: 'Options',
91
+ name: 'options',
92
+ type: 'collection',
93
+ placeholder: 'Add Option',
94
+ default: {},
95
+ options: [
96
+ {
97
+ displayName: 'Landscape',
98
+ name: 'landscape',
99
+ type: 'boolean',
100
+ default: false,
101
+ description: 'Whether to use landscape orientation',
102
+ },
103
+ {
104
+ displayName: 'Page Ranges',
105
+ name: 'pageRanges',
106
+ type: 'string',
107
+ default: '',
108
+ description: 'Page ranges to include (e.g., "1-2,4-5")',
109
+ },
110
+ {
111
+ displayName: 'Scale',
112
+ name: 'scale',
113
+ type: 'number',
114
+ typeOptions: {
115
+ minValue: 0.1,
116
+ maxValue: 2,
117
+ },
118
+ default: 1,
119
+ description: 'Scale factor for the PDF',
120
+ },
121
+ {
122
+ displayName: 'Wait Timeout',
123
+ name: 'waitTimeout',
124
+ type: 'string',
125
+ default: '30s',
126
+ description: 'Maximum time to wait for conversion (e.g., "30s", "1m")',
127
+ },
128
+ {
129
+ displayName: 'Wait Delay',
130
+ name: 'waitDelay',
131
+ type: 'string',
132
+ default: '0s',
133
+ description: 'Delay before starting conversion (e.g., "1s")',
134
+ },
135
+ ],
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ async execute() {
141
+ const items = this.getInputData();
142
+ const returnData = [];
143
+ const gotenbergUrl = this.getNodeParameter('gotenbergUrl', 0);
144
+ const operation = this.getNodeParameter('operation', 0);
145
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) || '';
146
+ const outputBinaryPropertyName = this.getNodeParameter('outputBinaryPropertyName', 0);
147
+ const options = this.getNodeParameter('options', 0, {});
148
+ // Вспомогательная функция для получения расширения файла из MIME типа
149
+ const getFileExtensionFromMimeType = (mimeType) => {
150
+ const mimeToExt = {
151
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
152
+ 'application/msword': 'doc',
153
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
154
+ 'application/vnd.ms-excel': 'xls',
155
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
156
+ 'application/vnd.ms-powerpoint': 'ppt',
157
+ 'text/html': 'html',
158
+ 'text/markdown': 'md',
159
+ 'application/pdf': 'pdf',
160
+ };
161
+ return mimeToExt[mimeType] || 'bin';
162
+ };
163
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
164
+ try {
165
+ const item = items[itemIndex];
166
+ const binaryData = item.binary;
167
+ if (!binaryData || Object.keys(binaryData).length === 0) {
168
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No binary data found in item ${itemIndex}. Please make sure the binary property exists.`, { itemIndex });
169
+ }
170
+ // Создаем FormData для отправки в Gotenberg
171
+ const formData = new form_data_1.default();
172
+ let totalFilesCount = 0;
173
+ // Определяем, какие binary свойства обрабатывать
174
+ const binaryPropertiesToProcess = binaryPropertyName
175
+ ? [binaryPropertyName]
176
+ : Object.keys(binaryData);
177
+ // Обрабатываем все указанные binary свойства
178
+ for (const propName of binaryPropertiesToProcess) {
179
+ const binaryProperty = binaryData[propName];
180
+ if (!binaryProperty) {
181
+ if (binaryPropertyName) {
182
+ // Если явно указано свойство, но его нет - ошибка
183
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Binary property "${propName}" not found in item ${itemIndex}`, { itemIndex });
184
+ }
185
+ // Если не указано явно - просто пропускаем отсутствующие свойства
186
+ continue;
187
+ }
188
+ // Обрабатываем как одиночное значение, так и массив
189
+ const binaryItems = Array.isArray(binaryProperty) ? binaryProperty : [binaryProperty];
190
+ // Добавляем все файлы из этого свойства
191
+ for (let i = 0; i < binaryItems.length; i++) {
192
+ const binaryItem = binaryItems[i];
193
+ if (!binaryItem || !binaryItem.data) {
194
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Binary item ${i} in property "${propName}" is missing data`, { itemIndex });
195
+ }
196
+ // Получаем buffer из binary данных
197
+ // В n8n, когда binary данные приходят как массив, каждый элемент уже содержит data в base64
198
+ let dataBuffer;
199
+ if (Array.isArray(binaryProperty)) {
200
+ // Если это массив, декодируем base64 из элемента массива
201
+ dataBuffer = Buffer.from(binaryItem.data, 'base64');
202
+ }
203
+ else {
204
+ // Если это одиночное значение, используем helper
205
+ dataBuffer = await this.helpers.getBinaryDataBuffer(itemIndex, propName);
206
+ }
207
+ const fileName = binaryItem.fileName ||
208
+ `${propName}_${i}.${getFileExtensionFromMimeType(binaryItem.mimeType || '')}`;
209
+ formData.append('files', dataBuffer, {
210
+ filename: fileName,
211
+ contentType: binaryItem.mimeType || 'application/octet-stream',
212
+ });
213
+ totalFilesCount++;
214
+ }
215
+ }
216
+ if (totalFilesCount === 0) {
217
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No valid binary files found in item ${itemIndex}`, { itemIndex });
218
+ }
219
+ // Добавляем опции в зависимости от операции
220
+ if (operation === 'office' || operation === 'html' || operation === 'markdown') {
221
+ if (options.landscape) {
222
+ formData.append('landscape', 'true');
223
+ }
224
+ if (options.pageRanges) {
225
+ formData.append('pageRanges', options.pageRanges);
226
+ }
227
+ if (options.scale !== undefined) {
228
+ formData.append('scale', options.scale.toString());
229
+ }
230
+ if (options.waitTimeout) {
231
+ formData.append('waitTimeout', options.waitTimeout);
232
+ }
233
+ if (options.waitDelay) {
234
+ formData.append('waitDelay', options.waitDelay);
235
+ }
236
+ }
237
+ // Определяем endpoint в зависимости от операции
238
+ let endpoint = '';
239
+ switch (operation) {
240
+ case 'office':
241
+ endpoint = '/convert/office';
242
+ break;
243
+ case 'html':
244
+ endpoint = '/convert/html';
245
+ break;
246
+ case 'markdown':
247
+ endpoint = '/convert/markdown';
248
+ break;
249
+ case 'url':
250
+ endpoint = '/convert/url';
251
+ // Для URL операции нужен параметр url
252
+ if (item.json.url) {
253
+ formData.append('url', item.json.url);
254
+ }
255
+ break;
256
+ case 'merge':
257
+ endpoint = '/forms/pdfengines/merge';
258
+ break;
259
+ default:
260
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, { itemIndex });
261
+ }
262
+ // Отправляем запрос в Gotenberg
263
+ const url = `${gotenbergUrl.replace(/\/$/, '')}${endpoint}`;
264
+ const response = await this.helpers.httpRequest({
265
+ method: 'POST',
266
+ url,
267
+ body: formData,
268
+ returnFullResponse: true,
269
+ headers: formData.getHeaders(),
270
+ });
271
+ // Проверяем статус ответа
272
+ if (response.statusCode && response.statusCode >= 400) {
273
+ let errorText;
274
+ if (Buffer.isBuffer(response.body)) {
275
+ errorText = response.body.toString('utf-8');
276
+ }
277
+ else if (typeof response.body === 'string') {
278
+ errorText = response.body;
279
+ }
280
+ else {
281
+ errorText = JSON.stringify(response.body);
282
+ }
283
+ throw new n8n_workflow_2.NodeApiError(this.getNode(), {
284
+ message: `Gotenberg API error: ${response.statusCode}`,
285
+ description: errorText,
286
+ }, { itemIndex });
287
+ }
288
+ // Получаем PDF buffer
289
+ let pdfBuffer;
290
+ if (Buffer.isBuffer(response.body)) {
291
+ pdfBuffer = response.body;
292
+ }
293
+ else if (typeof response.body === 'string') {
294
+ pdfBuffer = Buffer.from(response.body, 'base64');
295
+ }
296
+ else {
297
+ // Если это ArrayBuffer или другой тип
298
+ const bodyArray = new Uint8Array(response.body);
299
+ pdfBuffer = Buffer.from(bodyArray);
300
+ }
301
+ // Создаем выходной элемент
302
+ const newItem = {
303
+ json: {
304
+ ...item.json,
305
+ success: true,
306
+ operation,
307
+ fileCount: totalFilesCount,
308
+ },
309
+ binary: {
310
+ ...item.binary,
311
+ [outputBinaryPropertyName]: {
312
+ data: pdfBuffer.toString('base64'),
313
+ mimeType: 'application/pdf',
314
+ fileName: `converted_${itemIndex}.pdf`,
315
+ },
316
+ },
317
+ };
318
+ returnData.push(newItem);
319
+ }
320
+ catch (error) {
321
+ if (this.continueOnFail()) {
322
+ returnData.push({
323
+ json: {
324
+ error: error instanceof Error ? error.message : String(error),
325
+ success: false,
326
+ },
327
+ binary: items[itemIndex].binary,
328
+ });
329
+ continue;
330
+ }
331
+ throw error;
332
+ }
333
+ }
334
+ return [returnData];
335
+ }
336
+ }
337
+ exports.Gotenberg = Gotenberg;
@@ -0,0 +1 @@
1
+ export { Gotenberg } from './Gotenberg.node';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Gotenberg = void 0;
4
+ var Gotenberg_node_1 = require("./Gotenberg.node");
5
+ Object.defineProperty(exports, "Gotenberg", { enumerable: true, get: function () { return Gotenberg_node_1.Gotenberg; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sotoros-gotenberg",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "n8n custom node for Gotenberg integration with binary data support",
5
5
  "keywords": [
6
6
  "n8n-community-node",
@@ -10,7 +10,7 @@
10
10
  ],
11
11
  "license": "MIT",
12
12
  "homepage": "",
13
- "author": "Danila Fokin <sir.sotoros@ys.ru> (https://github.com/sotoros)",
13
+ "author": "Danila Fokin <sir.sotoros@ya.ru> (https://github.com/sotoros)",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": ""