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: `${PINTEREST_BASE}/resource/HomefeedBadgingResource/get/`,
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: `${PINTEREST_BASE}/resource/BoardsResource/get/`,
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, _d;
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: `${PINTEREST_BASE}/resource/HomefeedBadgingResource/get/`,
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: `${PINTEREST_BASE}/resource/HomefeedBadgingResource/get/`,
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: `${PINTEREST_BASE}/resource/BoardsResource/get/`,
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: `${PINTEREST_BASE}/resource/VIPResource/create/`,
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: 'https://www.pinterest.com',
302
- Referer: 'https://www.pinterest.com',
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 createResp = (await this.helpers.request({
335
- method: 'POST',
336
- uri: `${PINTEREST_BASE}/resource/PinResource/create/`,
337
- form: {
338
- source_url: '/pin-builder/',
339
- data: JSON.stringify(sendData),
340
- },
341
- headers: { ...baseHeaders, Cookie: cookieHeader },
342
- proxy: proxy || undefined,
343
- }));
344
- const created = JSON.parse(createResp);
345
- const createdRes = created.resource_response || {};
346
- const pinData = createdRes.data || {};
347
- const id = String(pinData.id || '');
348
- if (!id) {
349
- const message = createdRes.message || ((_d = createdRes.error) === null || _d === void 0 ? void 0 : _d.message) || 'Create failed';
350
- throw new Error(String(message));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-pinterest",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "n8n community nodes for Pinterest v5 API (list boards, create pins)",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",