n8n-nodes-bulkpublish 1.0.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 +83 -0
- package/dist/BulkPublish/icon.svg +4 -0
- package/dist/credentials/BulkPublishApi.credentials.d.ts +9 -0
- package/dist/credentials/BulkPublishApi.credentials.js +35 -0
- package/dist/nodes/BulkPublish/BulkPublish.node.d.ts +5 -0
- package/dist/nodes/BulkPublish/BulkPublish.node.js +500 -0
- package/dist/nodes/BulkPublish/icon.svg +4 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# n8n-nodes-bulkpublish
|
|
2
|
+
|
|
3
|
+
n8n community node for [BulkPublish](https://bulkpublish.com) — publish to 11 social media platforms from your n8n workflows.
|
|
4
|
+
|
|
5
|
+
## Supported Platforms
|
|
6
|
+
|
|
7
|
+
Facebook, Instagram, X (Twitter), TikTok, YouTube, Threads, Bluesky, Pinterest, Google Business Profile, LinkedIn, Mastodon
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
In your n8n instance:
|
|
12
|
+
|
|
13
|
+
1. Go to **Settings > Community Nodes**
|
|
14
|
+
2. Enter `n8n-nodes-bulkpublish`
|
|
15
|
+
3. Click Install
|
|
16
|
+
|
|
17
|
+
Or via CLI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install n8n-nodes-bulkpublish
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Credentials
|
|
24
|
+
|
|
25
|
+
1. Sign up at [app.bulkpublish.com](https://app.bulkpublish.com/register)
|
|
26
|
+
2. Go to **Settings > Developer** and create an API key
|
|
27
|
+
3. In n8n, add a **BulkPublish API** credential with your key
|
|
28
|
+
|
|
29
|
+
## Operations
|
|
30
|
+
|
|
31
|
+
### Post
|
|
32
|
+
- **Create** — Create a draft or scheduled post to one or more channels
|
|
33
|
+
- **Get** — Get a post with platform statuses
|
|
34
|
+
- **List** — List posts with filters (status, search, date)
|
|
35
|
+
- **Update** — Update a draft or scheduled post
|
|
36
|
+
- **Delete** — Delete a post
|
|
37
|
+
- **Publish** — Publish a draft immediately
|
|
38
|
+
- **Retry** — Retry failed platforms
|
|
39
|
+
|
|
40
|
+
### Channel
|
|
41
|
+
- **List** — List all connected social media channels
|
|
42
|
+
- **Get** — Get a channel by ID
|
|
43
|
+
- **Health Check** — Check channel token health
|
|
44
|
+
- **Get Options** — Get platform-specific options (Pinterest boards, YouTube playlists, LinkedIn organizations)
|
|
45
|
+
|
|
46
|
+
### Media
|
|
47
|
+
- **Upload** — Upload an image or video file
|
|
48
|
+
- **List** — List uploaded media files
|
|
49
|
+
- **Delete** — Delete a media file
|
|
50
|
+
|
|
51
|
+
### Label
|
|
52
|
+
- **Create** — Create a label
|
|
53
|
+
- **List** — List all labels
|
|
54
|
+
- **Delete** — Delete a label
|
|
55
|
+
|
|
56
|
+
### Analytics
|
|
57
|
+
- **Summary** — Get analytics summary for a date range
|
|
58
|
+
- **Engagement** — Get engagement data grouped by time period
|
|
59
|
+
|
|
60
|
+
### Schedule
|
|
61
|
+
- **List** — List recurring schedules
|
|
62
|
+
- **Delete** — Delete a schedule
|
|
63
|
+
|
|
64
|
+
## Example Workflows
|
|
65
|
+
|
|
66
|
+
### Auto-post RSS feed items
|
|
67
|
+
1. **RSS Feed Trigger** → reads new items
|
|
68
|
+
2. **BulkPublish** → creates a scheduled post for each item
|
|
69
|
+
|
|
70
|
+
### Post on GitHub release
|
|
71
|
+
1. **GitHub Trigger** → new release event
|
|
72
|
+
2. **BulkPublish** → creates and publishes announcement to all channels
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- [API Docs](https://app.bulkpublish.com/docs)
|
|
77
|
+
- [SDK & Examples](https://github.com/azeemkafridi/bulkpublish-api)
|
|
78
|
+
- [npm](https://www.npmjs.com/package/bulkpublish)
|
|
79
|
+
- [PyPI](https://pypi.org/project/bulkpublish/)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class BulkPublishApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
test: ICredentialTestRequest;
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BulkPublishApi = void 0;
|
|
4
|
+
class BulkPublishApi {
|
|
5
|
+
name = 'bulkPublishApi';
|
|
6
|
+
displayName = 'BulkPublish API';
|
|
7
|
+
documentationUrl = 'https://github.com/azeemkafridi/bulkpublish-api';
|
|
8
|
+
properties = [
|
|
9
|
+
{
|
|
10
|
+
displayName: 'API Key',
|
|
11
|
+
name: 'apiKey',
|
|
12
|
+
type: 'string',
|
|
13
|
+
typeOptions: { password: true },
|
|
14
|
+
default: '',
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'Your BulkPublish API key (starts with bp_). Get one at https://app.bulkpublish.com/settings/developer',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
authenticate = {
|
|
20
|
+
type: 'generic',
|
|
21
|
+
properties: {
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: '=Bearer {{$credentials.apiKey}}',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
test = {
|
|
28
|
+
request: {
|
|
29
|
+
baseURL: 'https://app.bulkpublish.com',
|
|
30
|
+
url: '/api/channels',
|
|
31
|
+
method: 'GET',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.BulkPublishApi = BulkPublishApi;
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BulkPublish = void 0;
|
|
4
|
+
const BASE_URL = 'https://app.bulkpublish.com';
|
|
5
|
+
class BulkPublish {
|
|
6
|
+
description = {
|
|
7
|
+
displayName: 'BulkPublish',
|
|
8
|
+
name: 'bulkPublish',
|
|
9
|
+
icon: 'file:icon.svg',
|
|
10
|
+
group: ['output'],
|
|
11
|
+
version: 1,
|
|
12
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
13
|
+
description: 'Publish to 11 social media platforms — Facebook, Instagram, X, TikTok, YouTube, Threads, Bluesky, Pinterest, LinkedIn, Mastodon, Google Business Profile',
|
|
14
|
+
defaults: { name: 'BulkPublish' },
|
|
15
|
+
inputs: ['main'],
|
|
16
|
+
outputs: ['main'],
|
|
17
|
+
credentials: [{ name: 'bulkPublishApi', required: true }],
|
|
18
|
+
properties: [
|
|
19
|
+
// ── Resource ─────────────────────────────────────────────
|
|
20
|
+
{
|
|
21
|
+
displayName: 'Resource',
|
|
22
|
+
name: 'resource',
|
|
23
|
+
type: 'options',
|
|
24
|
+
noDataExpression: true,
|
|
25
|
+
options: [
|
|
26
|
+
{ name: 'Post', value: 'post' },
|
|
27
|
+
{ name: 'Channel', value: 'channel' },
|
|
28
|
+
{ name: 'Media', value: 'media' },
|
|
29
|
+
{ name: 'Label', value: 'label' },
|
|
30
|
+
{ name: 'Analytics', value: 'analytics' },
|
|
31
|
+
{ name: 'Schedule', value: 'schedule' },
|
|
32
|
+
],
|
|
33
|
+
default: 'post',
|
|
34
|
+
},
|
|
35
|
+
// ── Post Operations ──────────────────────────────────────
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Operation',
|
|
38
|
+
name: 'operation',
|
|
39
|
+
type: 'options',
|
|
40
|
+
noDataExpression: true,
|
|
41
|
+
displayOptions: { show: { resource: ['post'] } },
|
|
42
|
+
options: [
|
|
43
|
+
{ name: 'Create', value: 'create', action: 'Create a post' },
|
|
44
|
+
{ name: 'Get', value: 'get', action: 'Get a post' },
|
|
45
|
+
{ name: 'List', value: 'list', action: 'List posts' },
|
|
46
|
+
{ name: 'Update', value: 'update', action: 'Update a post' },
|
|
47
|
+
{ name: 'Delete', value: 'delete', action: 'Delete a post' },
|
|
48
|
+
{ name: 'Publish', value: 'publish', action: 'Publish a draft immediately' },
|
|
49
|
+
{ name: 'Retry', value: 'retry', action: 'Retry failed platforms' },
|
|
50
|
+
],
|
|
51
|
+
default: 'create',
|
|
52
|
+
},
|
|
53
|
+
// Post: Create fields
|
|
54
|
+
{
|
|
55
|
+
displayName: 'Content',
|
|
56
|
+
name: 'content',
|
|
57
|
+
type: 'string',
|
|
58
|
+
typeOptions: { rows: 4 },
|
|
59
|
+
default: '',
|
|
60
|
+
required: true,
|
|
61
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
62
|
+
description: 'The post text content',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
displayName: 'Channels (JSON)',
|
|
66
|
+
name: 'channels',
|
|
67
|
+
type: 'string',
|
|
68
|
+
default: '[{"channelId": 1, "platform": "facebook"}]',
|
|
69
|
+
required: true,
|
|
70
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
71
|
+
description: 'JSON array of channels: [{"channelId": number, "platform": string}]. Get IDs from the Channel > List operation.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
displayName: 'Status',
|
|
75
|
+
name: 'status',
|
|
76
|
+
type: 'options',
|
|
77
|
+
options: [
|
|
78
|
+
{ name: 'Draft', value: 'draft' },
|
|
79
|
+
{ name: 'Scheduled', value: 'scheduled' },
|
|
80
|
+
],
|
|
81
|
+
default: 'draft',
|
|
82
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
displayName: 'Scheduled At',
|
|
86
|
+
name: 'scheduledAt',
|
|
87
|
+
type: 'dateTime',
|
|
88
|
+
default: '',
|
|
89
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'], status: ['scheduled'] } },
|
|
90
|
+
description: 'When to publish (ISO 8601)',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
displayName: 'Timezone',
|
|
94
|
+
name: 'timezone',
|
|
95
|
+
type: 'string',
|
|
96
|
+
default: 'UTC',
|
|
97
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'], status: ['scheduled'] } },
|
|
98
|
+
description: 'IANA timezone (e.g. America/New_York)',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
displayName: 'Media File IDs',
|
|
102
|
+
name: 'mediaFiles',
|
|
103
|
+
type: 'string',
|
|
104
|
+
default: '',
|
|
105
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
106
|
+
description: 'Comma-separated media file IDs (from Media > Upload)',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
displayName: 'Label IDs',
|
|
110
|
+
name: 'labelIds',
|
|
111
|
+
type: 'string',
|
|
112
|
+
default: '',
|
|
113
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
114
|
+
description: 'Comma-separated label IDs',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
displayName: 'Platform Content (JSON)',
|
|
118
|
+
name: 'platformContent',
|
|
119
|
+
type: 'string',
|
|
120
|
+
default: '',
|
|
121
|
+
displayOptions: { show: { resource: ['post'], operation: ['create'] } },
|
|
122
|
+
description: 'Per-platform text overrides: {"x": "Short", "linkedin": "Long version"}',
|
|
123
|
+
},
|
|
124
|
+
// Post: Get/Update/Delete/Publish/Retry — ID
|
|
125
|
+
{
|
|
126
|
+
displayName: 'Post ID',
|
|
127
|
+
name: 'postId',
|
|
128
|
+
type: 'number',
|
|
129
|
+
default: 0,
|
|
130
|
+
required: true,
|
|
131
|
+
displayOptions: { show: { resource: ['post'], operation: ['get', 'update', 'delete', 'publish', 'retry'] } },
|
|
132
|
+
},
|
|
133
|
+
// Post: Update fields
|
|
134
|
+
{
|
|
135
|
+
displayName: 'Content',
|
|
136
|
+
name: 'updateContent',
|
|
137
|
+
type: 'string',
|
|
138
|
+
typeOptions: { rows: 4 },
|
|
139
|
+
default: '',
|
|
140
|
+
displayOptions: { show: { resource: ['post'], operation: ['update'] } },
|
|
141
|
+
description: 'New post content (leave empty to keep current)',
|
|
142
|
+
},
|
|
143
|
+
// Post: List filters
|
|
144
|
+
{
|
|
145
|
+
displayName: 'Status Filter',
|
|
146
|
+
name: 'statusFilter',
|
|
147
|
+
type: 'options',
|
|
148
|
+
options: [
|
|
149
|
+
{ name: 'All', value: '' },
|
|
150
|
+
{ name: 'Draft', value: 'draft' },
|
|
151
|
+
{ name: 'Scheduled', value: 'scheduled' },
|
|
152
|
+
{ name: 'Published', value: 'published' },
|
|
153
|
+
{ name: 'Failed', value: 'failed' },
|
|
154
|
+
],
|
|
155
|
+
default: '',
|
|
156
|
+
displayOptions: { show: { resource: ['post'], operation: ['list'] } },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
displayName: 'Limit',
|
|
160
|
+
name: 'limit',
|
|
161
|
+
type: 'number',
|
|
162
|
+
default: 20,
|
|
163
|
+
displayOptions: { show: { resource: ['post'], operation: ['list'] } },
|
|
164
|
+
},
|
|
165
|
+
// ── Channel Operations ───────────────────────────────────
|
|
166
|
+
{
|
|
167
|
+
displayName: 'Operation',
|
|
168
|
+
name: 'operation',
|
|
169
|
+
type: 'options',
|
|
170
|
+
noDataExpression: true,
|
|
171
|
+
displayOptions: { show: { resource: ['channel'] } },
|
|
172
|
+
options: [
|
|
173
|
+
{ name: 'List', value: 'list', action: 'List channels' },
|
|
174
|
+
{ name: 'Get', value: 'get', action: 'Get a channel' },
|
|
175
|
+
{ name: 'Health Check', value: 'health', action: 'Check channel health' },
|
|
176
|
+
{ name: 'Get Options', value: 'options', action: 'Get platform options (boards, playlists, orgs)' },
|
|
177
|
+
],
|
|
178
|
+
default: 'list',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
displayName: 'Channel ID',
|
|
182
|
+
name: 'channelId',
|
|
183
|
+
type: 'number',
|
|
184
|
+
default: 0,
|
|
185
|
+
required: true,
|
|
186
|
+
displayOptions: { show: { resource: ['channel'], operation: ['get', 'health', 'options'] } },
|
|
187
|
+
},
|
|
188
|
+
// ── Media Operations ─────────────────────────────────────
|
|
189
|
+
{
|
|
190
|
+
displayName: 'Operation',
|
|
191
|
+
name: 'operation',
|
|
192
|
+
type: 'options',
|
|
193
|
+
noDataExpression: true,
|
|
194
|
+
displayOptions: { show: { resource: ['media'] } },
|
|
195
|
+
options: [
|
|
196
|
+
{ name: 'Upload', value: 'upload', action: 'Upload a media file' },
|
|
197
|
+
{ name: 'List', value: 'list', action: 'List media files' },
|
|
198
|
+
{ name: 'Delete', value: 'delete', action: 'Delete a media file' },
|
|
199
|
+
],
|
|
200
|
+
default: 'upload',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
displayName: 'Binary Property',
|
|
204
|
+
name: 'binaryProperty',
|
|
205
|
+
type: 'string',
|
|
206
|
+
default: 'data',
|
|
207
|
+
required: true,
|
|
208
|
+
displayOptions: { show: { resource: ['media'], operation: ['upload'] } },
|
|
209
|
+
description: 'Name of the binary property containing the file to upload',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
displayName: 'Media ID',
|
|
213
|
+
name: 'mediaId',
|
|
214
|
+
type: 'number',
|
|
215
|
+
default: 0,
|
|
216
|
+
required: true,
|
|
217
|
+
displayOptions: { show: { resource: ['media'], operation: ['delete'] } },
|
|
218
|
+
},
|
|
219
|
+
// ── Label Operations ─────────────────────────────────────
|
|
220
|
+
{
|
|
221
|
+
displayName: 'Operation',
|
|
222
|
+
name: 'operation',
|
|
223
|
+
type: 'options',
|
|
224
|
+
noDataExpression: true,
|
|
225
|
+
displayOptions: { show: { resource: ['label'] } },
|
|
226
|
+
options: [
|
|
227
|
+
{ name: 'Create', value: 'create', action: 'Create a label' },
|
|
228
|
+
{ name: 'List', value: 'list', action: 'List labels' },
|
|
229
|
+
{ name: 'Delete', value: 'delete', action: 'Delete a label' },
|
|
230
|
+
],
|
|
231
|
+
default: 'list',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
displayName: 'Label Name',
|
|
235
|
+
name: 'labelName',
|
|
236
|
+
type: 'string',
|
|
237
|
+
default: '',
|
|
238
|
+
required: true,
|
|
239
|
+
displayOptions: { show: { resource: ['label'], operation: ['create'] } },
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
displayName: 'Label Color',
|
|
243
|
+
name: 'labelColor',
|
|
244
|
+
type: 'string',
|
|
245
|
+
default: '#6366f1',
|
|
246
|
+
displayOptions: { show: { resource: ['label'], operation: ['create'] } },
|
|
247
|
+
description: 'Hex color code',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
displayName: 'Label ID',
|
|
251
|
+
name: 'labelId',
|
|
252
|
+
type: 'number',
|
|
253
|
+
default: 0,
|
|
254
|
+
required: true,
|
|
255
|
+
displayOptions: { show: { resource: ['label'], operation: ['delete'] } },
|
|
256
|
+
},
|
|
257
|
+
// ── Analytics Operations ─────────────────────────────────
|
|
258
|
+
{
|
|
259
|
+
displayName: 'Operation',
|
|
260
|
+
name: 'operation',
|
|
261
|
+
type: 'options',
|
|
262
|
+
noDataExpression: true,
|
|
263
|
+
displayOptions: { show: { resource: ['analytics'] } },
|
|
264
|
+
options: [
|
|
265
|
+
{ name: 'Summary', value: 'summary', action: 'Get analytics summary' },
|
|
266
|
+
{ name: 'Engagement', value: 'engagement', action: 'Get engagement data' },
|
|
267
|
+
],
|
|
268
|
+
default: 'summary',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
displayName: 'From',
|
|
272
|
+
name: 'from',
|
|
273
|
+
type: 'dateTime',
|
|
274
|
+
default: '',
|
|
275
|
+
required: true,
|
|
276
|
+
displayOptions: { show: { resource: ['analytics'] } },
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
displayName: 'To',
|
|
280
|
+
name: 'to',
|
|
281
|
+
type: 'dateTime',
|
|
282
|
+
default: '',
|
|
283
|
+
required: true,
|
|
284
|
+
displayOptions: { show: { resource: ['analytics'] } },
|
|
285
|
+
},
|
|
286
|
+
// ── Schedule Operations ──────────────────────────────────
|
|
287
|
+
{
|
|
288
|
+
displayName: 'Operation',
|
|
289
|
+
name: 'operation',
|
|
290
|
+
type: 'options',
|
|
291
|
+
noDataExpression: true,
|
|
292
|
+
displayOptions: { show: { resource: ['schedule'] } },
|
|
293
|
+
options: [
|
|
294
|
+
{ name: 'List', value: 'list', action: 'List recurring schedules' },
|
|
295
|
+
{ name: 'Delete', value: 'delete', action: 'Delete a schedule' },
|
|
296
|
+
],
|
|
297
|
+
default: 'list',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
displayName: 'Schedule ID',
|
|
301
|
+
name: 'scheduleId',
|
|
302
|
+
type: 'number',
|
|
303
|
+
default: 0,
|
|
304
|
+
required: true,
|
|
305
|
+
displayOptions: { show: { resource: ['schedule'], operation: ['delete'] } },
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
async execute() {
|
|
310
|
+
const items = this.getInputData();
|
|
311
|
+
const returnData = [];
|
|
312
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
313
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
314
|
+
for (let i = 0; i < items.length; i++) {
|
|
315
|
+
let responseData;
|
|
316
|
+
// ── Posts ───────────────────────────────────────────────
|
|
317
|
+
if (resource === 'post') {
|
|
318
|
+
if (operation === 'create') {
|
|
319
|
+
const channelsJson = this.getNodeParameter('channels', i);
|
|
320
|
+
const body = {
|
|
321
|
+
content: this.getNodeParameter('content', i),
|
|
322
|
+
channels: JSON.parse(channelsJson),
|
|
323
|
+
status: this.getNodeParameter('status', i),
|
|
324
|
+
};
|
|
325
|
+
const scheduledAt = this.getNodeParameter('scheduledAt', i, '');
|
|
326
|
+
if (scheduledAt)
|
|
327
|
+
body.scheduledAt = scheduledAt;
|
|
328
|
+
const timezone = this.getNodeParameter('timezone', i, 'UTC');
|
|
329
|
+
if (timezone)
|
|
330
|
+
body.timezone = timezone;
|
|
331
|
+
const mediaStr = this.getNodeParameter('mediaFiles', i, '');
|
|
332
|
+
if (mediaStr)
|
|
333
|
+
body.mediaFiles = mediaStr.split(',').map((s) => parseInt(s.trim(), 10)).filter(Boolean);
|
|
334
|
+
const labelStr = this.getNodeParameter('labelIds', i, '');
|
|
335
|
+
if (labelStr)
|
|
336
|
+
body.labelIds = labelStr.split(',').map((s) => parseInt(s.trim(), 10)).filter(Boolean);
|
|
337
|
+
const pcStr = this.getNodeParameter('platformContent', i, '');
|
|
338
|
+
if (pcStr)
|
|
339
|
+
body.platformContent = JSON.parse(pcStr);
|
|
340
|
+
responseData = await this.helpers.httpRequest({
|
|
341
|
+
method: 'POST', url: `${BASE_URL}/api/posts`, body, json: true,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
else if (operation === 'get') {
|
|
345
|
+
const id = this.getNodeParameter('postId', i);
|
|
346
|
+
responseData = await this.helpers.httpRequest({
|
|
347
|
+
method: 'GET', url: `${BASE_URL}/api/posts/${id}`, json: true,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
else if (operation === 'list') {
|
|
351
|
+
const qs = { limit: this.getNodeParameter('limit', i) };
|
|
352
|
+
const status = this.getNodeParameter('statusFilter', i, '');
|
|
353
|
+
if (status)
|
|
354
|
+
qs.status = status;
|
|
355
|
+
responseData = await this.helpers.httpRequest({
|
|
356
|
+
method: 'GET', url: `${BASE_URL}/api/posts`, qs, json: true,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
else if (operation === 'update') {
|
|
360
|
+
const id = this.getNodeParameter('postId', i);
|
|
361
|
+
const body = {};
|
|
362
|
+
const content = this.getNodeParameter('updateContent', i, '');
|
|
363
|
+
if (content)
|
|
364
|
+
body.content = content;
|
|
365
|
+
responseData = await this.helpers.httpRequest({
|
|
366
|
+
method: 'PUT', url: `${BASE_URL}/api/posts/${id}`, body, json: true,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
else if (operation === 'delete') {
|
|
370
|
+
const id = this.getNodeParameter('postId', i);
|
|
371
|
+
responseData = await this.helpers.httpRequest({
|
|
372
|
+
method: 'DELETE', url: `${BASE_URL}/api/posts/${id}`, json: true,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
else if (operation === 'publish') {
|
|
376
|
+
const id = this.getNodeParameter('postId', i);
|
|
377
|
+
responseData = await this.helpers.httpRequest({
|
|
378
|
+
method: 'POST', url: `${BASE_URL}/api/posts/${id}/publish`, json: true,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
else if (operation === 'retry') {
|
|
382
|
+
const id = this.getNodeParameter('postId', i);
|
|
383
|
+
responseData = await this.helpers.httpRequest({
|
|
384
|
+
method: 'POST', url: `${BASE_URL}/api/posts/${id}/retry`, json: true,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// ── Channels ───────────────────────────────────────────
|
|
389
|
+
else if (resource === 'channel') {
|
|
390
|
+
if (operation === 'list') {
|
|
391
|
+
responseData = await this.helpers.httpRequest({
|
|
392
|
+
method: 'GET', url: `${BASE_URL}/api/channels`, json: true,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
else if (operation === 'get') {
|
|
396
|
+
const id = this.getNodeParameter('channelId', i);
|
|
397
|
+
responseData = await this.helpers.httpRequest({
|
|
398
|
+
method: 'GET', url: `${BASE_URL}/api/channels/${id}`, json: true,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
else if (operation === 'health') {
|
|
402
|
+
const id = this.getNodeParameter('channelId', i);
|
|
403
|
+
responseData = await this.helpers.httpRequest({
|
|
404
|
+
method: 'GET', url: `${BASE_URL}/api/channels/${id}/health`, json: true,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
else if (operation === 'options') {
|
|
408
|
+
const id = this.getNodeParameter('channelId', i);
|
|
409
|
+
responseData = await this.helpers.httpRequest({
|
|
410
|
+
method: 'GET', url: `${BASE_URL}/api/channels/${id}/options`, json: true,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// ── Media ──────────────────────────────────────────────
|
|
415
|
+
else if (resource === 'media') {
|
|
416
|
+
if (operation === 'upload') {
|
|
417
|
+
const binaryProperty = this.getNodeParameter('binaryProperty', i);
|
|
418
|
+
const binaryData = this.helpers.assertBinaryData(i, binaryProperty);
|
|
419
|
+
const buffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
|
|
420
|
+
responseData = await this.helpers.httpRequest({
|
|
421
|
+
method: 'POST',
|
|
422
|
+
url: `${BASE_URL}/api/media`,
|
|
423
|
+
body: buffer,
|
|
424
|
+
headers: {
|
|
425
|
+
'Content-Type': binaryData.mimeType,
|
|
426
|
+
'Content-Disposition': `attachment; filename="${binaryData.fileName || 'upload'}"`,
|
|
427
|
+
},
|
|
428
|
+
json: false,
|
|
429
|
+
});
|
|
430
|
+
if (typeof responseData === 'string')
|
|
431
|
+
responseData = JSON.parse(responseData);
|
|
432
|
+
}
|
|
433
|
+
else if (operation === 'list') {
|
|
434
|
+
responseData = await this.helpers.httpRequest({
|
|
435
|
+
method: 'GET', url: `${BASE_URL}/api/media`, json: true,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
else if (operation === 'delete') {
|
|
439
|
+
const id = this.getNodeParameter('mediaId', i);
|
|
440
|
+
responseData = await this.helpers.httpRequest({
|
|
441
|
+
method: 'DELETE', url: `${BASE_URL}/api/media/${id}`, json: true,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ── Labels ─────────────────────────────────────────────
|
|
446
|
+
else if (resource === 'label') {
|
|
447
|
+
if (operation === 'create') {
|
|
448
|
+
responseData = await this.helpers.httpRequest({
|
|
449
|
+
method: 'POST', url: `${BASE_URL}/api/labels`, json: true,
|
|
450
|
+
body: {
|
|
451
|
+
name: this.getNodeParameter('labelName', i),
|
|
452
|
+
color: this.getNodeParameter('labelColor', i),
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else if (operation === 'list') {
|
|
457
|
+
responseData = await this.helpers.httpRequest({
|
|
458
|
+
method: 'GET', url: `${BASE_URL}/api/labels`, json: true,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
else if (operation === 'delete') {
|
|
462
|
+
const id = this.getNodeParameter('labelId', i);
|
|
463
|
+
responseData = await this.helpers.httpRequest({
|
|
464
|
+
method: 'DELETE', url: `${BASE_URL}/api/labels/${id}`, json: true,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// ── Analytics ──────────────────────────────────────────
|
|
469
|
+
else if (resource === 'analytics') {
|
|
470
|
+
const from = this.getNodeParameter('from', i);
|
|
471
|
+
const to = this.getNodeParameter('to', i);
|
|
472
|
+
const endpoint = operation === 'engagement' ? 'engagement' : 'summary';
|
|
473
|
+
responseData = await this.helpers.httpRequest({
|
|
474
|
+
method: 'GET', url: `${BASE_URL}/api/analytics/${endpoint}`,
|
|
475
|
+
qs: { from, to }, json: true,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
// ── Schedules ──────────────────────────────────────────
|
|
479
|
+
else if (resource === 'schedule') {
|
|
480
|
+
if (operation === 'list') {
|
|
481
|
+
responseData = await this.helpers.httpRequest({
|
|
482
|
+
method: 'GET', url: `${BASE_URL}/api/schedules`, json: true,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else if (operation === 'delete') {
|
|
486
|
+
const id = this.getNodeParameter('scheduleId', i);
|
|
487
|
+
responseData = await this.helpers.httpRequest({
|
|
488
|
+
method: 'DELETE', url: `${BASE_URL}/api/schedules/${id}`, json: true,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const items = Array.isArray(responseData) ? responseData : [responseData];
|
|
493
|
+
for (const item of items) {
|
|
494
|
+
returnData.push({ json: item });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return [returnData];
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
exports.BulkPublish = BulkPublish;
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-bulkpublish",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n community node for BulkPublish — publish to 11 social media platforms from n8n workflows",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n-nodes",
|
|
8
|
+
"social-media",
|
|
9
|
+
"publishing",
|
|
10
|
+
"scheduling",
|
|
11
|
+
"automation",
|
|
12
|
+
"facebook",
|
|
13
|
+
"instagram",
|
|
14
|
+
"twitter",
|
|
15
|
+
"tiktok",
|
|
16
|
+
"linkedin",
|
|
17
|
+
"youtube",
|
|
18
|
+
"threads",
|
|
19
|
+
"bluesky",
|
|
20
|
+
"pinterest"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "BulkPublish",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/azeemkafridi/n8n-nodes-bulkpublish"
|
|
27
|
+
},
|
|
28
|
+
"main": "dist/nodes/BulkPublish/BulkPublish.node.js",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc && gulp build:icons",
|
|
31
|
+
"lint": "eslint nodes/**/*.ts credentials/**/*.ts",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"n8n": {
|
|
35
|
+
"n8nNodesApiVersion": 1,
|
|
36
|
+
"credentials": [
|
|
37
|
+
"dist/credentials/BulkPublishApi.credentials.js"
|
|
38
|
+
],
|
|
39
|
+
"nodes": [
|
|
40
|
+
"dist/nodes/BulkPublish/BulkPublish.node.js"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"n8n-workflow": "^1.0.0",
|
|
45
|
+
"typescript": "^5.5.0",
|
|
46
|
+
"gulp": "^5.0.0",
|
|
47
|
+
"@types/node": "^22.0.0"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist"
|
|
51
|
+
]
|
|
52
|
+
}
|