n8n-nodes-pinterest 0.1.0 → 0.1.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
|
@@ -42,6 +42,10 @@ Inputs:
|
|
|
42
42
|
|
|
43
43
|
Binary image comes from the input item's binary property. The node uploads the image to the `upload_url` from VIPResource, derives the final `image_url` from the `ETag`, then creates the Pin via `PinResource/create/`.
|
|
44
44
|
|
|
45
|
+
Additional behavior:
|
|
46
|
+
|
|
47
|
+
- "Attach Link" (default true) inside Additional Fields controls whether the destination link is sent. If Pinterest returns an error like "This site doesn't allow you to save Pins.", the node automatically retries without the link.
|
|
48
|
+
|
|
45
49
|
## Disclaimer
|
|
46
50
|
|
|
47
51
|
This package uses cookie-based authentication and undocumented endpoints. Use at your own risk and in accordance with Pinterest's Terms of Service.
|
|
@@ -11,12 +11,36 @@ function buildCommonHeaders(csrf) {
|
|
|
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',
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
function buildCookieHeader(sess, csrf) {
|
|
17
18
|
return `_pinterest_sess=${sess}; csrftoken=${csrf};`;
|
|
18
19
|
}
|
|
19
20
|
const PINTEREST_BASE = 'https://www.pinterest.com';
|
|
21
|
+
async function resolveBaseUrl(ctx, headers, proxy) {
|
|
22
|
+
try {
|
|
23
|
+
const resp = (await ctx.helpers.request({
|
|
24
|
+
method: 'GET',
|
|
25
|
+
uri: PINTEREST_BASE,
|
|
26
|
+
headers,
|
|
27
|
+
proxy: proxy || undefined,
|
|
28
|
+
simple: false,
|
|
29
|
+
resolveWithFullResponse: true,
|
|
30
|
+
followRedirect: false,
|
|
31
|
+
}));
|
|
32
|
+
const loc = resp.headers['location'];
|
|
33
|
+
if (loc) {
|
|
34
|
+
try {
|
|
35
|
+
const u = new URL(loc);
|
|
36
|
+
return `${u.protocol}//${u.host}`;
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
return PINTEREST_BASE;
|
|
43
|
+
}
|
|
20
44
|
class PinterestCookie {
|
|
21
45
|
constructor() {
|
|
22
46
|
this.description = {
|
|
@@ -100,6 +124,13 @@ class PinterestCookie {
|
|
|
100
124
|
options: [
|
|
101
125
|
{ displayName: 'Alt Text', name: 'alt_text', type: 'string', default: '' },
|
|
102
126
|
{ displayName: 'Link', name: 'link', type: 'string', default: '' },
|
|
127
|
+
{
|
|
128
|
+
displayName: 'Attach Link',
|
|
129
|
+
name: 'attachLink',
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
default: true,
|
|
132
|
+
description: 'If disabled or link is blocked, pin will be created without a destination link',
|
|
133
|
+
},
|
|
103
134
|
],
|
|
104
135
|
},
|
|
105
136
|
// Board ops
|
|
@@ -138,10 +169,11 @@ class PinterestCookie {
|
|
|
138
169
|
...buildCommonHeaders(csrf),
|
|
139
170
|
Cookie: buildCookieHeader(pinterestSess, csrf),
|
|
140
171
|
};
|
|
172
|
+
const base = await resolveBaseUrl(this, headers, proxy);
|
|
141
173
|
// Fetch username first (like plugin getMyInfo)
|
|
142
174
|
const meResp = (await this.helpers.request({
|
|
143
175
|
method: 'GET',
|
|
144
|
-
uri: `${
|
|
176
|
+
uri: `${base}/resource/HomefeedBadgingResource/get/`,
|
|
145
177
|
headers,
|
|
146
178
|
proxy: proxy || undefined,
|
|
147
179
|
}));
|
|
@@ -168,7 +200,7 @@ class PinterestCookie {
|
|
|
168
200
|
const qs = { data: JSON.stringify(data) };
|
|
169
201
|
const resp = (await this.helpers.request({
|
|
170
202
|
method: 'GET',
|
|
171
|
-
uri: `${
|
|
203
|
+
uri: `${base}/resource/BoardsResource/get/`,
|
|
172
204
|
qs,
|
|
173
205
|
headers,
|
|
174
206
|
proxy: proxy || undefined,
|
|
@@ -190,7 +222,7 @@ class PinterestCookie {
|
|
|
190
222
|
};
|
|
191
223
|
}
|
|
192
224
|
async execute() {
|
|
193
|
-
var _a, _b, _c
|
|
225
|
+
var _a, _b, _c;
|
|
194
226
|
const items = this.getInputData();
|
|
195
227
|
const returnData = [];
|
|
196
228
|
const creds = (await this.getCredentials('pinterestCookieApi'));
|
|
@@ -202,10 +234,11 @@ class PinterestCookie {
|
|
|
202
234
|
const csrf = randomCsrf();
|
|
203
235
|
const baseHeaders = buildCommonHeaders(csrf);
|
|
204
236
|
const cookieHeader = buildCookieHeader(pinterestSess, csrf);
|
|
237
|
+
const base = await resolveBaseUrl(this, { ...baseHeaders, Cookie: cookieHeader }, proxy);
|
|
205
238
|
if (resource === 'me') {
|
|
206
239
|
const resp = (await this.helpers.request({
|
|
207
240
|
method: 'GET',
|
|
208
|
-
uri: `${
|
|
241
|
+
uri: `${base}/resource/HomefeedBadgingResource/get/`,
|
|
209
242
|
headers: { ...baseHeaders, Cookie: cookieHeader },
|
|
210
243
|
proxy: proxy || undefined,
|
|
211
244
|
}));
|
|
@@ -217,7 +250,7 @@ class PinterestCookie {
|
|
|
217
250
|
// Fetch username first to scope boards
|
|
218
251
|
const meResp = (await this.helpers.request({
|
|
219
252
|
method: 'GET',
|
|
220
|
-
uri: `${
|
|
253
|
+
uri: `${base}/resource/HomefeedBadgingResource/get/`,
|
|
221
254
|
headers: { ...baseHeaders, Cookie: cookieHeader },
|
|
222
255
|
proxy: proxy || undefined,
|
|
223
256
|
}));
|
|
@@ -225,7 +258,7 @@ class PinterestCookie {
|
|
|
225
258
|
const username = String(((_b = (_a = meJson.client_context) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.username) || '');
|
|
226
259
|
const resp = (await this.helpers.request({
|
|
227
260
|
method: 'GET',
|
|
228
|
-
uri: `${
|
|
261
|
+
uri: `${base}/resource/BoardsResource/get/`,
|
|
229
262
|
qs: {
|
|
230
263
|
data: JSON.stringify({
|
|
231
264
|
options: {
|
|
@@ -263,12 +296,12 @@ class PinterestCookie {
|
|
|
263
296
|
// Step 1: request upload slot
|
|
264
297
|
const vipResp = (await this.helpers.request({
|
|
265
298
|
method: 'POST',
|
|
266
|
-
uri: `${
|
|
299
|
+
uri: `${base}/resource/VIPResource/create/`,
|
|
267
300
|
form: {
|
|
268
301
|
source_url: '/pin-builder/',
|
|
269
302
|
data: '{"options":{"type":"pinimage"},"context":{}}',
|
|
270
303
|
},
|
|
271
|
-
headers: { ...baseHeaders, Cookie: cookieHeader },
|
|
304
|
+
headers: { ...baseHeaders, Cookie: cookieHeader, Referer: `${base}/pin-builder/`, Origin: base },
|
|
272
305
|
proxy: proxy || undefined,
|
|
273
306
|
}));
|
|
274
307
|
const vip = JSON.parse(vipResp);
|
|
@@ -298,8 +331,8 @@ class PinterestCookie {
|
|
|
298
331
|
Accept: '*/*',
|
|
299
332
|
'Accept-Encoding': 'gzip',
|
|
300
333
|
'User-Agent': baseHeaders['User-Agent'],
|
|
301
|
-
Origin:
|
|
302
|
-
Referer:
|
|
334
|
+
Origin: base,
|
|
335
|
+
Referer: `${base}/pin-builder/`,
|
|
303
336
|
},
|
|
304
337
|
formData,
|
|
305
338
|
proxy: proxy || undefined,
|
|
@@ -314,6 +347,7 @@ class PinterestCookie {
|
|
|
314
347
|
// compute imageUrl like plugin
|
|
315
348
|
const imageUrl = `https://i.pinimg.com/736x/${etag[0]}${etag[1]}/${etag[2]}${etag[3]}/${etag[4]}${etag[5]}/${etag}.jpg`;
|
|
316
349
|
// Step 3: create pin
|
|
350
|
+
const attachLink = (additionalFields.attachLink !== undefined) ? Boolean(additionalFields.attachLink) : true;
|
|
317
351
|
const sendData = {
|
|
318
352
|
options: {
|
|
319
353
|
board_id: boardId,
|
|
@@ -321,7 +355,7 @@ class PinterestCookie {
|
|
|
321
355
|
skip_pin_create_log: true,
|
|
322
356
|
description,
|
|
323
357
|
alt_text: additionalFields.alt_text || '',
|
|
324
|
-
link: additionalFields.link
|
|
358
|
+
...(attachLink && additionalFields.link ? { link: additionalFields.link } : {}),
|
|
325
359
|
title,
|
|
326
360
|
image_url: imageUrl,
|
|
327
361
|
method: 'uploaded',
|
|
@@ -331,25 +365,47 @@ class PinterestCookie {
|
|
|
331
365
|
},
|
|
332
366
|
context: {},
|
|
333
367
|
};
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
368
|
+
const attemptCreate = async (payload) => {
|
|
369
|
+
var _a, _b;
|
|
370
|
+
const createResp = (await this.helpers.request({
|
|
371
|
+
method: 'POST',
|
|
372
|
+
uri: `${base}/resource/PinResource/create/`,
|
|
373
|
+
form: {
|
|
374
|
+
source_url: '/pin-builder/',
|
|
375
|
+
data: JSON.stringify(payload),
|
|
376
|
+
},
|
|
377
|
+
headers: { ...baseHeaders, Cookie: cookieHeader, Accept: '*/*', Referer: `${base}/pin-builder/`, Origin: base },
|
|
378
|
+
proxy: proxy || undefined,
|
|
379
|
+
// Get body even on 4xx
|
|
380
|
+
simple: false,
|
|
381
|
+
resolveWithFullResponse: true,
|
|
382
|
+
}));
|
|
383
|
+
const body = createResp.body || '';
|
|
384
|
+
let created = {};
|
|
385
|
+
try {
|
|
386
|
+
created = JSON.parse(body);
|
|
387
|
+
}
|
|
388
|
+
catch { }
|
|
389
|
+
const createdRes = created.resource_response || {};
|
|
390
|
+
const pinData = createdRes.data || {};
|
|
391
|
+
const id = String(pinData.id || '');
|
|
392
|
+
const msg = createdRes.message || ((_a = createdRes.error) === null || _a === void 0 ? void 0 : _a.message) || '';
|
|
393
|
+
const msgDetail = ((_b = createdRes.error) === null || _b === void 0 ? void 0 : _b.message_detail) || '';
|
|
394
|
+
return { ok: Boolean(id), id, msg: String(msg || msgDetail || ''), body };
|
|
395
|
+
};
|
|
396
|
+
// First attempt (with link if provided and attachLink=true)
|
|
397
|
+
let result = await attemptCreate(sendData);
|
|
398
|
+
// If blocked due to destination site, retry without link
|
|
399
|
+
if (!result.ok && /doesn\'t allow you to save Pins|does not allow you to save Pins/i.test(result.msg)) {
|
|
400
|
+
const withoutLink = JSON.parse(JSON.stringify(sendData));
|
|
401
|
+
if (withoutLink.options)
|
|
402
|
+
delete withoutLink.options.link;
|
|
403
|
+
result = await attemptCreate(withoutLink);
|
|
404
|
+
}
|
|
405
|
+
if (!result.ok) {
|
|
406
|
+
throw new Error(result.msg || 'Create failed');
|
|
351
407
|
}
|
|
352
|
-
returnData.push({ json: { id, link: `https://www.pinterest.com/pin/${id}` } });
|
|
408
|
+
returnData.push({ json: { id: result.id, link: `https://www.pinterest.com/pin/${result.id}` } });
|
|
353
409
|
}
|
|
354
410
|
}
|
|
355
411
|
}
|