n8n-nodes-pinflow-pinterest 0.1.0
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 +42 -0
- package/dist/credentials/PinFlowApi.credentials.js +31 -0
- package/dist/index.js +2 -0
- package/dist/nodes/PinFlow/PinFlow.node.js +351 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# n8n-nodes-pinflow-pinterest
|
|
2
|
+
|
|
3
|
+
n8n community node package for PinFlow.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Schedule pin (`POST /pins`)
|
|
8
|
+
- Get pin (`GET /pins/{id}`)
|
|
9
|
+
- List pins (`GET /pins`)
|
|
10
|
+
- Delete pin (`DELETE /pins/{id}`)
|
|
11
|
+
|
|
12
|
+
## Install (for development)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd n8n-nodes-pinflow
|
|
16
|
+
npm install
|
|
17
|
+
npm run build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Publish to npm
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm login
|
|
24
|
+
npm publish --access public
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Install in n8n
|
|
28
|
+
|
|
29
|
+
1. Go to `Settings` -> `Community Nodes`
|
|
30
|
+
2. Click `Install`
|
|
31
|
+
3. Enter package name: `n8n-nodes-pinflow-pinterest`
|
|
32
|
+
|
|
33
|
+
## Credentials
|
|
34
|
+
|
|
35
|
+
- `Base URL`: for example `https://pinflow.mirajpatterns.com`
|
|
36
|
+
- `API Key`: from PinFlow dashboard API keys
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- Free plan in PinFlow does not support API access.
|
|
41
|
+
- If account billing is locked, API calls can fail with payment-required errors.
|
|
42
|
+
- For multi-profile accounts, pass the correct `Pinterest Profile ID`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PinFlowApi = void 0;
|
|
4
|
+
class PinFlowApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'pinFlowApi';
|
|
7
|
+
this.displayName = 'PinFlow API';
|
|
8
|
+
this.documentationUrl = 'https://pinflow.mirajpatterns.com/api-reference';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Base URL',
|
|
12
|
+
name: 'baseUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: 'https://pinflow.mirajpatterns.com',
|
|
15
|
+
placeholder: 'https://pinflow.mirajpatterns.com',
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
displayName: 'API Key',
|
|
20
|
+
name: 'apiKey',
|
|
21
|
+
type: 'string',
|
|
22
|
+
typeOptions: {
|
|
23
|
+
password: true,
|
|
24
|
+
},
|
|
25
|
+
default: '',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.PinFlowApi = PinFlowApi;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PinFlow = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
async function pinFlowRequest(ctx, method, endpoint, body, qs) {
|
|
6
|
+
const credentials = await ctx.getCredentials('pinFlowApi');
|
|
7
|
+
const baseUrl = String(credentials.baseUrl || '').trim().replace(/\/+$/, '');
|
|
8
|
+
const apiKey = String(credentials.apiKey || '').trim();
|
|
9
|
+
if (!baseUrl) {
|
|
10
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'PinFlow Base URL is required in credentials.');
|
|
11
|
+
}
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'PinFlow API key is required in credentials.');
|
|
14
|
+
}
|
|
15
|
+
const options = {
|
|
16
|
+
method,
|
|
17
|
+
url: `${baseUrl}${endpoint}`,
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${apiKey}`,
|
|
20
|
+
Accept: 'application/json',
|
|
21
|
+
},
|
|
22
|
+
json: true,
|
|
23
|
+
};
|
|
24
|
+
if (body && Object.keys(body).length) {
|
|
25
|
+
options.body = body;
|
|
26
|
+
}
|
|
27
|
+
if (qs && Object.keys(qs).length) {
|
|
28
|
+
options.qs = qs;
|
|
29
|
+
}
|
|
30
|
+
return await ctx.helpers.httpRequest(options);
|
|
31
|
+
}
|
|
32
|
+
class PinFlow {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.description = {
|
|
35
|
+
displayName: 'PinFlow',
|
|
36
|
+
name: 'pinFlow',
|
|
37
|
+
icon: 'fa:pinterest',
|
|
38
|
+
group: ['transform'],
|
|
39
|
+
version: 1,
|
|
40
|
+
description: 'Create and manage scheduled Pinterest pins in PinFlow',
|
|
41
|
+
defaults: {
|
|
42
|
+
name: 'PinFlow',
|
|
43
|
+
},
|
|
44
|
+
inputs: ['main'],
|
|
45
|
+
outputs: ['main'],
|
|
46
|
+
credentials: [
|
|
47
|
+
{
|
|
48
|
+
name: 'pinFlowApi',
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
properties: [
|
|
53
|
+
{
|
|
54
|
+
displayName: 'Resource',
|
|
55
|
+
name: 'resource',
|
|
56
|
+
type: 'options',
|
|
57
|
+
noDataExpression: true,
|
|
58
|
+
options: [
|
|
59
|
+
{
|
|
60
|
+
name: 'Pin',
|
|
61
|
+
value: 'pin',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
default: 'pin',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
displayName: 'Operation',
|
|
68
|
+
name: 'operation',
|
|
69
|
+
type: 'options',
|
|
70
|
+
noDataExpression: true,
|
|
71
|
+
displayOptions: {
|
|
72
|
+
show: {
|
|
73
|
+
resource: ['pin'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
options: [
|
|
77
|
+
{
|
|
78
|
+
name: 'Schedule',
|
|
79
|
+
value: 'schedule',
|
|
80
|
+
description: 'Create and schedule a new pin',
|
|
81
|
+
action: 'Schedule a pin',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Get',
|
|
85
|
+
value: 'get',
|
|
86
|
+
description: 'Get a pin by ID',
|
|
87
|
+
action: 'Get a pin',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'List',
|
|
91
|
+
value: 'list',
|
|
92
|
+
description: 'List pins',
|
|
93
|
+
action: 'List pins',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Delete',
|
|
97
|
+
value: 'delete',
|
|
98
|
+
description: 'Delete a pin by ID',
|
|
99
|
+
action: 'Delete a pin',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
default: 'schedule',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
displayName: 'Pinterest Profile ID',
|
|
106
|
+
name: 'pinterestProfileId',
|
|
107
|
+
type: 'number',
|
|
108
|
+
typeOptions: {
|
|
109
|
+
minValue: 1,
|
|
110
|
+
},
|
|
111
|
+
required: true,
|
|
112
|
+
default: 1,
|
|
113
|
+
description: 'PinFlow Pinterest profile ID that this pin should use',
|
|
114
|
+
displayOptions: {
|
|
115
|
+
show: {
|
|
116
|
+
resource: ['pin'],
|
|
117
|
+
operation: ['schedule'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
displayName: 'Board ID',
|
|
123
|
+
name: 'boardId',
|
|
124
|
+
type: 'string',
|
|
125
|
+
required: true,
|
|
126
|
+
default: '',
|
|
127
|
+
description: 'Target Pinterest board ID',
|
|
128
|
+
displayOptions: {
|
|
129
|
+
show: {
|
|
130
|
+
resource: ['pin'],
|
|
131
|
+
operation: ['schedule'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
displayName: 'Media URL',
|
|
137
|
+
name: 'mediaUrl',
|
|
138
|
+
type: 'string',
|
|
139
|
+
required: true,
|
|
140
|
+
default: '',
|
|
141
|
+
placeholder: 'https://example.com/image.jpg',
|
|
142
|
+
description: 'Public image URL to publish',
|
|
143
|
+
displayOptions: {
|
|
144
|
+
show: {
|
|
145
|
+
resource: ['pin'],
|
|
146
|
+
operation: ['schedule'],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
displayName: 'Title',
|
|
152
|
+
name: 'title',
|
|
153
|
+
type: 'string',
|
|
154
|
+
default: '',
|
|
155
|
+
description: 'Pin title',
|
|
156
|
+
displayOptions: {
|
|
157
|
+
show: {
|
|
158
|
+
resource: ['pin'],
|
|
159
|
+
operation: ['schedule'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
displayName: 'Description',
|
|
165
|
+
name: 'description',
|
|
166
|
+
type: 'string',
|
|
167
|
+
typeOptions: {
|
|
168
|
+
rows: 4,
|
|
169
|
+
},
|
|
170
|
+
default: '',
|
|
171
|
+
description: 'Pin description',
|
|
172
|
+
displayOptions: {
|
|
173
|
+
show: {
|
|
174
|
+
resource: ['pin'],
|
|
175
|
+
operation: ['schedule'],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
displayName: 'Destination Link',
|
|
181
|
+
name: 'link',
|
|
182
|
+
type: 'string',
|
|
183
|
+
default: '',
|
|
184
|
+
placeholder: 'https://example.com/product-page',
|
|
185
|
+
description: 'External link to attach to the pin',
|
|
186
|
+
displayOptions: {
|
|
187
|
+
show: {
|
|
188
|
+
resource: ['pin'],
|
|
189
|
+
operation: ['schedule'],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
displayName: 'Schedule At (ISO UTC)',
|
|
195
|
+
name: 'scheduledAt',
|
|
196
|
+
type: 'string',
|
|
197
|
+
required: true,
|
|
198
|
+
default: '',
|
|
199
|
+
placeholder: '2026-05-16T11:55:00Z',
|
|
200
|
+
description: 'ISO datetime in UTC for scheduling',
|
|
201
|
+
displayOptions: {
|
|
202
|
+
show: {
|
|
203
|
+
resource: ['pin'],
|
|
204
|
+
operation: ['schedule'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
displayName: 'Pin ID',
|
|
210
|
+
name: 'pinId',
|
|
211
|
+
type: 'number',
|
|
212
|
+
typeOptions: {
|
|
213
|
+
minValue: 1,
|
|
214
|
+
},
|
|
215
|
+
required: true,
|
|
216
|
+
default: 1,
|
|
217
|
+
displayOptions: {
|
|
218
|
+
show: {
|
|
219
|
+
resource: ['pin'],
|
|
220
|
+
operation: ['get', 'delete'],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
displayName: 'Status',
|
|
226
|
+
name: 'status',
|
|
227
|
+
type: 'options',
|
|
228
|
+
options: [
|
|
229
|
+
{ name: 'All', value: '' },
|
|
230
|
+
{ name: 'Draft', value: 'draft' },
|
|
231
|
+
{ name: 'Review', value: 'review' },
|
|
232
|
+
{ name: 'Queued', value: 'queued' },
|
|
233
|
+
{ name: 'Scheduled', value: 'scheduled' },
|
|
234
|
+
{ name: 'Publishing', value: 'publishing' },
|
|
235
|
+
{ name: 'Published', value: 'published' },
|
|
236
|
+
{ name: 'Failed', value: 'failed' },
|
|
237
|
+
],
|
|
238
|
+
default: '',
|
|
239
|
+
displayOptions: {
|
|
240
|
+
show: {
|
|
241
|
+
resource: ['pin'],
|
|
242
|
+
operation: ['list'],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
displayName: 'Limit',
|
|
248
|
+
name: 'limit',
|
|
249
|
+
type: 'number',
|
|
250
|
+
typeOptions: {
|
|
251
|
+
minValue: 1,
|
|
252
|
+
},
|
|
253
|
+
default: 50,
|
|
254
|
+
description: 'Maximum number of pins to return',
|
|
255
|
+
displayOptions: {
|
|
256
|
+
show: {
|
|
257
|
+
resource: ['pin'],
|
|
258
|
+
operation: ['list'],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async execute() {
|
|
266
|
+
const items = this.getInputData();
|
|
267
|
+
const returnData = [];
|
|
268
|
+
for (let i = 0; i < items.length; i++) {
|
|
269
|
+
try {
|
|
270
|
+
const resource = this.getNodeParameter('resource', i);
|
|
271
|
+
const operation = this.getNodeParameter('operation', i);
|
|
272
|
+
let responseData;
|
|
273
|
+
if (resource === 'pin') {
|
|
274
|
+
if (operation === 'schedule') {
|
|
275
|
+
const payload = {
|
|
276
|
+
pinterest_profile_id: this.getNodeParameter('pinterestProfileId', i),
|
|
277
|
+
board_id: this.getNodeParameter('boardId', i),
|
|
278
|
+
media_url: this.getNodeParameter('mediaUrl', i),
|
|
279
|
+
scheduled_at: this.getNodeParameter('scheduledAt', i),
|
|
280
|
+
};
|
|
281
|
+
const title = this.getNodeParameter('title', i);
|
|
282
|
+
const description = this.getNodeParameter('description', i);
|
|
283
|
+
const link = this.getNodeParameter('link', i);
|
|
284
|
+
if (title)
|
|
285
|
+
payload.title = title;
|
|
286
|
+
if (description)
|
|
287
|
+
payload.description = description;
|
|
288
|
+
if (link)
|
|
289
|
+
payload.link = link;
|
|
290
|
+
responseData = await pinFlowRequest(this, 'POST', '/pins', payload);
|
|
291
|
+
}
|
|
292
|
+
else if (operation === 'get') {
|
|
293
|
+
const pinId = this.getNodeParameter('pinId', i);
|
|
294
|
+
responseData = await pinFlowRequest(this, 'GET', `/pins/${pinId}`);
|
|
295
|
+
}
|
|
296
|
+
else if (operation === 'list') {
|
|
297
|
+
const status = this.getNodeParameter('status', i);
|
|
298
|
+
const limit = this.getNodeParameter('limit', i);
|
|
299
|
+
const qs = {};
|
|
300
|
+
if (status)
|
|
301
|
+
qs.status = status;
|
|
302
|
+
if (limit > 0)
|
|
303
|
+
qs.limit = limit;
|
|
304
|
+
responseData = await pinFlowRequest(this, 'GET', '/pins', undefined, qs);
|
|
305
|
+
}
|
|
306
|
+
else if (operation === 'delete') {
|
|
307
|
+
const pinId = this.getNodeParameter('pinId', i);
|
|
308
|
+
responseData = await pinFlowRequest(this, 'DELETE', `/pins/${pinId}`);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported operation: ${operation}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported resource: ${resource}`);
|
|
316
|
+
}
|
|
317
|
+
if (Array.isArray(responseData)) {
|
|
318
|
+
for (const row of responseData) {
|
|
319
|
+
returnData.push({ json: row, pairedItem: { item: i } });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (responseData && typeof responseData === 'object') {
|
|
323
|
+
returnData.push({ json: responseData, pairedItem: { item: i } });
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const primitive = typeof responseData === 'string' ||
|
|
327
|
+
typeof responseData === 'number' ||
|
|
328
|
+
typeof responseData === 'boolean' ||
|
|
329
|
+
responseData === null
|
|
330
|
+
? responseData
|
|
331
|
+
: String(responseData);
|
|
332
|
+
returnData.push({ json: { success: true, data: primitive }, pairedItem: { item: i } });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
if (this.continueOnFail()) {
|
|
337
|
+
returnData.push({
|
|
338
|
+
json: {
|
|
339
|
+
error: error.message,
|
|
340
|
+
},
|
|
341
|
+
pairedItem: { item: i },
|
|
342
|
+
});
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return [returnData];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
exports.PinFlow = PinFlow;
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-pinflow-pinterest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "n8n community node for PinFlow",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://pinflow.mirajpatterns.com",
|
|
7
|
+
"author": "PinFlow",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"n8n-community-node-package",
|
|
10
|
+
"n8n",
|
|
11
|
+
"pinflow",
|
|
12
|
+
"pinterest"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"dev": "tsc -w -p tsconfig.json"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/soudou98/pinflow.git"
|
|
25
|
+
},
|
|
26
|
+
"n8n": {
|
|
27
|
+
"n8nNodesApiVersion": 1,
|
|
28
|
+
"credentials": [
|
|
29
|
+
"dist/credentials/PinFlowApi.credentials.js"
|
|
30
|
+
],
|
|
31
|
+
"nodes": [
|
|
32
|
+
"dist/nodes/PinFlow/PinFlow.node.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.17.57",
|
|
37
|
+
"n8n-core": "^1.109.0",
|
|
38
|
+
"n8n-workflow": "^1.109.0",
|
|
39
|
+
"typescript": "^5.8.3"
|
|
40
|
+
}
|
|
41
|
+
}
|