n8n-nodes-sotoros-gotenberg 1.0.7 → 1.0.9
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.
|
@@ -288,33 +288,70 @@ class Gotenberg {
|
|
|
288
288
|
// Создаем FormData для отправки в Gotenberg - один запрос для всех элементов списка
|
|
289
289
|
const formData = new form_data_1.default();
|
|
290
290
|
let totalFilesCount = 0;
|
|
291
|
+
const processedFiles = [];
|
|
292
|
+
// Собираем информацию о доступных binary данных в aggregatedItem
|
|
293
|
+
const aggregatedBinaryKeys = Object.keys(aggregatedItem.binary || {});
|
|
294
|
+
const debugInfo = {
|
|
295
|
+
aggregatedItemBinaryKeys: aggregatedBinaryKeys,
|
|
296
|
+
listDataLength: listData.length,
|
|
297
|
+
binaryPropertyName,
|
|
298
|
+
listPropertyName: listPropertyName || '(root array)',
|
|
299
|
+
processingSteps: [],
|
|
300
|
+
};
|
|
291
301
|
// Собираем все binary данные из всех элементов списка
|
|
292
302
|
for (let listIndex = 0; listIndex < listData.length; listIndex++) {
|
|
293
303
|
const listItem = listData[listIndex];
|
|
304
|
+
const stepInfo = {
|
|
305
|
+
listIndex,
|
|
306
|
+
itemType: typeof listItem,
|
|
307
|
+
isObject: typeof listItem === 'object' && listItem !== null,
|
|
308
|
+
itemKeys: listItem && typeof listItem === 'object' ? Object.keys(listItem) : [],
|
|
309
|
+
foundBinary: false,
|
|
310
|
+
source: 'none',
|
|
311
|
+
binaryKey: undefined,
|
|
312
|
+
};
|
|
294
313
|
if (!listItem || typeof listItem !== 'object') {
|
|
314
|
+
stepInfo.error = 'Item is not an object or is null';
|
|
315
|
+
debugInfo.processingSteps.push(stepInfo);
|
|
295
316
|
continue; // Пропускаем некорректные элементы
|
|
296
317
|
}
|
|
297
318
|
// Элемент списка может быть INodeExecutionData (с полями json и binary)
|
|
298
319
|
// или обычным объектом с binary данными в разных местах
|
|
299
320
|
let itemBinary;
|
|
300
321
|
let binaryProperty;
|
|
322
|
+
let binaryKeyFromAggregated;
|
|
323
|
+
let source = 'unknown';
|
|
301
324
|
// Вариант 1: элемент имеет структуру INodeExecutionData (json и binary)
|
|
302
325
|
if ('binary' in listItem && listItem.binary && typeof listItem.binary === 'object') {
|
|
303
326
|
itemBinary = listItem.binary;
|
|
304
|
-
|
|
327
|
+
if (binaryPropertyName in itemBinary) {
|
|
328
|
+
binaryProperty = itemBinary[binaryPropertyName];
|
|
329
|
+
source = 'listItem.binary';
|
|
330
|
+
stepInfo.source = source;
|
|
331
|
+
stepInfo.binaryKey = binaryPropertyName;
|
|
332
|
+
stepInfo.foundBinary = true;
|
|
333
|
+
}
|
|
305
334
|
}
|
|
306
335
|
// Вариант 2: binary данные находятся в свойстве элемента напрямую
|
|
307
|
-
|
|
336
|
+
if (!binaryProperty && binaryPropertyName in listItem) {
|
|
308
337
|
const binaryProp = listItem[binaryPropertyName];
|
|
309
338
|
if (binaryProp && typeof binaryProp === 'object') {
|
|
310
339
|
// Может быть одиночным объектом или массивом
|
|
311
|
-
if (binaryProp.data) {
|
|
340
|
+
if (binaryProp.data || binaryProp.fileName || binaryProp.mimeType) {
|
|
312
341
|
// Это одиночный binary объект
|
|
313
342
|
binaryProperty = binaryProp;
|
|
343
|
+
source = 'listItem[binaryPropertyName]';
|
|
344
|
+
stepInfo.source = source;
|
|
345
|
+
stepInfo.binaryKey = binaryPropertyName;
|
|
346
|
+
stepInfo.foundBinary = true;
|
|
314
347
|
}
|
|
315
348
|
else if (Array.isArray(binaryProp)) {
|
|
316
349
|
// Это массив binary объектов
|
|
317
350
|
binaryProperty = binaryProp;
|
|
351
|
+
source = 'listItem[binaryPropertyName] (array)';
|
|
352
|
+
stepInfo.source = source;
|
|
353
|
+
stepInfo.binaryKey = binaryPropertyName;
|
|
354
|
+
stepInfo.foundBinary = true;
|
|
318
355
|
}
|
|
319
356
|
}
|
|
320
357
|
}
|
|
@@ -322,24 +359,102 @@ class Gotenberg {
|
|
|
322
359
|
// ищем их в aggregatedItem.binary по индексу
|
|
323
360
|
// Для первого элемента (index 0) используем binaryPropertyName, для остальных - binaryPropertyName_${index}
|
|
324
361
|
if (!binaryProperty && aggregatedItem.binary) {
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
binaryProperty = aggregatedItem.binary[
|
|
362
|
+
binaryKeyFromAggregated = listIndex === 0 ? binaryPropertyName : `${binaryPropertyName}_${listIndex}`;
|
|
363
|
+
if (binaryKeyFromAggregated in aggregatedItem.binary) {
|
|
364
|
+
binaryProperty = aggregatedItem.binary[binaryKeyFromAggregated];
|
|
365
|
+
source = 'aggregatedItem.binary';
|
|
366
|
+
stepInfo.source = source;
|
|
367
|
+
stepInfo.binaryKey = binaryKeyFromAggregated;
|
|
368
|
+
stepInfo.foundBinary = true;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// Пробуем альтернативные варианты именования
|
|
372
|
+
const alternativeKeys = [
|
|
373
|
+
`${binaryPropertyName}_${listIndex + 1}`, // data_1 для index 0
|
|
374
|
+
`${listIndex}`, // просто индекс
|
|
375
|
+
`file_${listIndex}`, // file_0, file_1
|
|
376
|
+
];
|
|
377
|
+
for (const altKey of alternativeKeys) {
|
|
378
|
+
if (altKey in aggregatedItem.binary) {
|
|
379
|
+
binaryProperty = aggregatedItem.binary[altKey];
|
|
380
|
+
source = `aggregatedItem.binary (alternative key: ${altKey})`;
|
|
381
|
+
stepInfo.source = source;
|
|
382
|
+
stepInfo.binaryKey = altKey;
|
|
383
|
+
stepInfo.foundBinary = true;
|
|
384
|
+
binaryKeyFromAggregated = altKey;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
328
388
|
}
|
|
329
389
|
}
|
|
330
390
|
if (!binaryProperty) {
|
|
391
|
+
stepInfo.error = `Binary property not found. Tried: listItem.binary["${binaryPropertyName}"], listItem["${binaryPropertyName}"], aggregatedItem.binary["${binaryPropertyName}${listIndex === 0 ? '' : '_' + listIndex}"]`;
|
|
392
|
+
debugInfo.processingSteps.push(stepInfo);
|
|
331
393
|
continue; // Пропускаем элементы без binary данных
|
|
332
394
|
}
|
|
333
395
|
// Обрабатываем как одиночное значение, так и массив
|
|
334
396
|
const binaryItems = Array.isArray(binaryProperty) ? binaryProperty : [binaryProperty];
|
|
397
|
+
stepInfo.binaryItemsCount = binaryItems.length;
|
|
335
398
|
// Добавляем все файлы из этого элемента списка
|
|
336
399
|
for (let i = 0; i < binaryItems.length; i++) {
|
|
337
400
|
const binaryItem = binaryItems[i];
|
|
338
|
-
if (!binaryItem
|
|
401
|
+
if (!binaryItem) {
|
|
402
|
+
stepInfo.error = `Binary item ${i} is null or undefined`;
|
|
339
403
|
continue; // Пропускаем файлы без данных
|
|
340
404
|
}
|
|
341
|
-
// Получаем buffer из binary данных
|
|
342
|
-
|
|
405
|
+
// Получаем buffer из binary данных через хелпер n8n
|
|
406
|
+
let dataBuffer;
|
|
407
|
+
let bufferError;
|
|
408
|
+
const fileInfo = {
|
|
409
|
+
listIndex,
|
|
410
|
+
fileIndex: i,
|
|
411
|
+
source,
|
|
412
|
+
binaryKey: binaryKeyFromAggregated || binaryPropertyName,
|
|
413
|
+
fileName: binaryItem.fileName || 'unknown',
|
|
414
|
+
mimeType: binaryItem.mimeType || 'unknown',
|
|
415
|
+
hasData: false,
|
|
416
|
+
fileSize: 0,
|
|
417
|
+
};
|
|
418
|
+
try {
|
|
419
|
+
// Если binary данные из aggregatedItem.binary, используем имя свойства напрямую
|
|
420
|
+
if (binaryKeyFromAggregated) {
|
|
421
|
+
dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryKeyFromAggregated);
|
|
422
|
+
fileInfo.source = `aggregatedItem.binary["${binaryKeyFromAggregated}"]`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// Используем хелпер getBinaryDataBuffer для правильной обработки binary данных
|
|
426
|
+
// Передаем itemIndex = 0 (aggregatedItem) и IBinaryData объект
|
|
427
|
+
dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryItem);
|
|
428
|
+
fileInfo.source = source;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
bufferError = error.message || String(error);
|
|
433
|
+
fileInfo.bufferError = bufferError;
|
|
434
|
+
// Если не получилось через хелпер, пробуем напрямую (для обратной совместимости)
|
|
435
|
+
if (binaryItem.data) {
|
|
436
|
+
try {
|
|
437
|
+
dataBuffer = Buffer.from(binaryItem.data, 'base64');
|
|
438
|
+
fileInfo.source = `${source} (direct base64)`;
|
|
439
|
+
}
|
|
440
|
+
catch (e) {
|
|
441
|
+
bufferError = `Failed to decode base64: ${e.message}`;
|
|
442
|
+
fileInfo.bufferError = bufferError;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
fileInfo.bufferError = 'No data field in binaryItem and getBinaryDataBuffer failed';
|
|
447
|
+
debugInfo.processingSteps.push(stepInfo);
|
|
448
|
+
continue; // Пропускаем файлы без данных
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (!dataBuffer || dataBuffer.length === 0) {
|
|
452
|
+
fileInfo.bufferError = 'Buffer is empty or null';
|
|
453
|
+
debugInfo.processingSteps.push(stepInfo);
|
|
454
|
+
continue; // Пропускаем пустые файлы
|
|
455
|
+
}
|
|
456
|
+
fileInfo.hasData = true;
|
|
457
|
+
fileInfo.fileSize = dataBuffer.length;
|
|
343
458
|
// Используем fileName из метаданных элемента списка, если доступно
|
|
344
459
|
const fileName = binaryItem.fileName ||
|
|
345
460
|
(listItem && typeof listItem === 'object' && 'fileName' in listItem
|
|
@@ -348,14 +463,21 @@ class Gotenberg {
|
|
|
348
463
|
(listItem && typeof listItem === 'object' && 'fileExtension' in listItem
|
|
349
464
|
? `${binaryPropertyName}_${listIndex}.${listItem.fileExtension}`
|
|
350
465
|
: `${binaryPropertyName}_${listIndex}_${i}.${getFileExtensionFromMimeType(binaryItem.mimeType || '')}`);
|
|
466
|
+
fileInfo.fileName = fileName;
|
|
351
467
|
formData.append('files', dataBuffer, {
|
|
352
468
|
filename: fileName,
|
|
353
469
|
contentType: binaryItem.mimeType || 'application/octet-stream',
|
|
354
470
|
});
|
|
471
|
+
processedFiles.push(fileInfo);
|
|
355
472
|
totalFilesCount++;
|
|
356
473
|
}
|
|
474
|
+
stepInfo.processedFiles = processedFiles.filter((f) => f.listIndex === listIndex).length;
|
|
475
|
+
debugInfo.processingSteps.push(stepInfo);
|
|
357
476
|
}
|
|
358
477
|
if (totalFilesCount === 0) {
|
|
478
|
+
// Добавляем информацию о шагах обработки в debugInfo
|
|
479
|
+
debugInfo.processedFiles = processedFiles;
|
|
480
|
+
debugInfo.totalFilesCount = totalFilesCount;
|
|
359
481
|
// Собираем информацию о структуре элементов списка
|
|
360
482
|
const sampleItems = listData.slice(0, 3).map((item, index) => {
|
|
361
483
|
const itemInfo = {
|
|
@@ -393,7 +515,7 @@ class Gotenberg {
|
|
|
393
515
|
sampleBinaryData: {},
|
|
394
516
|
};
|
|
395
517
|
// Показываем структуру первых нескольких binary данных
|
|
396
|
-
const binaryKeysToShow = Object.keys(aggregatedItem.binary || {}).slice(0,
|
|
518
|
+
const binaryKeysToShow = Object.keys(aggregatedItem.binary || {}).slice(0, 5);
|
|
397
519
|
for (const key of binaryKeysToShow) {
|
|
398
520
|
const binaryData = aggregatedItem.binary[key];
|
|
399
521
|
if (binaryData) {
|
|
@@ -402,6 +524,8 @@ class Gotenberg {
|
|
|
402
524
|
dataLength: binaryData.data ? binaryData.data.length : 0,
|
|
403
525
|
mimeType: binaryData.mimeType,
|
|
404
526
|
fileName: binaryData.fileName,
|
|
527
|
+
hasId: !!binaryData.id,
|
|
528
|
+
id: binaryData.id,
|
|
405
529
|
};
|
|
406
530
|
}
|
|
407
531
|
}
|
|
@@ -428,6 +552,7 @@ class Gotenberg {
|
|
|
428
552
|
return keys;
|
|
429
553
|
})(),
|
|
430
554
|
},
|
|
555
|
+
debugInfo,
|
|
431
556
|
});
|
|
432
557
|
const structureInfo = JSON.stringify(formattedStructure, null, 2);
|
|
433
558
|
// Проверяем, есть ли binary данные в aggregatedItem.binary
|
|
@@ -437,6 +562,7 @@ class Gotenberg {
|
|
|
437
562
|
expectedBinaryKeys.push(i === 0 ? binaryPropertyName : `${binaryPropertyName}_${i}`);
|
|
438
563
|
}
|
|
439
564
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No valid binary files found in any items. Please make sure binary data exists.\n\n` +
|
|
565
|
+
`Processing steps:\n${JSON.stringify(debugInfo.processingSteps, null, 2)}\n\n` +
|
|
440
566
|
`Input data structure:\n${structureInfo}\n\n` +
|
|
441
567
|
`Binary data search locations:\n` +
|
|
442
568
|
`1. In list item: item.binary["${binaryPropertyName}"] or item["${binaryPropertyName}"]\n` +
|
|
@@ -447,7 +573,7 @@ class Gotenberg {
|
|
|
447
573
|
`Tips:\n` +
|
|
448
574
|
`- Check that binaryPropertyName "${binaryPropertyName}" matches the property name\n` +
|
|
449
575
|
`- Verify that binary data exists in aggregatedItem.binary with keys: "${binaryPropertyName}", "${binaryPropertyName}_1", "${binaryPropertyName}_2", etc.\n` +
|
|
450
|
-
`- Make sure binary data has a "data" field with base64 content`);
|
|
576
|
+
`- Make sure binary data has a "data" field with base64 content or uses n8n binary storage`);
|
|
451
577
|
}
|
|
452
578
|
// Добавляем опции в зависимости от операции
|
|
453
579
|
if (operation === 'office' || operation === 'html' || operation === 'markdown') {
|
|
@@ -494,13 +620,63 @@ class Gotenberg {
|
|
|
494
620
|
}
|
|
495
621
|
// Отправляем запрос в Gotenberg
|
|
496
622
|
const url = `${gotenbergUrl.replace(/\/$/, '')}${endpoint}`;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
623
|
+
let response;
|
|
624
|
+
try {
|
|
625
|
+
response = await this.helpers.httpRequest({
|
|
626
|
+
method: 'POST',
|
|
627
|
+
url,
|
|
628
|
+
body: formData,
|
|
629
|
+
returnFullResponse: true,
|
|
630
|
+
headers: formData.getHeaders(),
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
// Улучшенная обработка ошибок подключения
|
|
635
|
+
if (error.code === 'ECONNREFUSED') {
|
|
636
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot connect to Gotenberg server at ${url}. ` +
|
|
637
|
+
`Connection refused. Please check:\n` +
|
|
638
|
+
`- Is Gotenberg server running?\n` +
|
|
639
|
+
`- Is the URL correct? (current: ${gotenbergUrl})\n` +
|
|
640
|
+
`- Is the port correct? (current endpoint: ${url})\n` +
|
|
641
|
+
`- If using IPv6 (::1), try using 127.0.0.1 or localhost instead`);
|
|
642
|
+
}
|
|
643
|
+
else if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') {
|
|
644
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot resolve Gotenberg server hostname. ` +
|
|
645
|
+
`Please check that the URL is correct: ${gotenbergUrl}`);
|
|
646
|
+
}
|
|
647
|
+
else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
|
|
648
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Connection to Gotenberg server timed out at ${url}. ` +
|
|
649
|
+
`Please check that the server is accessible and not overloaded.`);
|
|
650
|
+
}
|
|
651
|
+
else if (error.response) {
|
|
652
|
+
// Ошибка от сервера (HTTP ошибка)
|
|
653
|
+
const statusCode = error.response.status || error.response.statusCode;
|
|
654
|
+
let errorText;
|
|
655
|
+
if (error.response.data) {
|
|
656
|
+
if (Buffer.isBuffer(error.response.data)) {
|
|
657
|
+
errorText = error.response.data.toString('utf-8');
|
|
658
|
+
}
|
|
659
|
+
else if (typeof error.response.data === 'string') {
|
|
660
|
+
errorText = error.response.data;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
errorText = JSON.stringify(error.response.data);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
errorText = error.message || 'Unknown error';
|
|
668
|
+
}
|
|
669
|
+
throw new n8n_workflow_2.NodeApiError(this.getNode(), {
|
|
670
|
+
message: `Gotenberg API error: ${statusCode}`,
|
|
671
|
+
description: errorText,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
// Другие ошибки
|
|
676
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error connecting to Gotenberg server: ${error.message || String(error)}. ` +
|
|
677
|
+
`URL: ${url}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
504
680
|
// Проверяем статус ответа
|
|
505
681
|
if (response.statusCode && response.statusCode >= 400) {
|
|
506
682
|
let errorText;
|
|
@@ -551,9 +727,14 @@ class Gotenberg {
|
|
|
551
727
|
}
|
|
552
728
|
// Проверяем, что получили валидный PDF
|
|
553
729
|
if (pdfBuffer.length < 4 || pdfBuffer.toString('ascii', 0, 4) !== '%PDF') {
|
|
554
|
-
|
|
730
|
+
const pdfHeader = pdfBuffer.length > 0 ? pdfBuffer.toString('ascii', 0, Math.min(20, pdfBuffer.length)) : 'empty';
|
|
731
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid PDF received from Gotenberg. The response does not appear to be a valid PDF file.\n` +
|
|
732
|
+
`PDF buffer size: ${pdfBuffer.length} bytes\n` +
|
|
733
|
+
`First 20 bytes: ${pdfHeader}\n` +
|
|
734
|
+
`Processed files info:\n${JSON.stringify(processedFiles, null, 2)}\n` +
|
|
735
|
+
`Debug info:\n${JSON.stringify(debugInfo, null, 2)}`);
|
|
555
736
|
}
|
|
556
|
-
// Создаем один выходной элемент с результатом
|
|
737
|
+
// Создаем один выходной элемент с результатом и отладочной информацией
|
|
557
738
|
const newItem = {
|
|
558
739
|
json: {
|
|
559
740
|
...aggregatedItem.json,
|
|
@@ -561,6 +742,27 @@ class Gotenberg {
|
|
|
561
742
|
operation,
|
|
562
743
|
fileCount: totalFilesCount,
|
|
563
744
|
processedItems: listData.length,
|
|
745
|
+
debug: {
|
|
746
|
+
processedFiles: processedFiles.map((f) => ({
|
|
747
|
+
source: f.source,
|
|
748
|
+
binaryKey: f.binaryKey,
|
|
749
|
+
fileName: f.fileName,
|
|
750
|
+
fileSize: f.fileSize,
|
|
751
|
+
mimeType: f.mimeType,
|
|
752
|
+
listIndex: f.listIndex,
|
|
753
|
+
fileIndex: f.fileIndex,
|
|
754
|
+
})),
|
|
755
|
+
processingSteps: debugInfo.processingSteps,
|
|
756
|
+
aggregatedBinaryKeys: debugInfo.aggregatedItemBinaryKeys,
|
|
757
|
+
configuration: {
|
|
758
|
+
binaryPropertyName,
|
|
759
|
+
listPropertyName: listPropertyName || '(root array)',
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
pdfInfo: {
|
|
763
|
+
size: pdfBuffer.length,
|
|
764
|
+
isValid: pdfBuffer.length >= 4 && pdfBuffer.toString('ascii', 0, 4) === '%PDF',
|
|
765
|
+
},
|
|
564
766
|
},
|
|
565
767
|
binary: {
|
|
566
768
|
...aggregatedItem.binary,
|