n8n-nodes-pinterest 0.1.7 → 0.2.1
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
|
@@ -6,7 +6,7 @@ Supports:
|
|
|
6
6
|
|
|
7
7
|
- Cookie-based authentication (session cookie)
|
|
8
8
|
- List boards (current user)
|
|
9
|
-
- Create a Pin by
|
|
9
|
+
- Create a Pin by letting Pinterest scrape a public image URL
|
|
10
10
|
|
|
11
11
|
Notes:
|
|
12
12
|
|
|
@@ -37,16 +37,14 @@ Creates a pin using cookie-based authentication.
|
|
|
37
37
|
Inputs:
|
|
38
38
|
|
|
39
39
|
- Board (select from available boards)
|
|
40
|
-
- Image
|
|
41
|
-
|
|
42
|
-
- **Scraped Image URL**: supply a direct image URL. The node calls the same `/pin/find/?url=...` flow as wp-pinterest-automatic and creates the pin with `method="scraped"`—no binary data required.
|
|
43
|
-
- Optional: Title, Description, Link, Alt Text
|
|
40
|
+
- Image URL: Direct URL of the image for Pinterest to scrape
|
|
41
|
+
- Optional: Title, Description, Link
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
The node calls the `/pin/find/?url=...` flow and creates the pin with `method="scraped"`. Works for images that are accessible over HTTPS.
|
|
46
44
|
|
|
47
45
|
Additional behavior:
|
|
48
46
|
|
|
49
|
-
-
|
|
47
|
+
- If Pinterest responds with errors such as "This site doesn't allow you to save Pins" or "Please make sure the URL is correct", the node automatically retries without the link.
|
|
50
48
|
|
|
51
49
|
## Disclaimer
|
|
52
50
|
|
|
@@ -6,18 +6,15 @@ 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
|
-
|
|
9
|
+
function buildCommonHeaders(csrf) {
|
|
10
|
+
return {
|
|
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',
|
|
14
17
|
};
|
|
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;
|
|
21
18
|
}
|
|
22
19
|
function buildCookieHeader(sess, csrf) {
|
|
23
20
|
return `_pinterest_sess=${sess}; csrftoken=${csrf};`;
|
|
@@ -111,84 +108,40 @@ class PinterestCookie {
|
|
|
111
108
|
required: true,
|
|
112
109
|
description: 'Board to pin to',
|
|
113
110
|
},
|
|
114
|
-
{
|
|
115
|
-
displayName: 'Image Source',
|
|
116
|
-
name: 'imageSource',
|
|
117
|
-
type: 'options',
|
|
118
|
-
options: [
|
|
119
|
-
{ name: 'Upload Binary', value: 'uploadBinary' },
|
|
120
|
-
{ name: 'Scraped Image URL', value: 'scrapedUrl' },
|
|
121
|
-
],
|
|
122
|
-
default: 'uploadBinary',
|
|
123
|
-
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
124
|
-
description: 'Choose whether to upload a binary image or let Pinterest scrape an external URL',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
displayName: 'Binary Property',
|
|
128
|
-
name: 'binaryProperty',
|
|
129
|
-
type: 'string',
|
|
130
|
-
default: 'data',
|
|
131
|
-
required: true,
|
|
132
|
-
displayOptions: { show: { resource: ['pin'], operation: ['create'], imageSource: ['uploadBinary'] } },
|
|
133
|
-
description: 'Name of the binary property containing the image',
|
|
134
|
-
},
|
|
135
111
|
{
|
|
136
112
|
displayName: 'Image URL',
|
|
137
113
|
name: 'imageUrl',
|
|
138
114
|
type: 'string',
|
|
139
115
|
default: '',
|
|
140
116
|
required: true,
|
|
141
|
-
displayOptions: { show: { resource: ['pin'], operation: ['create']
|
|
142
|
-
description: 'Direct URL of the image
|
|
117
|
+
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
118
|
+
description: 'Direct URL of the image for Pinterest to scrape',
|
|
143
119
|
},
|
|
144
120
|
{
|
|
145
121
|
displayName: 'Title',
|
|
146
122
|
name: 'title',
|
|
147
123
|
type: 'string',
|
|
148
124
|
default: '',
|
|
125
|
+
typeOptions: { maxLength: 100 },
|
|
149
126
|
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
127
|
+
description: 'Maximum 100 characters',
|
|
150
128
|
},
|
|
151
129
|
{
|
|
152
130
|
displayName: 'Description',
|
|
153
131
|
name: 'description',
|
|
154
132
|
type: 'string',
|
|
155
133
|
default: '',
|
|
134
|
+
typeOptions: { maxLength: 800 },
|
|
156
135
|
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
136
|
+
description: 'Maximum 800 characters',
|
|
157
137
|
},
|
|
158
138
|
{
|
|
159
|
-
displayName: '
|
|
160
|
-
name: '
|
|
161
|
-
type: '
|
|
162
|
-
|
|
163
|
-
default: {},
|
|
164
|
-
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
165
|
-
options: [
|
|
166
|
-
{ displayName: 'Alt Text', name: 'alt_text', type: 'string', default: '' },
|
|
167
|
-
{ displayName: 'Link', name: 'link', type: 'string', default: '' },
|
|
168
|
-
{
|
|
169
|
-
displayName: 'Attach Link',
|
|
170
|
-
name: 'attachLink',
|
|
171
|
-
type: 'boolean',
|
|
172
|
-
default: true,
|
|
173
|
-
description: 'If disabled or link is blocked, pin will be created without a destination link',
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
displayName: 'Strict Plugin Mode',
|
|
179
|
-
name: 'strictPluginMode',
|
|
180
|
-
type: 'boolean',
|
|
181
|
-
default: false,
|
|
182
|
-
description: 'Force exact request shape and headers used by the referenced plugin. Disables retries/fallbacks for 1:1 parity while debugging.',
|
|
183
|
-
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
displayName: 'Debug: Return Raw Response',
|
|
187
|
-
name: 'debugRaw',
|
|
188
|
-
type: 'boolean',
|
|
189
|
-
default: false,
|
|
190
|
-
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.',
|
|
139
|
+
displayName: 'Link',
|
|
140
|
+
name: 'link',
|
|
141
|
+
type: 'string',
|
|
142
|
+
default: '',
|
|
191
143
|
displayOptions: { show: { resource: ['pin'], operation: ['create'] } },
|
|
144
|
+
description: 'Destination URL for the pin',
|
|
192
145
|
},
|
|
193
146
|
// Board ops
|
|
194
147
|
{
|
|
@@ -279,7 +232,7 @@ class PinterestCookie {
|
|
|
279
232
|
};
|
|
280
233
|
}
|
|
281
234
|
async execute() {
|
|
282
|
-
var _a, _b
|
|
235
|
+
var _a, _b;
|
|
283
236
|
const items = this.getInputData();
|
|
284
237
|
const returnData = [];
|
|
285
238
|
const creds = (await this.getCredentials('pinterestCookieApi'));
|
|
@@ -343,13 +296,10 @@ class PinterestCookie {
|
|
|
343
296
|
const operation = this.getNodeParameter('operation', i);
|
|
344
297
|
if (operation === 'create') {
|
|
345
298
|
const boardId = this.getNodeParameter('boardId', i);
|
|
346
|
-
const
|
|
299
|
+
const imageUrl = this.getNodeParameter('imageUrl', i);
|
|
347
300
|
const title = this.getNodeParameter('title', i, '');
|
|
348
301
|
const description = this.getNodeParameter('description', i, '');
|
|
349
|
-
const
|
|
350
|
-
const strictPluginMode = this.getNodeParameter('strictPluginMode', i, false);
|
|
351
|
-
const debugRaw = this.getNodeParameter('debugRaw', i, false);
|
|
352
|
-
const attachLink = (additionalFields.attachLink !== undefined) ? Boolean(additionalFields.attachLink) : true;
|
|
302
|
+
const link = this.getNodeParameter('link', i, '');
|
|
353
303
|
const attemptCreate = async (payload, createOptions) => {
|
|
354
304
|
var _a, _b, _c, _d, _e;
|
|
355
305
|
const form = {
|
|
@@ -363,16 +313,14 @@ class PinterestCookie {
|
|
|
363
313
|
method: 'POST',
|
|
364
314
|
uri: `${base}/resource/PinResource/create/`,
|
|
365
315
|
form,
|
|
366
|
-
headers:
|
|
367
|
-
|
|
368
|
-
:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
375
|
-
},
|
|
316
|
+
headers: {
|
|
317
|
+
...baseHeaders,
|
|
318
|
+
Cookie: cookieHeader,
|
|
319
|
+
Referer: ((_b = createOptions === null || createOptions === void 0 ? void 0 : createOptions.sourceUrl) === null || _b === void 0 ? void 0 : _b.startsWith('/pin/find')) ? `${base}/pin/find/` : `${base}/pin-builder/`,
|
|
320
|
+
Origin: base,
|
|
321
|
+
'X-Pinterest-Source-Url': (_c = createOptions === null || createOptions === void 0 ? void 0 : createOptions.sourceUrl) !== null && _c !== void 0 ? _c : '/pin-builder/',
|
|
322
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
323
|
+
},
|
|
376
324
|
proxy: proxy || undefined,
|
|
377
325
|
simple: false,
|
|
378
326
|
resolveWithFullResponse: true,
|
|
@@ -398,171 +346,47 @@ class PinterestCookie {
|
|
|
398
346
|
if (lastResult.ok) {
|
|
399
347
|
return lastResult;
|
|
400
348
|
}
|
|
401
|
-
if (
|
|
349
|
+
if (isLinkError(lastResult.msg)) {
|
|
402
350
|
continue;
|
|
403
351
|
}
|
|
404
352
|
}
|
|
405
353
|
return lastResult;
|
|
406
354
|
};
|
|
407
355
|
let result = { ok: false };
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
if (!imageUrl) {
|
|
411
|
-
throw new Error('Image URL is required when using the scraped method.');
|
|
412
|
-
}
|
|
413
|
-
const sanitizedTitle = sanitizePinterestText(title, 100);
|
|
414
|
-
const sanitizedDesc = sanitizePinterestText(description, 500);
|
|
415
|
-
const linkValue = attachLink ? String((_c = additionalFields.link) !== null && _c !== void 0 ? _c : '') : '';
|
|
416
|
-
const scrapedPayload = {
|
|
417
|
-
options: {
|
|
418
|
-
method: 'scraped',
|
|
419
|
-
title: sanitizedTitle,
|
|
420
|
-
description: sanitizedDesc,
|
|
421
|
-
link: linkValue,
|
|
422
|
-
image_url: imageUrl,
|
|
423
|
-
share_facebook: false,
|
|
424
|
-
board_id: boardId,
|
|
425
|
-
scrape_metric: { source: 'www_url_scrape' },
|
|
426
|
-
},
|
|
427
|
-
context: {},
|
|
428
|
-
};
|
|
429
|
-
const scrapedOptions = {
|
|
430
|
-
sourceUrl: `/pin/find/?url=${doubleEncodeUrl(imageUrl)}`,
|
|
431
|
-
modulePath: 'App()%3EModalManager%3EModal%3EPinCreate%3EBoardPicker%3ESelectList(view_type%3DpinCreate%2C+selected_section_index%3Dundefined%2C+selected_item_index%3Dundefined%2C+highlight_matched_text%3Dtrue%2C+suppress_hover_events%3Dundefined%2C+scroll_selected_item_into_view%3Dtrue%2C+select_first_item_after_update%3Dfalse%2C+item_module%3D%5Bobject+Object%5D)',
|
|
432
|
-
};
|
|
433
|
-
const strategies = [
|
|
434
|
-
{ payload: scrapedPayload, options: scrapedOptions },
|
|
435
|
-
];
|
|
436
|
-
if (!strictPluginMode && linkValue) {
|
|
437
|
-
const withoutLink = JSON.parse(JSON.stringify(scrapedPayload));
|
|
438
|
-
if (withoutLink.options) {
|
|
439
|
-
withoutLink.options.link = '';
|
|
440
|
-
}
|
|
441
|
-
strategies.push({ payload: withoutLink, options: scrapedOptions });
|
|
442
|
-
}
|
|
443
|
-
result = await runStrategies(strategies);
|
|
356
|
+
if (!imageUrl) {
|
|
357
|
+
throw new Error('Image URL is required.');
|
|
444
358
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const data = res.data || {};
|
|
472
|
-
const uploadUrl = String(data.upload_url || '');
|
|
473
|
-
const uploadParams = data.upload_parameters || {};
|
|
474
|
-
if (!uploadUrl)
|
|
475
|
-
throw new Error('Failed to get upload URL');
|
|
476
|
-
const buffer = Buffer.from(binary.data, 'base64');
|
|
477
|
-
const contentType = binary.mimeType || 'image/jpeg';
|
|
478
|
-
const formData = {};
|
|
479
|
-
for (const [k, v] of Object.entries(uploadParams))
|
|
480
|
-
formData[k] = String(v);
|
|
481
|
-
formData['file'] = {
|
|
482
|
-
value: buffer,
|
|
483
|
-
options: {
|
|
484
|
-
filename: 'blob',
|
|
485
|
-
contentType,
|
|
486
|
-
},
|
|
487
|
-
};
|
|
488
|
-
const uploadResp = (await this.helpers.request({
|
|
489
|
-
method: 'POST',
|
|
490
|
-
uri: uploadUrl,
|
|
491
|
-
headers: {
|
|
492
|
-
Accept: '*/*',
|
|
493
|
-
'Accept-Encoding': 'gzip',
|
|
494
|
-
'User-Agent': strictPluginMode
|
|
495
|
-
? 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
|
|
496
|
-
: baseHeaders['User-Agent'],
|
|
497
|
-
Origin: strictPluginMode ? 'https://www.pinterest.com' : base,
|
|
498
|
-
Referer: strictPluginMode ? 'https://www.pinterest.com' : `${base}/pin-builder/`,
|
|
499
|
-
...(strictPluginMode
|
|
500
|
-
? {
|
|
501
|
-
'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"',
|
|
502
|
-
'sec-ch-ua-mobile': '?0',
|
|
503
|
-
'sec-ch-ua-full': '?1',
|
|
504
|
-
'sec-ch-ua-platform': '"Windows"',
|
|
505
|
-
'Sec-Fetch-Dest': 'empty',
|
|
506
|
-
'Sec-Fetch-Mode': 'cors',
|
|
507
|
-
'Sec-Fetch-Site': 'same-origin',
|
|
508
|
-
Connection: 'keep-alive',
|
|
509
|
-
}
|
|
510
|
-
: {}),
|
|
511
|
-
},
|
|
512
|
-
formData,
|
|
513
|
-
proxy: proxy || undefined,
|
|
514
|
-
resolveWithFullResponse: true,
|
|
515
|
-
simple: false,
|
|
516
|
-
}));
|
|
517
|
-
const etagRaw = uploadResp.headers['etag'] || uploadResp.headers['ETag'] || '';
|
|
518
|
-
const etag = String(etagRaw).replace(/"/g, '');
|
|
519
|
-
if (!etag)
|
|
520
|
-
throw new Error('Upload failed: missing ETag');
|
|
521
|
-
const imageUrl = `https://i.pinimg.com/736x/${etag[0]}${etag[1]}/${etag[2]}${etag[3]}/${etag[4]}${etag[5]}/${etag}.jpg`;
|
|
522
|
-
const sendData = {
|
|
523
|
-
options: {
|
|
524
|
-
board_id: String(boardId),
|
|
525
|
-
field_set_key: 'create_success',
|
|
526
|
-
skip_pin_create_log: true,
|
|
527
|
-
description: String(description || ''),
|
|
528
|
-
alt_text: String(additionalFields.alt_text || ''),
|
|
529
|
-
link: attachLink && additionalFields.link ? String(additionalFields.link) : '',
|
|
530
|
-
title: String(title || ''),
|
|
531
|
-
image_url: String(imageUrl),
|
|
532
|
-
method: 'uploaded',
|
|
533
|
-
upload_metric: { source: 'pinner_upload_standalone' },
|
|
534
|
-
user_mention_tags: [],
|
|
535
|
-
no_fetch_context_on_resource: false,
|
|
536
|
-
},
|
|
537
|
-
context: {},
|
|
538
|
-
};
|
|
539
|
-
const strategies = [
|
|
540
|
-
{ payload: JSON.parse(JSON.stringify(sendData)) },
|
|
541
|
-
];
|
|
542
|
-
if (!strictPluginMode) {
|
|
543
|
-
const withoutLink = JSON.parse(JSON.stringify(sendData));
|
|
544
|
-
if (withoutLink.options)
|
|
545
|
-
delete withoutLink.options.link;
|
|
546
|
-
strategies.push({ payload: withoutLink });
|
|
547
|
-
strategies.push({
|
|
548
|
-
payload: {
|
|
549
|
-
options: {
|
|
550
|
-
board_id: boardId,
|
|
551
|
-
field_set_key: 'create_success',
|
|
552
|
-
image_url: imageUrl,
|
|
553
|
-
method: 'uploaded',
|
|
554
|
-
},
|
|
555
|
-
context: {},
|
|
556
|
-
},
|
|
557
|
-
});
|
|
359
|
+
const sanitizedTitle = sanitizePinterestText(title, 100);
|
|
360
|
+
const sanitizedDesc = sanitizePinterestText(description, 500);
|
|
361
|
+
const scrapedPayload = {
|
|
362
|
+
options: {
|
|
363
|
+
method: 'scraped',
|
|
364
|
+
title: sanitizedTitle,
|
|
365
|
+
description: sanitizedDesc,
|
|
366
|
+
link: link,
|
|
367
|
+
image_url: imageUrl,
|
|
368
|
+
share_facebook: false,
|
|
369
|
+
board_id: boardId,
|
|
370
|
+
scrape_metric: { source: 'www_url_scrape' },
|
|
371
|
+
},
|
|
372
|
+
context: {},
|
|
373
|
+
};
|
|
374
|
+
const scrapedOptions = {
|
|
375
|
+
sourceUrl: `/pin/find/?url=${doubleEncodeUrl(imageUrl)}`,
|
|
376
|
+
modulePath: 'App()%3EModalManager%3EModal%3EPinCreate%3EBoardPicker%3ESelectList(view_type%3DpinCreate%2C+selected_section_index%3Dundefined%2C+selected_item_index%3Dundefined%2C+highlight_matched_text%3Dtrue%2C+suppress_hover_events%3Dundefined%2C+scroll_selected_item_into_view%3Dtrue%2C+select_first_item_after_update%3Dfalse%2C+item_module%3D%5Bobject+Object%5D)',
|
|
377
|
+
};
|
|
378
|
+
const strategies = [
|
|
379
|
+
{ payload: scrapedPayload, options: scrapedOptions },
|
|
380
|
+
];
|
|
381
|
+
if (link) {
|
|
382
|
+
const withoutLink = JSON.parse(JSON.stringify(scrapedPayload));
|
|
383
|
+
if (withoutLink.options) {
|
|
384
|
+
withoutLink.options.link = '';
|
|
558
385
|
}
|
|
559
|
-
|
|
386
|
+
strategies.push({ payload: withoutLink, options: scrapedOptions });
|
|
560
387
|
}
|
|
388
|
+
result = await runStrategies(strategies);
|
|
561
389
|
if (!result.ok) {
|
|
562
|
-
if (debugRaw && this.continueOnFail()) {
|
|
563
|
-
returnData.push({ json: { error: result.msg || 'Create failed', response: result.body, request: result.payload } });
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
390
|
throw new Error(result.msg || result.body || 'Create failed');
|
|
567
391
|
}
|
|
568
392
|
returnData.push({ json: { id: result.id, link: `https://www.pinterest.com/pin/${result.id}` } });
|