n8n-nodes-sotoros-gotenberg 1.0.0 → 1.0.2
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/nodes/Gotenberg/Gotenberg.node.d.ts +5 -0
- package/dist/nodes/Gotenberg/Gotenberg.node.js +337 -0
- package/dist/nodes/Gotenberg/index.d.ts +2 -0
- package/dist/nodes/Gotenberg/index.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Gotenberg } from './Gotenberg.node';
|
|
2
|
-
export
|
|
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
|
-
|
|
4
|
+
exports.default = Gotenberg_node_1.Gotenberg;
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-sotoros-gotenberg",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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@
|
|
13
|
+
"author": "Danila Fokin <sir.sotoros@ya.ru> (https://github.com/sotoros)",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": ""
|