n8n-nodes-pinterest 0.1.3 → 0.1.5

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.
@@ -6,15 +6,18 @@ function randomCsrf() {
6
6
  const seed = `${Date.now()}${Math.floor(Math.random() * 100000)}`;
7
7
  return Buffer.from(seed).toString('base64');
8
8
  }
9
- function buildCommonHeaders(csrf) {
10
- return {
9
+ function buildCommonHeaders(csrf, strict = false) {
10
+ const base = {
11
11
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0',
12
12
  'x-pinterest-pws-handler': 'www/[username].js',
13
13
  'x-CSRFToken': csrf,
14
- 'Accept-Language': 'en-US,en;q=0.9',
15
- Accept: 'application/json, text/plain, */*',
16
- 'X-Requested-With': 'XMLHttpRequest',
17
14
  };
15
+ if (!strict) {
16
+ base['Accept-Language'] = 'en-US,en;q=0.9';
17
+ base['Accept'] = 'application/json, text/plain, */*';
18
+ base['X-Requested-With'] = 'XMLHttpRequest';
19
+ }
20
+ return base;
18
21
  }
19
22
  function buildCookieHeader(sess, csrf) {
20
23
  return `_pinterest_sess=${sess}; csrftoken=${csrf};`;
@@ -135,6 +138,22 @@ class PinterestCookie {
135
138
  },
136
139
  ],
137
140
  },
141
+ {
142
+ displayName: 'Strict Plugin Mode',
143
+ name: 'strictPluginMode',
144
+ type: 'boolean',
145
+ default: false,
146
+ description: 'Force exact request shape and headers used by the referenced plugin. Disables retries/fallbacks for 1:1 parity while debugging.',
147
+ displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
148
+ },
149
+ {
150
+ displayName: 'Debug: Return Raw Response',
151
+ name: 'debugRaw',
152
+ type: 'boolean',
153
+ default: false,
154
+ description: 'When enabled, include the full server response body and request payload in the output for troubleshooting. The node will not throw if Continue On Fail is enabled.',
155
+ displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
156
+ },
138
157
  // Board ops
139
158
  {
140
159
  displayName: 'Operation',
@@ -292,6 +311,8 @@ class PinterestCookie {
292
311
  const title = this.getNodeParameter('title', i, '');
293
312
  const description = this.getNodeParameter('description', i, '');
294
313
  const additionalFields = this.getNodeParameter('additionalFields', i, {});
314
+ const debugRaw = this.getNodeParameter('debugRaw', i, false);
315
+ const strictPluginMode = this.getNodeParameter('strictPluginMode', i, false);
295
316
  const binary = (_c = items[i].binary) === null || _c === void 0 ? void 0 : _c[binaryProperty];
296
317
  if (!binary)
297
318
  throw new Error(`Binary property "${binaryProperty}" is missing on item ${i}`);
@@ -303,7 +324,16 @@ class PinterestCookie {
303
324
  source_url: '/pin-builder/',
304
325
  data: '{"options":{"type":"pinimage"},"context":{}}',
305
326
  },
306
- headers: { ...baseHeaders, Cookie: cookieHeader, Referer: `${base}/pin-builder/`, Origin: base },
327
+ headers: strictPluginMode
328
+ ? { ...buildCommonHeaders(csrf, true), Cookie: cookieHeader }
329
+ : {
330
+ ...baseHeaders,
331
+ Cookie: cookieHeader,
332
+ Referer: `${base}/pin-builder/`,
333
+ Origin: base,
334
+ 'X-Pinterest-Source-Url': '/pin-builder/',
335
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
336
+ },
307
337
  proxy: proxy || undefined,
308
338
  }));
309
339
  const vip = JSON.parse(vipResp);
