n8n-nodes-script-runner 1.16.1 → 1.16.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 CHANGED
@@ -19,10 +19,10 @@ A custom n8n node package that includes:
19
19
  - 📄 Convert HTML strings to PDF files
20
20
  - 🌐 Convert web pages (URLs) to PDF
21
21
  - ⚙️ Configurable page format (A4, A3, Letter, Legal, Tabloid)
22
- - 🎨 Support for custom margins and orientations
22
+ - 🎨 Support for custom orientations
23
23
  - 💾 Output as binary data or base64 string
24
- - 🖼️ Print background graphics support
25
- - Wait for network resources to load before converting
24
+ - 🚀 Lightweight - no browser required (uses PDFKit)
25
+ - 📝 Extracts headings and paragraphs from HTML
26
26
 
27
27
  ## Installation
28
28
 
@@ -1,41 +1,14 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
6
  exports.HtmlToPdfConverter = void 0;
37
7
  const n8n_workflow_1 = require("n8n-workflow");
38
- const puppeteer = __importStar(require("puppeteer"));
8
+ const pdfkit_1 = __importDefault(require("pdfkit"));
9
+ const html_to_text_1 = require("html-to-text");
10
+ const jsdom_1 = require("jsdom");
11
+ const axios_1 = __importDefault(require("axios"));
39
12
  class HtmlToPdfConverter {
40
13
  constructor() {
41
14
  this.description = {
@@ -174,111 +147,121 @@ class HtmlToPdfConverter {
174
147
  async execute() {
175
148
  const items = this.getInputData();
176
149
  const returnData = [];
177
- let browser = null;
178
- try {
179
- // Launch browser once for all items
180
- browser = await puppeteer.launch({
181
- headless: true,
182
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
183
- });
184
- for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
185
- try {
186
- const htmlSource = this.getNodeParameter('htmlSource', itemIndex);
187
- const outputFormat = this.getNodeParameter('outputFormat', itemIndex);
188
- const fileName = this.getNodeParameter('fileName', itemIndex);
189
- const pageFormat = this.getNodeParameter('pageFormat', itemIndex);
190
- const marginJson = this.getNodeParameter('margin', itemIndex);
191
- const printBackground = this.getNodeParameter('printBackground', itemIndex);
192
- const landscape = this.getNodeParameter('landscape', itemIndex);
193
- const waitForNetworkIdle = this.getNodeParameter('waitForNetworkIdle', itemIndex);
194
- let margin;
195
- try {
196
- margin = JSON.parse(marginJson);
150
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
151
+ try {
152
+ const htmlSource = this.getNodeParameter('htmlSource', itemIndex);
153
+ const outputFormat = this.getNodeParameter('outputFormat', itemIndex);
154
+ const fileName = this.getNodeParameter('fileName', itemIndex);
155
+ const pageFormat = this.getNodeParameter('pageFormat', itemIndex);
156
+ const landscape = this.getNodeParameter('landscape', itemIndex);
157
+ let htmlContent = '';
158
+ if (htmlSource === 'url') {
159
+ const url = this.getNodeParameter('url', itemIndex);
160
+ if (!url) {
161
+ throw new Error('URL is required when HTML Source is set to URL');
197
162
  }
198
- catch (e) {
199
- throw new Error('Invalid margin JSON format');
163
+ const response = await axios_1.default.get(url);
164
+ htmlContent = response.data;
165
+ }
166
+ else {
167
+ htmlContent = this.getNodeParameter('htmlContent', itemIndex);
168
+ if (!htmlContent) {
169
+ throw new Error('HTML Content is required when HTML Source is set to HTML String');
200
170
  }
201
- const page = await browser.newPage();
202
- try {
203
- if (htmlSource === 'url') {
204
- const url = this.getNodeParameter('url', itemIndex);
205
- if (!url) {
206
- throw new Error('URL is required when HTML Source is set to URL');
207
- }
208
- await page.goto(url, {
209
- waitUntil: waitForNetworkIdle ? 'networkidle0' : 'load',
210
- });
211
- }
212
- else {
213
- const htmlContent = this.getNodeParameter('htmlContent', itemIndex);
214
- if (!htmlContent) {
215
- throw new Error('HTML Content is required when HTML Source is set to HTML String');
216
- }
217
- await page.setContent(htmlContent, {
218
- waitUntil: waitForNetworkIdle ? 'networkidle0' : 'load',
219
- });
220
- }
221
- // Generate PDF
222
- const pdfBuffer = await page.pdf({
223
- format: pageFormat,
224
- margin,
225
- printBackground,
226
- landscape,
171
+ }
172
+ // Parse HTML with JSDOM
173
+ const dom = new jsdom_1.JSDOM(htmlContent);
174
+ const document = dom.window.document;
175
+ // Extract text content
176
+ const textContent = (0, html_to_text_1.convert)(htmlContent, {
177
+ wordwrap: 130,
178
+ preserveNewlines: true,
179
+ });
180
+ // Create PDF
181
+ const pdfBuffer = await new Promise((resolve, reject) => {
182
+ const chunks = [];
183
+ // Define page sizes
184
+ const pageSizes = {
185
+ 'A4': [595.28, 841.89],
186
+ 'A3': [841.89, 1190.55],
187
+ 'Letter': [612, 792],
188
+ 'Legal': [612, 1008],
189
+ 'Tabloid': [792, 1224],
190
+ };
191
+ const size = pageSizes[pageFormat] || pageSizes['A4'];
192
+ const doc = new pdfkit_1.default({
193
+ size: landscape ? [size[1], size[0]] : size,
194
+ margin: 50,
195
+ });
196
+ doc.on('data', (chunk) => chunks.push(chunk));
197
+ doc.on('end', () => resolve(Buffer.concat(chunks)));
198
+ doc.on('error', reject);
199
+ // Add title from HTML
200
+ const title = document.querySelector('title')?.textContent || 'Document';
201
+ doc.fontSize(20).text(title, { align: 'center' });
202
+ doc.moveDown();
203
+ // Add headings and paragraphs
204
+ const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
205
+ const paragraphs = document.querySelectorAll('p');
206
+ if (headings.length > 0 || paragraphs.length > 0) {
207
+ headings.forEach((heading) => {
208
+ const level = parseInt(heading.tagName.substring(1));
209
+ const fontSize = 24 - (level * 2);
210
+ doc.font('Helvetica-Bold').fontSize(fontSize).text(heading.textContent || '');
211
+ doc.font('Helvetica');
212
+ doc.moveDown(0.5);
213
+ });
214
+ paragraphs.forEach((para) => {
215
+ doc.fontSize(12).text(para.textContent || '');
216
+ doc.moveDown();
227
217
  });
228
- await page.close();
229
- if (outputFormat === 'binary') {
230
- // Return as binary data
231
- const binaryData = await this.helpers.prepareBinaryData(pdfBuffer, fileName, 'application/pdf');
232
- returnData.push({
233
- json: {
234
- success: true,
235
- fileName,
236
- size: pdfBuffer.length,
237
- },
238
- binary: {
239
- data: binaryData,
240
- },
241
- pairedItem: { item: itemIndex },
242
- });
243
- }
244
- else {
245
- // Return as base64 string
246
- const base64String = pdfBuffer.toString('base64');
247
- returnData.push({
248
- json: {
249
- success: true,
250
- fileName,
251
- size: pdfBuffer.length,
252
- pdfData: base64String,
253
- },
254
- pairedItem: { item: itemIndex },
255
- });
256
- }
257
218
  }
258
- catch (pageError) {
259
- await page.close();
260
- throw pageError;
219
+ else {
220
+ // Fallback to plain text conversion
221
+ doc.fontSize(12).text(textContent);
261
222
  }
223
+ doc.end();
224
+ });
225
+ if (outputFormat === 'binary') {
226
+ const binaryData = await this.helpers.prepareBinaryData(pdfBuffer, fileName, 'application/pdf');
227
+ returnData.push({
228
+ json: {
229
+ success: true,
230
+ fileName,
231
+ size: pdfBuffer.length,
232
+ },
233
+ binary: {
234
+ data: binaryData,
235
+ },
236
+ pairedItem: { item: itemIndex },
237
+ });
262
238
  }
263
- catch (err) {
264
- const error = err;
265
- if (this.continueOnFail()) {
266
- returnData.push({
267
- json: {
268
- success: false,
269
- error: error.message || 'PDF generation failed',
270
- },
271
- pairedItem: { item: itemIndex },
272
- });
273
- continue;
274
- }
275
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
239
+ else {
240
+ const base64String = pdfBuffer.toString('base64');
241
+ returnData.push({
242
+ json: {
243
+ success: true,
244
+ fileName,
245
+ size: pdfBuffer.length,
246
+ pdfData: base64String,
247
+ },
248
+ pairedItem: { item: itemIndex },
249
+ });
276
250
  }
277
251
  }
278
- }
279
- finally {
280
- if (browser) {
281
- await browser.close();
252
+ catch (err) {
253
+ const error = err;
254
+ if (this.continueOnFail()) {
255
+ returnData.push({
256
+ json: {
257
+ success: false,
258
+ error: error.message || 'PDF generation failed',
259
+ },
260
+ pairedItem: { item: itemIndex },
261
+ });
262
+ continue;
263
+ }
264
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex });
282
265
  }
283
266
  }
284
267
  return [returnData];
@@ -9,7 +9,7 @@ const axios_1 = __importDefault(require("axios"));
9
9
  function randomDelayMs(minSeconds = 45, maxSeconds = 90) {
10
10
  return Math.floor(Math.random() * (maxSeconds - minSeconds + 1) + minSeconds) * 1000;
11
11
  }
12
- async function fetchLinkedIn(accountId, url, totalLimit, apiKey, minDelaySec, maxDelaySec, startCursor = null, collected = [], seenIds = new Set(), consecutiveEmptyPages = 0, pageCount = 0) {
12
+ async function fetchLinkedIn(DSN, accountId, url, totalLimit, apiKey, minDelaySec, maxDelaySec, startCursor = null, collected = [], seenIds = new Set(), consecutiveEmptyPages = 0, pageCount = 0) {
13
13
  const MAX_PAGES = 25;
14
14
  const MAX_CONSECUTIVE_EMPTY = 1;
15
15
  const PAGE_SIZE = 100;
@@ -34,7 +34,7 @@ async function fetchLinkedIn(accountId, url, totalLimit, apiKey, minDelaySec, ma
34
34
  });
35
35
  const config = {
36
36
  method: 'POST',
37
- url: `https://api15.unipile.com:14554/api/v1/linkedin/search?${queryParams.toString()}`,
37
+ url: `https://${DSN}/api/v1/linkedin/search?${queryParams.toString()}`,
38
38
  headers: {
39
39
  'X-API-KEY': apiKey,
40
40
  'Accept': 'application/json',
@@ -129,7 +129,7 @@ async function fetchLinkedIn(accountId, url, totalLimit, apiKey, minDelaySec, ma
129
129
  this.logger.info(`Added ${addedThisPage} new items → total ${collected.length}/${totalLimit}. ` +
130
130
  `Waiting ~${Math.round(delayMs / 1000)} seconds...`);
131
131
  await new Promise(resolve => setTimeout(resolve, delayMs));
132
- return fetchLinkedIn.call(this, accountId, url, totalLimit, apiKey, minDelaySec, maxDelaySec, nextCursor, collected, seenIds, consecutiveEmptyPages, pageCount + 1);
132
+ return fetchLinkedIn.call(this, DSN, accountId, url, totalLimit, apiKey, minDelaySec, maxDelaySec, nextCursor, collected, seenIds, consecutiveEmptyPages, pageCount + 1);
133
133
  }
134
134
  }
135
135
  catch (err) {
@@ -175,6 +175,14 @@ class LinkedInFetcher {
175
175
  inputs: ['main'],
176
176
  outputs: ['main'],
177
177
  properties: [
178
+ {
179
+ displayName: 'DSN',
180
+ name: 'DSN',
181
+ type: 'string',
182
+ default: 'api15.unipile.com:14554',
183
+ placeholder: 'e.g. api15.unipile.com:14554',
184
+ description: 'Custom DNS address for the Unipile API',
185
+ },
178
186
  {
179
187
  displayName: 'Account ID',
180
188
  name: 'accountId',
@@ -238,6 +246,7 @@ class LinkedInFetcher {
238
246
  const returnData = [];
239
247
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
240
248
  try {
249
+ const DSN = this.getNodeParameter('DSN', itemIndex, 'api15.unipile.com:14554');
241
250
  const accountId = this.getNodeParameter('accountId', itemIndex);
242
251
  const url = this.getNodeParameter('url', itemIndex)?.trim();
243
252
  const totalLimit = this.getNodeParameter('total', itemIndex);
@@ -252,7 +261,7 @@ class LinkedInFetcher {
252
261
  }
253
262
  if (startCursor === '' || startCursor === 'null')
254
263
  startCursor = null;
255
- const result = await fetchLinkedIn.call(this, accountId, url, total, apiKey, minDelay, maxDelay, startCursor);
264
+ const result = await fetchLinkedIn.call(this, DSN, accountId, url, total, apiKey, minDelay, maxDelay, startCursor);
256
265
  returnData.push({
257
266
  json: {
258
267
  result: result.items,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-script-runner",
3
- "version": "1.16.1",
3
+ "version": "1.16.2",
4
4
  "description": "Custom n8n nodes for script execution (jsdom, cheerio) and HTTP requests (axios, fetch)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -36,17 +36,20 @@
36
36
  "devDependencies": {
37
37
  "@types/jsdom": "^27.0.0",
38
38
  "@types/node": "^20.10.0",
39
+ "@types/pdfkit": "^0.13.0",
39
40
  "@types/user-agents": "^1.0.4",
40
41
  "gulp": "^4.0.2",
41
42
  "n8n-workflow": "^1.0.0",
42
43
  "typescript": "^5.3.0"
43
44
  },
44
45
  "dependencies": {
46
+ "@types/html-to-text": "^9.0.4",
45
47
  "axios": "^1.6.0",
46
48
  "cheerio": "^1.0.0-rc.12",
49
+ "html-to-text": "^9.0.5",
47
50
  "jsdom": "^23.0.0",
48
- "user-agents": "^1.1.669",
49
- "puppeteer": "^24.0.0"
51
+ "pdfkit": "^0.15.0",
52
+ "user-agents": "^1.1.669"
50
53
  },
51
54
  "author": "",
52
55
  "license": "MIT"