n8n-nodes-sotoros-gotenberg 1.0.6 → 1.0.8
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.
|
@@ -297,9 +297,11 @@ class Gotenberg {
|
|
|
297
297
|
// Элемент списка может быть INodeExecutionData (с полями json и binary)
|
|
298
298
|
// или обычным объектом с binary данными в разных местах
|
|
299
299
|
let itemBinary;
|
|
300
|
+
let binaryProperty;
|
|
300
301
|
// Вариант 1: элемент имеет структуру INodeExecutionData (json и binary)
|
|
301
302
|
if ('binary' in listItem && listItem.binary && typeof listItem.binary === 'object') {
|
|
302
303
|
itemBinary = listItem.binary;
|
|
304
|
+
binaryProperty = itemBinary[binaryPropertyName];
|
|
303
305
|
}
|
|
304
306
|
// Вариант 2: binary данные находятся в свойстве элемента напрямую
|
|
305
307
|
else if (binaryPropertyName in listItem) {
|
|
@@ -308,34 +310,65 @@ class Gotenberg {
|
|
|
308
310
|
// Может быть одиночным объектом или массивом
|
|
309
311
|
if (binaryProp.data) {
|
|
310
312
|
// Это одиночный binary объект
|
|
311
|
-
|
|
313
|
+
binaryProperty = binaryProp;
|
|
312
314
|
}
|
|
313
315
|
else if (Array.isArray(binaryProp)) {
|
|
314
316
|
// Это массив binary объектов
|
|
315
|
-
|
|
317
|
+
binaryProperty = binaryProp;
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
// Вариант 3: если binary данные не найдены в элементе списка,
|
|
322
|
+
// ищем их в aggregatedItem.binary по индексу
|
|
323
|
+
// Для первого элемента (index 0) используем binaryPropertyName, для остальных - binaryPropertyName_${index}
|
|
324
|
+
let binaryKeyFromAggregated;
|
|
325
|
+
if (!binaryProperty && aggregatedItem.binary) {
|
|
326
|
+
binaryKeyFromAggregated = listIndex === 0 ? binaryPropertyName : `${binaryPropertyName}_${listIndex}`;
|
|
327
|
+
if (binaryKeyFromAggregated in aggregatedItem.binary) {
|
|
328
|
+
binaryProperty = aggregatedItem.binary[binaryKeyFromAggregated];
|
|
329
|
+
}
|
|
321
330
|
}
|
|
322
|
-
// Получаем binary свойство
|
|
323
|
-
const binaryProperty = itemBinary[binaryPropertyName];
|
|
324
331
|
if (!binaryProperty) {
|
|
325
|
-
continue; // Пропускаем элементы без
|
|
332
|
+
continue; // Пропускаем элементы без binary данных
|
|
326
333
|
}
|
|
327
334
|
// Обрабатываем как одиночное значение, так и массив
|
|
328
335
|
const binaryItems = Array.isArray(binaryProperty) ? binaryProperty : [binaryProperty];
|
|
329
336
|
// Добавляем все файлы из этого элемента списка
|
|
330
337
|
for (let i = 0; i < binaryItems.length; i++) {
|
|
331
338
|
const binaryItem = binaryItems[i];
|
|
332
|
-
if (!binaryItem
|
|
339
|
+
if (!binaryItem) {
|
|
333
340
|
continue; // Пропускаем файлы без данных
|
|
334
341
|
}
|
|
335
|
-
// Получаем buffer из binary данных
|
|
336
|
-
|
|
342
|
+
// Получаем buffer из binary данных через хелпер n8n
|
|
343
|
+
let dataBuffer;
|
|
344
|
+
try {
|
|
345
|
+
// Если binary данные из aggregatedItem.binary, используем имя свойства напрямую
|
|
346
|
+
if (binaryKeyFromAggregated) {
|
|
347
|
+
dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryKeyFromAggregated);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Используем хелпер getBinaryDataBuffer для правильной обработки binary данных
|
|
351
|
+
// Передаем itemIndex = 0 (aggregatedItem) и IBinaryData объект
|
|
352
|
+
dataBuffer = await this.helpers.getBinaryDataBuffer(0, binaryItem);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
// Если не получилось через хелпер, пробуем напрямую (для обратной совместимости)
|
|
357
|
+
if (binaryItem.data) {
|
|
358
|
+
dataBuffer = Buffer.from(binaryItem.data, 'base64');
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
continue; // Пропускаем файлы без данных
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Используем fileName из метаданных элемента списка, если доступно
|
|
337
365
|
const fileName = binaryItem.fileName ||
|
|
338
|
-
|
|
366
|
+
(listItem && typeof listItem === 'object' && 'fileName' in listItem
|
|
367
|
+
? listItem.fileName
|
|
368
|
+
: undefined) ||
|
|
369
|
+
(listItem && typeof listItem === 'object' && 'fileExtension' in listItem
|
|
370
|
+
? `${binaryPropertyName}_${listIndex}.${listItem.fileExtension}`
|
|
371
|
+
: `${binaryPropertyName}_${listIndex}_${i}.${getFileExtensionFromMimeType(binaryItem.mimeType || '')}`);
|
|
339
372
|
formData.append('files', dataBuffer, {
|
|
340
373
|
filename: fileName,
|
|
341
374
|
contentType: binaryItem.mimeType || 'application/octet-stream',
|
|
@@ -375,11 +408,29 @@ class Gotenberg {
|
|
|
375
408
|
}
|
|
376
409
|
return itemInfo;
|
|
377
410
|
});
|
|
411
|
+
// Собираем информацию о binary данных в aggregatedItem
|
|
412
|
+
const aggregatedBinaryInfo = {
|
|
413
|
+
keys: Object.keys(aggregatedItem.binary || {}),
|
|
414
|
+
sampleBinaryData: {},
|
|
415
|
+
};
|
|
416
|
+
// Показываем структуру первых нескольких binary данных
|
|
417
|
+
const binaryKeysToShow = Object.keys(aggregatedItem.binary || {}).slice(0, 3);
|
|
418
|
+
for (const key of binaryKeysToShow) {
|
|
419
|
+
const binaryData = aggregatedItem.binary[key];
|
|
420
|
+
if (binaryData) {
|
|
421
|
+
aggregatedBinaryInfo.sampleBinaryData[key] = {
|
|
422
|
+
hasData: !!binaryData.data,
|
|
423
|
+
dataLength: binaryData.data ? binaryData.data.length : 0,
|
|
424
|
+
mimeType: binaryData.mimeType,
|
|
425
|
+
fileName: binaryData.fileName,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
378
429
|
// Форматируем структуру данных для отладки
|
|
379
430
|
const formattedStructure = formatDataStructure({
|
|
380
431
|
aggregatedItem: {
|
|
381
432
|
jsonKeys: Object.keys(aggregatedItem.json || {}),
|
|
382
|
-
|
|
433
|
+
binaryInfo: aggregatedBinaryInfo,
|
|
383
434
|
jsonSample: formatDataStructure(aggregatedItem.json),
|
|
384
435
|
},
|
|
385
436
|
listDataInfo: {
|
|
@@ -390,17 +441,34 @@ class Gotenberg {
|
|
|
390
441
|
configuration: {
|
|
391
442
|
binaryPropertyName,
|
|
392
443
|
listPropertyName: listPropertyName || '(root array)',
|
|
444
|
+
expectedBinaryKeys: (() => {
|
|
445
|
+
const keys = [];
|
|
446
|
+
for (let i = 0; i < Math.min(listData.length, 10); i++) {
|
|
447
|
+
keys.push(i === 0 ? binaryPropertyName : `${binaryPropertyName}_${i}`);
|
|
448
|
+
}
|
|
449
|
+
return keys;
|
|
450
|
+
})(),
|
|
393
451
|
},
|
|
394
452
|
});
|
|
395
453
|
const structureInfo = JSON.stringify(formattedStructure, null, 2);
|
|
454
|
+
// Проверяем, есть ли binary данные в aggregatedItem.binary
|
|
455
|
+
const aggregatedBinaryKeys = Object.keys(aggregatedItem.binary || {});
|
|
456
|
+
const expectedBinaryKeys = [];
|
|
457
|
+
for (let i = 0; i < Math.min(listData.length, 10); i++) {
|
|
458
|
+
expectedBinaryKeys.push(i === 0 ? binaryPropertyName : `${binaryPropertyName}_${i}`);
|
|
459
|
+
}
|
|
396
460
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No valid binary files found in any items. Please make sure binary data exists.\n\n` +
|
|
397
461
|
`Input data structure:\n${structureInfo}\n\n` +
|
|
462
|
+
`Binary data search locations:\n` +
|
|
463
|
+
`1. In list item: item.binary["${binaryPropertyName}"] or item["${binaryPropertyName}"]\n` +
|
|
464
|
+
`2. In aggregated item: aggregatedItem.binary["${binaryPropertyName}"] (for first item)\n` +
|
|
465
|
+
`3. In aggregated item: aggregatedItem.binary["${binaryPropertyName}_${1}"] (for second item), etc.\n\n` +
|
|
466
|
+
`Found binary keys in aggregatedItem.binary: ${aggregatedBinaryKeys.length > 0 ? aggregatedBinaryKeys.join(', ') : 'none'}\n` +
|
|
467
|
+
`Expected binary keys for first 10 items: ${expectedBinaryKeys.join(', ')}\n\n` +
|
|
398
468
|
`Tips:\n` +
|
|
399
|
-
`- Check that binaryPropertyName "${binaryPropertyName}" matches the property name
|
|
400
|
-
`- Verify that binary data exists in
|
|
401
|
-
`- Make sure binary data has a "data" field with base64 content
|
|
402
|
-
`- If items have a "binary" property, binary data should be in binary["${binaryPropertyName}"]\n` +
|
|
403
|
-
`- If items have binary data directly, it should be in item["${binaryPropertyName}"]`);
|
|
469
|
+
`- Check that binaryPropertyName "${binaryPropertyName}" matches the property name\n` +
|
|
470
|
+
`- Verify that binary data exists in aggregatedItem.binary with keys: "${binaryPropertyName}", "${binaryPropertyName}_1", "${binaryPropertyName}_2", etc.\n` +
|
|
471
|
+
`- Make sure binary data has a "data" field with base64 content`);
|
|
404
472
|
}
|
|
405
473
|
// Добавляем опции в зависимости от операции
|
|
406
474
|
if (operation === 'office' || operation === 'html' || operation === 'markdown') {
|
|
@@ -447,13 +515,63 @@ class Gotenberg {
|
|
|
447
515
|
}
|
|
448
516
|
// Отправляем запрос в Gotenberg
|
|
449
517
|
const url = `${gotenbergUrl.replace(/\/$/, '')}${endpoint}`;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
518
|
+
let response;
|
|
519
|
+
try {
|
|
520
|
+
response = await this.helpers.httpRequest({
|
|
521
|
+
method: 'POST',
|
|
522
|
+
url,
|
|
523
|
+
body: formData,
|
|
524
|
+
returnFullResponse: true,
|
|
525
|
+
headers: formData.getHeaders(),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
// Улучшенная обработка ошибок подключения
|
|
530
|
+
if (error.code === 'ECONNREFUSED') {
|
|
531
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot connect to Gotenberg server at ${url}. ` +
|
|
532
|
+
`Connection refused. Please check:\n` +
|
|
533
|
+
`- Is Gotenberg server running?\n` +
|
|
534
|
+
`- Is the URL correct? (current: ${gotenbergUrl})\n` +
|
|
535
|
+
`- Is the port correct? (current endpoint: ${url})\n` +
|
|
536
|
+
`- If using IPv6 (::1), try using 127.0.0.1 or localhost instead`);
|
|
537
|
+
}
|
|
538
|
+
else if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') {
|
|
539
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot resolve Gotenberg server hostname. ` +
|
|
540
|
+
`Please check that the URL is correct: ${gotenbergUrl}`);
|
|
541
|
+
}
|
|
542
|
+
else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
|
|
543
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Connection to Gotenberg server timed out at ${url}. ` +
|
|
544
|
+
`Please check that the server is accessible and not overloaded.`);
|
|
545
|
+
}
|
|
546
|
+
else if (error.response) {
|
|
547
|
+
// Ошибка от сервера (HTTP ошибка)
|
|
548
|
+
const statusCode = error.response.status || error.response.statusCode;
|
|
549
|
+
let errorText;
|
|
550
|
+
if (error.response.data) {
|
|
551
|
+
if (Buffer.isBuffer(error.response.data)) {
|
|
552
|
+
errorText = error.response.data.toString('utf-8');
|
|
553
|
+
}
|
|
554
|
+
else if (typeof error.response.data === 'string') {
|
|
555
|
+
errorText = error.response.data;
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
errorText = JSON.stringify(error.response.data);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
errorText = error.message || 'Unknown error';
|
|
563
|
+
}
|
|
564
|
+
throw new n8n_workflow_2.NodeApiError(this.getNode(), {
|
|
565
|
+
message: `Gotenberg API error: ${statusCode}`,
|
|
566
|
+
description: errorText,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// Другие ошибки
|
|
571
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error connecting to Gotenberg server: ${error.message || String(error)}. ` +
|
|
572
|
+
`URL: ${url}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
457
575
|
// Проверяем статус ответа
|
|
458
576
|
if (response.statusCode && response.statusCode >= 400) {
|
|
459
577
|
let errorText;
|