@@ -332,9 +362,23 @@ class PinterestCookie {
332
362
  headers: {
333
363
  Accept: '*/*',
334
364
  'Accept-Encoding': 'gzip',
335
- 'User-Agent': baseHeaders['User-Agent'],
336
- Origin: base,
337
- Referer: `${base}/pin-builder/`,
365
+ 'User-Agent': strictPluginMode
366
+ ? 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
367
+ : baseHeaders['User-Agent'],
368
+ Origin: strictPluginMode ? 'https://www.pinterest.com' : base,
369
+ Referer: strictPluginMode ? 'https://www.pinterest.com' : `${base}/pin-builder/`,
370
+ ...(strictPluginMode
371
+ ? {
372
+ 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"',
373
+ 'sec-ch-ua-mobile': '?0',
374
+ 'sec-ch-ua-full': '?1',
375
+ 'sec-ch-ua-platform': '"Windows"',
376
+ 'Sec-Fetch-Dest': 'empty',
377
+ 'Sec-Fetch-Mode': 'cors',
378
+ 'Sec-Fetch-Site': 'same-origin',
379
+ Connection: 'keep-alive',
380
+ }
381
+ : {}),
338
382
  },
339
383
  formData,
340
384
  proxy: proxy || undefined,
@@ -350,19 +394,24 @@ class PinterestCookie {
350
394
  const imageUrl = `https://i.pinimg.com/736x/${etag[0]}${etag[1]}/${etag[2]}${etag[3]}/${etag[4]}${etag[5]}/${etag}.jpg`;
351
395
  // Step 3: create pin
352
396
  const attachLink = (additionalFields.attachLink !== undefined) ? Boolean(additionalFields.attachLink) : true;
397
+ // Build payload
353
398
  const sendData = {
354
399
  options: {
355
- board_id: boardId,
400
+ board_id: String(boardId),
356
401
  field_set_key: 'create_success',
357
402
  skip_pin_create_log: true,
358
- ...(description ? { description } : {}),
359
- ...(additionalFields.alt_text ? { alt_text: additionalFields.alt_text } : {}),
360
- ...(attachLink && additionalFields.link ? { link: additionalFields.link } : {}),
361
- ...(title ? { title } : {}),
362
- image_url: imageUrl,
403
+ description: String(description || ''),
404
+ alt_text: String(additionalFields.alt_text || ''),
405
+ // Strict mode: always include link key
406
+ link: String(additionalFields.link || ''),
407
+ // Non-strict: allow disabling link entirely
408
+ ...(strictPluginMode ? {} : (!attachLink ? { link: '' } : {})),
409
+ title: String(title || ''),
410
+ image_url: String(imageUrl),
363
411
  method: 'uploaded',
364
412
  upload_metric: { source: 'pinner_upload_standalone' },
365
- // omit user_mention_tags/no_fetch_context for stricter compatibility
413
+ user_mention_tags: [],
414
+ no_fetch_context_on_resource: false,
366
415
  },
367
416
  context: {},
368
417
  };
@@ -375,7 +424,16 @@ class PinterestCookie {
375
424
  source_url: '/pin-builder/',
376
425
  data: JSON.stringify(payload),
377
426
  },
378
- headers: { ...baseHeaders, Cookie: cookieHeader, Referer: `${base}/pin-builder/`, Origin: base },
427
+ headers: strictPluginMode
428
+ ? { ...buildCommonHeaders(csrf, true), Cookie: cookieHeader }
429
+ : {
430
+ ...baseHeaders,
431
+ Cookie: cookieHeader,
432
+ Referer: `${base}/pin-builder/`,
433
+ Origin: base,
434
+ 'X-Pinterest-Source-Url': '/pin-builder/',
435
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
436
+ },
379
437
  proxy: proxy || undefined,
380
438
  // Get body even on 4xx
381
439
  simple: false,
@@ -392,39 +450,50 @@ class PinterestCookie {
392
450
  const id = String(pinData.id || '');
393
451
  const msg = createdRes.message || ((_a = createdRes.error) === null || _a === void 0 ? void 0 : _a.message) || '';
394
452
  const msgDetail = ((_b = createdRes.error) === null || _b === void 0 ? void 0 : _b.message_detail) || '';
395
- return { ok: Boolean(id), id, msg: String(msg || msgDetail || ''), body };
453
+ return { ok: Boolean(id), id, msg: String(msg || msgDetail || ''), body, payload };
396
454
  };
397
- // Try strategies progressively to avoid generic "Invalid parameters"
398
- const strategies = [];
399
- // 1) As built
400
- strategies.push(JSON.parse(JSON.stringify(sendData)));
401
- // 2) Without link
402
- const s2 = JSON.parse(JSON.stringify(sendData));
403
- if (s2.options)
404
- delete s2.options.link;
405
- strategies.push(s2);
406
- // 3) Minimal fields only
407
- strategies.push({
408
- options: {
409
- board_id: boardId,
410
- field_set_key: 'create_success',
411
- image_url: imageUrl,
412
- method: 'uploaded',
413
- },
414
- context: {},
415
- });
416
455
  let result = { ok: false };
417
- for (const strat of strategies) {
418
- result = await attemptCreate(strat);
419
- if (result.ok)
420
- break;
421
- // If explicit site-block message, skip to minimal next
422
- if (/doesn\'t allow you to save Pins|does not allow you to save Pins/i.test(result.msg || ''))
423
- continue;
456
+ if (strictPluginMode) {
457
+ result = await attemptCreate(sendData);
458
+ if (!result.ok) {
459
+ if (debugRaw && this.continueOnFail()) {
460
+ returnData.push({ json: { error: result.msg || 'Create failed', response: result.body, request: sendData } });
461
+ continue;
462
+ }
463
+ throw new Error(result.msg || result.body || 'Create failed');
464
+ }
424
465
  }
425
- if (!result.ok) {
426
- // surface full server message for troubleshooting
427
- throw new Error(result.msg || result.body || 'Create failed');
466
+ else {
467
+ // Try strategies progressively to avoid generic errors
468
+ const strategies = [];
469
+ strategies.push(JSON.parse(JSON.stringify(sendData)));
470
+ const s2 = JSON.parse(JSON.stringify(sendData));
471
+ if (s2.options)
472
+ delete s2.options.link;
473
+ strategies.push(s2);
474
+ strategies.push({
475
+ options: {
476
+ board_id: boardId,
477
+ field_set_key: 'create_success',
478
+ image_url: imageUrl,
479
+ method: 'uploaded',
480
+ },
481
+ context: {},
482
+ });
483
+ for (const strat of strategies) {
484
+ result = await attemptCreate(strat);
485
+ if (result.ok)
486
+ break;
487
+ if (/doesn\'t allow you to save Pins|does not allow you to save Pins/i.test(result.msg || ''))
488
+ continue;
489
+ }
490
+ if (!result.ok) {
491
+ if (debugRaw && this.continueOnFail()) {
492
+ returnData.push({ json: { error: result.msg || 'Create failed', response: result.body, request: strategies[strategies.length - 1] } });
493
+ continue;
494
+ }
495
+ throw new Error(result.msg || result.body || 'Create failed');
496
+ }
428
497
  }
429
498
  returnData.push({ json: { id: result.id, link: `https://www.pinterest.com/pin/${result.id}` } });
430
499
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-pinterest",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "n8n community nodes for Pinterest v5 API (list boards, create pins)",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",