n8n-nodes-postwire 0.2.3 → 0.3.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 +92 -28
- package/dist/credentials/PostWireApi.credentials.js +1 -6
- package/dist/credentials/PostWireApi.credentials.js.map +1 -0
- package/dist/nodes/PostWire/Description.d.ts +9 -0
- package/dist/nodes/PostWire/Description.js +669 -0
- package/dist/nodes/PostWire/Description.js.map +1 -0
- package/dist/nodes/PostWire/GenericFunctions.d.ts +38 -0
- package/dist/nodes/PostWire/GenericFunctions.js +357 -0
- package/dist/nodes/PostWire/GenericFunctions.js.map +1 -0
- package/dist/nodes/PostWire/LegacyDescription.d.ts +2 -0
- package/dist/nodes/PostWire/LegacyDescription.js +157 -0
- package/dist/nodes/PostWire/LegacyDescription.js.map +1 -0
- package/dist/nodes/PostWire/PostWire.node.d.ts +6 -1
- package/dist/nodes/PostWire/PostWire.node.js +404 -271
- package/dist/nodes/PostWire/PostWire.node.js.map +1 -0
- package/dist/nodes/PostWire/PostWire.node.json +14 -0
- package/dist/package.json +67 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +20 -10
- package/index.js +0 -3
|
@@ -2,24 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PostWire = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
];
|
|
22
|
-
const needOf = (value) => PLATFORMS.find((p) => p.value === value)?.mediaNeed ?? null;
|
|
5
|
+
const Description_1 = require("./Description");
|
|
6
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
7
|
+
const LegacyDescription_1 = require("./LegacyDescription");
|
|
8
|
+
const LEGACY = {
|
|
9
|
+
me: { resource: 'account', operation: 'get' },
|
|
10
|
+
publish: { resource: 'post', operation: 'publish', contentMode: 'text' },
|
|
11
|
+
generateAndPublish: { resource: 'post', operation: 'publish', contentMode: 'smart' },
|
|
12
|
+
generate: { resource: 'post', operation: 'generate' },
|
|
13
|
+
schedule: { resource: 'post', operation: 'schedule', contentMode: 'text' },
|
|
14
|
+
};
|
|
15
|
+
function locatorValue(v) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (v && typeof v === 'object')
|
|
18
|
+
return String((_a = v.value) !== null && _a !== void 0 ? _a : '').trim();
|
|
19
|
+
return String(v !== null && v !== void 0 ? v : '').trim();
|
|
20
|
+
}
|
|
23
21
|
class PostWire {
|
|
24
22
|
constructor() {
|
|
25
23
|
this.description = {
|
|
@@ -27,310 +25,445 @@ class PostWire {
|
|
|
27
25
|
name: 'postWire',
|
|
28
26
|
icon: { light: 'file:postwire.svg', dark: 'file:postwire.dark.svg' },
|
|
29
27
|
group: ['output'],
|
|
30
|
-
version: 1,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
version: [1, 2],
|
|
29
|
+
defaultVersion: 2,
|
|
30
|
+
subtitle: '={{$parameter["operation"] + ($parameter["resource"] ? ": " + $parameter["resource"] : "")}}',
|
|
31
|
+
description: 'Publish one idea natively to TikTok, Instagram, YouTube, LinkedIn, X, Bluesky and more — a different post written for each network',
|
|
34
32
|
usableAsTool: true,
|
|
35
33
|
defaults: { name: 'PostWire' },
|
|
36
34
|
inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
37
35
|
outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
38
36
|
credentials: [{ name: 'postWireApi', required: true }],
|
|
39
|
-
requestDefaults: {
|
|
40
|
-
baseURL: BASE,
|
|
41
|
-
headers: { 'Content-Type': 'application/json' },
|
|
42
|
-
},
|
|
43
37
|
properties: [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
description: 'Plan, usage this month and connected accounts',
|
|
54
|
-
action: 'Get account',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: 'Publish',
|
|
58
|
-
value: 'publish',
|
|
59
|
-
description: 'Publish to one or more networks in a single call',
|
|
60
|
-
action: 'Publish to social networks',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'Schedule',
|
|
64
|
-
value: 'schedule',
|
|
65
|
-
description: 'Queue a post for a later time',
|
|
66
|
-
action: 'Schedule a post',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'Write a Post per Network',
|
|
70
|
-
value: 'generate',
|
|
71
|
-
description: 'One prompt in, a native draft out for each network — right length, hashtags and format per platform. Does not publish.',
|
|
72
|
-
action: 'Write a native post for each network',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'Write and Publish',
|
|
76
|
-
value: 'generateAndPublish',
|
|
77
|
-
description: 'Write a native draft per network, then publish them',
|
|
78
|
-
action: 'Write and publish in one step',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
default: 'generateAndPublish',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
displayName: 'Networks',
|
|
85
|
-
name: 'platforms',
|
|
86
|
-
type: 'multiOptions',
|
|
87
|
-
options: PLATFORMS.map((p) => ({ name: p.name, value: p.value })),
|
|
88
|
-
default: ['x', 'linkedin'],
|
|
89
|
-
required: true,
|
|
90
|
-
description: 'Connect these in the PostWire dashboard first. TikTok, Instagram and YouTube are one OAuth click — PostWire carries the platform approvals, so there is no app review on your side.',
|
|
91
|
-
displayOptions: {
|
|
92
|
-
show: { operation: ['generate', 'publish', 'generateAndPublish', 'schedule'] },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
displayName: 'Idea',
|
|
97
|
-
name: 'prompt',
|
|
98
|
-
type: 'string',
|
|
99
|
-
typeOptions: { rows: 3 },
|
|
100
|
-
default: '',
|
|
101
|
-
required: true,
|
|
102
|
-
placeholder: 'We shipped scheduling today — one idea, a native post per network',
|
|
103
|
-
description: 'What you want to say. More detail gives a better draft.',
|
|
104
|
-
displayOptions: { show: { operation: ['generate', 'generateAndPublish'] } },
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
displayName: 'Text',
|
|
108
|
-
name: 'text',
|
|
109
|
-
type: 'string',
|
|
110
|
-
typeOptions: { rows: 3 },
|
|
111
|
-
default: '',
|
|
112
|
-
description: 'Posted exactly as written to every selected network',
|
|
113
|
-
displayOptions: { show: { operation: ['publish', 'schedule'] } },
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
displayName: 'Media URL',
|
|
117
|
-
name: 'mediaUrl',
|
|
118
|
-
type: 'string',
|
|
119
|
-
default: '',
|
|
120
|
-
placeholder: 'https://example.com/clip.mp4',
|
|
121
|
-
description: 'A direct link to an .mp4 or an image. TikTok and YouTube will not accept a post without a video, and Instagram needs a photo or a video. A Google Drive share link does not work — it returns a web page, not a file.',
|
|
122
|
-
displayOptions: {
|
|
123
|
-
show: { operation: ['generate', 'publish', 'generateAndPublish', 'schedule'] },
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
displayName: 'Publish At',
|
|
128
|
-
name: 'runAt',
|
|
129
|
-
type: 'dateTime',
|
|
130
|
-
default: '',
|
|
131
|
-
required: true,
|
|
132
|
-
description: 'When to publish. PostWire validates the post now, so a missing video is caught here rather than at 7am tomorrow.',
|
|
133
|
-
displayOptions: { show: { operation: ['schedule'] } },
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
displayName: 'Options',
|
|
137
|
-
name: 'options',
|
|
138
|
-
type: 'collection',
|
|
139
|
-
placeholder: 'Add option',
|
|
140
|
-
default: {},
|
|
141
|
-
displayOptions: {
|
|
142
|
-
show: { operation: ['generate', 'publish', 'generateAndPublish', 'schedule'] },
|
|
143
|
-
},
|
|
144
|
-
options: [
|
|
145
|
-
{
|
|
146
|
-
displayName: 'Media Type',
|
|
147
|
-
name: 'mediaType',
|
|
148
|
-
type: 'options',
|
|
149
|
-
default: 'auto',
|
|
150
|
-
options: [
|
|
151
|
-
{ name: 'Detect From the URL', value: 'auto' },
|
|
152
|
-
{ name: 'Video', value: 'video' },
|
|
153
|
-
{ name: 'Image', value: 'image' },
|
|
154
|
-
],
|
|
155
|
-
description: 'Only needed when the link has no file extension — a signed CDN URL or a share link. Detection reads the extension and, when there is none, follows what the chosen networks require.',
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
displayName: 'Brand',
|
|
159
|
-
name: 'brandId',
|
|
160
|
-
type: 'string',
|
|
161
|
-
default: '',
|
|
162
|
-
description: 'Publish as a specific brand. Leave empty for your only brand.',
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
displayName: 'Brand Voice',
|
|
166
|
-
name: 'brandVoice',
|
|
167
|
-
type: 'string',
|
|
168
|
-
default: '',
|
|
169
|
-
description: 'A sentence describing how you want to sound, applied to every generated draft',
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
displayName: 'Title (YouTube)',
|
|
173
|
-
name: 'title',
|
|
174
|
-
type: 'string',
|
|
175
|
-
default: '',
|
|
176
|
-
description: 'Overrides the generated YouTube title',
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
displayName: 'Visibility',
|
|
180
|
-
name: 'privacy',
|
|
181
|
-
type: 'options',
|
|
182
|
-
options: [
|
|
183
|
-
{ name: 'Private', value: 'private' },
|
|
184
|
-
{ name: 'Public', value: 'public' },
|
|
185
|
-
{ name: 'Unlisted', value: 'unlisted' },
|
|
186
|
-
],
|
|
187
|
-
default: 'public',
|
|
188
|
-
description: 'Used by TikTok and YouTube',
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
},
|
|
38
|
+
...LegacyDescription_1.legacyProperties,
|
|
39
|
+
Description_1.resourceProperty,
|
|
40
|
+
...Description_1.operationProperties,
|
|
41
|
+
...Description_1.postProperties,
|
|
42
|
+
...Description_1.scheduledPostProperties,
|
|
43
|
+
...Description_1.brandProperties,
|
|
44
|
+
...Description_1.connectionProperties,
|
|
45
|
+
...Description_1.accountProperties,
|
|
46
|
+
...Description_1.mediaProperties,
|
|
192
47
|
],
|
|
193
48
|
};
|
|
49
|
+
this.methods = {
|
|
50
|
+
listSearch: {
|
|
51
|
+
async searchBrands(filter) {
|
|
52
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/brands');
|
|
53
|
+
const brands = res.brands || [];
|
|
54
|
+
const f = (filter || '').toLowerCase();
|
|
55
|
+
return {
|
|
56
|
+
results: brands
|
|
57
|
+
.filter((b) => !f || String(b.name).toLowerCase().includes(f))
|
|
58
|
+
.map((b) => {
|
|
59
|
+
var _a;
|
|
60
|
+
return ({
|
|
61
|
+
name: `${b.name}${((_a = b.platforms) === null || _a === void 0 ? void 0 : _a.length) ? ` (${b.platforms.map(GenericFunctions_1.nameOf).join(', ')})` : ''}`,
|
|
62
|
+
value: b.id,
|
|
63
|
+
});
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
194
69
|
}
|
|
195
70
|
async execute() {
|
|
71
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
196
72
|
const items = this.getInputData();
|
|
197
73
|
const out = [];
|
|
74
|
+
const version = this.getNode().typeVersion;
|
|
198
75
|
for (let i = 0; i < items.length; i++) {
|
|
199
76
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
77
|
+
let resource;
|
|
78
|
+
let operation;
|
|
79
|
+
let legacyMode;
|
|
80
|
+
if (version === 1) {
|
|
81
|
+
const legacy = LEGACY[this.getNodeParameter('operation', i)];
|
|
82
|
+
if (!legacy)
|
|
83
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Unknown operation', { itemIndex: i });
|
|
84
|
+
({ resource, operation } = legacy);
|
|
85
|
+
legacyMode = legacy.contentMode;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
resource = this.getNodeParameter('resource', i);
|
|
89
|
+
operation = this.getNodeParameter('operation', i);
|
|
90
|
+
}
|
|
91
|
+
const push = (json) => out.push({ json, pairedItem: { item: i } });
|
|
92
|
+
if (resource === 'account') {
|
|
93
|
+
const me = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/me', undefined, undefined, i);
|
|
94
|
+
const simplify = version === 1 ? false : this.getNodeParameter('simplify', i, true);
|
|
95
|
+
if (!simplify) {
|
|
96
|
+
push(me);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const usage = me.usage || {};
|
|
100
|
+
const brands = me.brands || {};
|
|
101
|
+
push({
|
|
102
|
+
email: me.email,
|
|
103
|
+
plan: me.plan,
|
|
104
|
+
posts_used: usage.posts,
|
|
105
|
+
posts_limit: usage.limit,
|
|
106
|
+
brands_used: brands.used,
|
|
107
|
+
brands_limit: brands.limit,
|
|
108
|
+
connected: (me.connections || []).map((c) => c.platform),
|
|
109
|
+
verified: me.verified,
|
|
110
|
+
account_id: me.account_id,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (resource === 'media') {
|
|
115
|
+
const field = this.getNodeParameter('binaryPropertyName', i);
|
|
116
|
+
push(await GenericFunctions_1.uploadBinary.call(this, i, field));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (resource === 'brand') {
|
|
120
|
+
if (operation === 'getAll') {
|
|
121
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/brands', undefined, undefined, i);
|
|
122
|
+
let list = res.brands || [];
|
|
123
|
+
if (!this.getNodeParameter('returnAll', i))
|
|
124
|
+
list = list.slice(0, this.getNodeParameter('limit', i));
|
|
125
|
+
for (const b of list)
|
|
126
|
+
push(b);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (operation === 'create') {
|
|
130
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'POST', '/api/brands', {
|
|
131
|
+
name: this.getNodeParameter('name', i),
|
|
132
|
+
}, undefined, i);
|
|
133
|
+
push(res.brand || res);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const id = locatorValue(this.getNodeParameter('brand', i));
|
|
137
|
+
if (operation === 'update') {
|
|
138
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'PATCH', `/api/brands/${encodeURIComponent(id)}`, {
|
|
139
|
+
name: this.getNodeParameter('name', i),
|
|
140
|
+
}, undefined, i);
|
|
141
|
+
push(res.brand || res);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (operation === 'delete') {
|
|
145
|
+
await GenericFunctions_1.apiRequest.call(this, 'DELETE', `/api/brands/${encodeURIComponent(id)}`, undefined, undefined, i);
|
|
146
|
+
push({ deleted: true, id });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (resource === 'connection') {
|
|
151
|
+
if (operation === 'getAll') {
|
|
152
|
+
const me = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/me', undefined, undefined, i);
|
|
153
|
+
const brandName = new Map((me.brand_list || []).map((b) => [b.id, b.name]));
|
|
154
|
+
let list = (me.connections || []).map((c) => {
|
|
155
|
+
var _a;
|
|
156
|
+
return ({
|
|
157
|
+
...c,
|
|
158
|
+
network: (0, GenericFunctions_1.nameOf)(c.platform),
|
|
159
|
+
brand_name: (_a = brandName.get(c.brand_id)) !== null && _a !== void 0 ? _a : null,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
if (!this.getNodeParameter('returnAll', i))
|
|
163
|
+
list = list.slice(0, this.getNodeParameter('limit', i));
|
|
164
|
+
for (const c of list)
|
|
165
|
+
push(c);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (operation === 'checkHealth') {
|
|
169
|
+
const platform = this.getNodeParameter('platform', i);
|
|
170
|
+
push(await GenericFunctions_1.apiRequest.call(this, 'GET', `/api/connect/${platform}/health`, undefined, undefined, i));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (operation === 'createConnectLink') {
|
|
174
|
+
const platform = this.getNodeParameter('platform', i, '');
|
|
175
|
+
push(await GenericFunctions_1.apiRequest.call(this, 'POST', '/api/connect-link', platform ? { platform } : {}, undefined, i));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (resource === 'scheduledPost') {
|
|
180
|
+
if (operation === 'getAll') {
|
|
181
|
+
const filters = this.getNodeParameter('filters', i, {});
|
|
182
|
+
const qs = {};
|
|
183
|
+
if (filters.from)
|
|
184
|
+
qs.from = new Date(filters.from).toISOString();
|
|
185
|
+
if (filters.to)
|
|
186
|
+
qs.to = new Date(filters.to).toISOString();
|
|
187
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/schedule', undefined, qs, i);
|
|
188
|
+
let list = res.scheduled || [];
|
|
189
|
+
if (filters.status)
|
|
190
|
+
list = list.filter((r) => r.status === filters.status);
|
|
191
|
+
if (!this.getNodeParameter('returnAll', i))
|
|
192
|
+
list = list.slice(0, this.getNodeParameter('limit', i));
|
|
193
|
+
for (const r of list)
|
|
194
|
+
push(r);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const id = encodeURIComponent(this.getNodeParameter('scheduledPostId', i));
|
|
198
|
+
if (operation === 'delete') {
|
|
199
|
+
await GenericFunctions_1.apiRequest.call(this, 'DELETE', `/api/schedule/${id}`, undefined, undefined, i);
|
|
200
|
+
push({ deleted: true, id: decodeURIComponent(id) });
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (operation === 'update') {
|
|
204
|
+
const fields = this.getNodeParameter('updateFields', i, {});
|
|
205
|
+
const body = {};
|
|
206
|
+
if (fields.runAt)
|
|
207
|
+
body.run_at = new Date(fields.runAt).toISOString();
|
|
208
|
+
if (fields.label !== undefined)
|
|
209
|
+
body.label = fields.label;
|
|
210
|
+
if (!Object.keys(body).length)
|
|
211
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Nothing to update', {
|
|
212
|
+
itemIndex: i,
|
|
213
|
+
description: "Add 'Publish At' or 'Label' under 'Update Fields'.",
|
|
214
|
+
});
|
|
215
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'PATCH', `/api/schedule/${id}`, body, undefined, i);
|
|
216
|
+
push(res.scheduled || res);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (resource !== 'post')
|
|
221
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Unknown operation', { itemIndex: i });
|
|
222
|
+
if (operation === 'getStatus') {
|
|
223
|
+
const platform = this.getNodeParameter('statusPlatform', i);
|
|
224
|
+
const id = String((_a = this.getNodeParameter('postId', i)) !== null && _a !== void 0 ? _a : '').trim();
|
|
225
|
+
push(await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/post/status', undefined, { platform, id }, i));
|
|
208
226
|
continue;
|
|
209
227
|
}
|
|
210
|
-
const platforms = this.getNodeParameter('platforms', i);
|
|
228
|
+
const platforms = (this.getNodeParameter('platforms', i, []) || []).filter(Boolean);
|
|
211
229
|
const opts = this.getNodeParameter('options', i, {});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Pick at least one network.', {
|
|
230
|
+
if (!platforms.length)
|
|
231
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "No network selected in 'Networks'", {
|
|
215
232
|
itemIndex: i,
|
|
233
|
+
description: 'Pick at least one network. They must be connected in the PostWire dashboard.',
|
|
216
234
|
});
|
|
235
|
+
if (operation === 'planWeek') {
|
|
236
|
+
const video = platforms.filter((p) => (0, GenericFunctions_1.needOf)(p));
|
|
237
|
+
if (video.length)
|
|
238
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${video.map(GenericFunctions_1.nameOf).join(', ')} cannot be in a week plan`, {
|
|
239
|
+
itemIndex: i,
|
|
240
|
+
description: 'A week plan is text only. Schedule video posts one by one with Post → Schedule.',
|
|
241
|
+
});
|
|
242
|
+
const tz = (opts.timezone || this.getTimezone() || 'UTC').trim();
|
|
243
|
+
let offset;
|
|
244
|
+
try {
|
|
245
|
+
offset = (0, GenericFunctions_1.tzOffsetMinutes)(tz);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `'Timezone' is not a known time zone: "${tz}"`, {
|
|
249
|
+
itemIndex: i,
|
|
250
|
+
description: 'Use an IANA name such as America/New_York or Europe/Madrid.',
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const res = await GenericFunctions_1.apiRequest.call(this, 'POST', '/api/week', {
|
|
254
|
+
topic: this.getNodeParameter('topic', i),
|
|
255
|
+
platforms,
|
|
256
|
+
days: this.getNodeParameter('days', i, 5),
|
|
257
|
+
start_hour: this.getNodeParameter('hour', i, 10),
|
|
258
|
+
tz_offset_minutes: offset,
|
|
259
|
+
brand_voice: opts.brandVoice || undefined,
|
|
260
|
+
}, undefined, i);
|
|
261
|
+
push({ ...res, timezone: tz });
|
|
262
|
+
continue;
|
|
217
263
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
264
|
+
const mediaSource = version === 1
|
|
265
|
+
? (this.getNodeParameter('mediaUrl', i, '') ? 'url' : 'none')
|
|
266
|
+
: this.getNodeParameter('mediaSource', i, 'none');
|
|
267
|
+
let mediaUrl = '';
|
|
268
|
+
let declared = opts.mediaType || 'auto';
|
|
269
|
+
if (mediaSource === 'url')
|
|
270
|
+
mediaUrl = String(this.getNodeParameter('mediaUrl', i, '') || '').trim();
|
|
271
|
+
if (mediaSource === 'binary') {
|
|
272
|
+
const field = this.getNodeParameter('binaryPropertyName', i);
|
|
273
|
+
const hosted = await GenericFunctions_1.uploadBinary.call(this, i, field);
|
|
274
|
+
mediaUrl = hosted.media_url;
|
|
275
|
+
const ct = String(hosted.content_type || this.helpers.assertBinaryData(i, field).mimeType || '');
|
|
276
|
+
if (declared === 'auto')
|
|
277
|
+
declared = ct.startsWith('video/') ? 'video' : ct.startsWith('image/') ? 'image' : 'auto';
|
|
278
|
+
}
|
|
279
|
+
if (operation === 'generate') {
|
|
280
|
+
const prompt = this.getNodeParameter('prompt', i);
|
|
281
|
+
const { drafts, missing } = await GenericFunctions_1.generateDrafts.call(this, i, {
|
|
282
|
+
prompt,
|
|
283
|
+
platforms,
|
|
284
|
+
media_url: mediaUrl || undefined,
|
|
285
|
+
brand_voice: opts.brandVoice || undefined,
|
|
286
|
+
});
|
|
287
|
+
push({
|
|
288
|
+
drafts,
|
|
289
|
+
...(missing.length ? { missing } : {}),
|
|
290
|
+
platforms,
|
|
291
|
+
media_url: mediaUrl || undefined,
|
|
292
|
+
preview: (0, GenericFunctions_1.draftsPreview)(drafts),
|
|
293
|
+
});
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const contentMode = legacyMode !== null && legacyMode !== void 0 ? legacyMode : this.getNodeParameter('contentMode', i, 'smart');
|
|
297
|
+
const kind = (0, GenericFunctions_1.classifyMedia)(mediaUrl, declared);
|
|
298
|
+
const blocked = (0, GenericFunctions_1.mediaBlocks)(platforms, kind);
|
|
244
299
|
if (blocked.length) {
|
|
245
|
-
const wantsVideo = blocked.some((p) => needOf(p) === 'video');
|
|
246
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${blocked.join(' and ')} will not accept a post without ${wantsVideo ? 'a video' : 'an image or video'}
|
|
300
|
+
const wantsVideo = blocked.some((p) => (0, GenericFunctions_1.needOf)(p) === 'video');
|
|
301
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${blocked.map(GenericFunctions_1.nameOf).join(' and ')} will not accept a post without ${wantsVideo ? 'a video' : 'an image or video'}`, {
|
|
302
|
+
itemIndex: i,
|
|
303
|
+
description: `Set 'Media' to a video URL or to "Binary File", or remove ${blocked.length > 1 ? 'those networks' : 'that network'} from 'Networks'.`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
if (mediaSource === 'url' && mediaUrl && !opts.skipMediaCheck) {
|
|
307
|
+
const why = await (0, GenericFunctions_1.checkMediaUrl)(this, mediaUrl, platforms);
|
|
308
|
+
if (why)
|
|
309
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The media link cannot be published', {
|
|
310
|
+
itemIndex: i,
|
|
311
|
+
description: why,
|
|
312
|
+
});
|
|
247
313
|
}
|
|
248
|
-
|
|
249
|
-
// one the chosen networks need — that is the only reading of it that can succeed.
|
|
250
|
-
const treatAsVideo = kind === 'video' || (kind === 'unknown' && platforms.some((p) => needOf(p) === 'video'));
|
|
251
|
-
const videoUrl = mediaUrl && treatAsVideo ? mediaUrl : undefined;
|
|
252
|
-
const photoUrl = mediaUrl && !treatAsVideo ? mediaUrl : undefined;
|
|
314
|
+
const treatAsVideo = kind === 'video' || (kind === 'unknown' && platforms.some((p) => (0, GenericFunctions_1.needOf)(p) === 'video'));
|
|
253
315
|
let perPlatform;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const gen =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
media_url: mediaUrl || undefined,
|
|
263
|
-
brand_voice: opts.brandVoice || undefined,
|
|
264
|
-
},
|
|
265
|
-
json: true,
|
|
266
|
-
}));
|
|
316
|
+
let text;
|
|
317
|
+
if (contentMode === 'smart') {
|
|
318
|
+
const gen = await GenericFunctions_1.generateDrafts.call(this, i, {
|
|
319
|
+
prompt: this.getNodeParameter('prompt', i),
|
|
320
|
+
platforms,
|
|
321
|
+
media_url: mediaUrl || undefined,
|
|
322
|
+
brand_voice: opts.brandVoice || undefined,
|
|
323
|
+
});
|
|
267
324
|
perPlatform = gen.drafts;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
325
|
+
}
|
|
326
|
+
else if (contentMode === 'drafts') {
|
|
327
|
+
const raw = this.getNodeParameter('drafts', i);
|
|
328
|
+
let parsed = raw;
|
|
329
|
+
if (typeof raw === 'string') {
|
|
330
|
+
try {
|
|
331
|
+
parsed = JSON.parse(raw);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "'Drafts' is not valid JSON", {
|
|
335
|
+
itemIndex: i,
|
|
336
|
+
description: 'Map the output of Write Drafts with {{ $json.drafts }}, or write an object like { "x": { "text": "…" } }.',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
271
339
|
}
|
|
340
|
+
const obj = (parsed && typeof parsed === 'object' ? parsed : {});
|
|
341
|
+
perPlatform = (obj.drafts && typeof obj.drafts === 'object' ? obj.drafts : obj);
|
|
342
|
+
const missingDraft = platforms.filter((p) => { var _a; return !((_a = perPlatform === null || perPlatform === void 0 ? void 0 : perPlatform[p]) === null || _a === void 0 ? void 0 : _a.text); });
|
|
343
|
+
if (missingDraft.length && !mediaUrl)
|
|
344
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No draft for ${missingDraft.map(GenericFunctions_1.nameOf).join(', ')}`, {
|
|
345
|
+
itemIndex: i,
|
|
346
|
+
description: `'Drafts' needs a { "text": … } entry for every network in 'Networks'. Remove those networks or write drafts for them.`,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
text = this.getNodeParameter('text', i, '');
|
|
272
351
|
}
|
|
352
|
+
const brandId = locatorValue((_b = opts.brand) !== null && _b !== void 0 ? _b : opts.brandId);
|
|
273
353
|
const body = {
|
|
274
354
|
platforms,
|
|
275
355
|
per_platform: perPlatform,
|
|
276
|
-
text: perPlatform ? undefined :
|
|
277
|
-
photo_url:
|
|
278
|
-
video_url:
|
|
356
|
+
text: perPlatform ? undefined : text,
|
|
357
|
+
photo_url: mediaUrl && !treatAsVideo ? mediaUrl : undefined,
|
|
358
|
+
video_url: mediaUrl && treatAsVideo ? mediaUrl : undefined,
|
|
279
359
|
title: opts.title || undefined,
|
|
280
360
|
privacy: opts.privacy || undefined,
|
|
281
|
-
|
|
361
|
+
subreddit: opts.subreddit || undefined,
|
|
362
|
+
brand_id: brandId || undefined,
|
|
282
363
|
};
|
|
283
364
|
if (operation === 'schedule') {
|
|
284
365
|
const runAt = this.getNodeParameter('runAt', i);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
366
|
+
if (!runAt)
|
|
367
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "'Publish At' is empty", {
|
|
368
|
+
itemIndex: i,
|
|
369
|
+
description: 'Pick a date and time, or map one with an expression such as {{ $json.publish_at }}.',
|
|
370
|
+
});
|
|
371
|
+
const scheduled = await GenericFunctions_1.apiRequest.call(this, 'POST', '/api/schedule', {
|
|
372
|
+
...body,
|
|
373
|
+
run_at: new Date(runAt).toISOString(),
|
|
374
|
+
label: opts.label || undefined,
|
|
375
|
+
}, undefined, i);
|
|
376
|
+
push({ ...(scheduled.scheduled || scheduled), drafts: perPlatform });
|
|
292
377
|
continue;
|
|
293
378
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
379
|
+
if (opts.dryRun) {
|
|
380
|
+
const me = await GenericFunctions_1.apiRequest.call(this, 'GET', '/api/me', undefined, undefined, i);
|
|
381
|
+
const conns = (me.connections || []).filter((c) => !brandId || c.brand_id === brandId);
|
|
382
|
+
for (const p of platforms) {
|
|
383
|
+
const draft = (perPlatform === null || perPlatform === void 0 ? void 0 : perPlatform[p]) || {};
|
|
384
|
+
const t = String((_d = (_c = draft.text) !== null && _c !== void 0 ? _c : text) !== null && _d !== void 0 ? _d : '');
|
|
385
|
+
const issues = [];
|
|
386
|
+
if (!conns.some((c) => c.platform === p))
|
|
387
|
+
issues.push(`${(0, GenericFunctions_1.nameOf)(p)} is not connected${brandId ? ' to this brand' : ''}`);
|
|
388
|
+
const max = (0, GenericFunctions_1.maxOf)(p);
|
|
389
|
+
if (max && t.length > max)
|
|
390
|
+
issues.push(perPlatform
|
|
391
|
+
? `${t.length} characters, over the ${max} limit`
|
|
392
|
+
: `${t.length} characters; PostWire will shorten it to ${max}`);
|
|
393
|
+
if (!t.trim() && !mediaUrl)
|
|
394
|
+
issues.push('no text');
|
|
395
|
+
push({
|
|
396
|
+
dryRun: true,
|
|
397
|
+
platform: p,
|
|
398
|
+
ready: !issues.some((s) => !s.includes('will shorten')),
|
|
399
|
+
text: t,
|
|
400
|
+
title: (_e = draft.title) !== null && _e !== void 0 ? _e : body.title,
|
|
401
|
+
tags: draft.tags,
|
|
402
|
+
media_url: mediaUrl || undefined,
|
|
403
|
+
issues,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
let idem = String(opts.idempotencyKey || '').trim();
|
|
409
|
+
if (!idem && contentMode === 'smart') {
|
|
410
|
+
const exec = (_f = this.getExecutionId) === null || _f === void 0 ? void 0 : _f.call(this);
|
|
411
|
+
if (exec)
|
|
412
|
+
idem = `n8n:${exec}:${this.getNode().name}:${i}`;
|
|
413
|
+
}
|
|
414
|
+
if (idem)
|
|
415
|
+
body.idempotency_key = idem.slice(0, 200);
|
|
416
|
+
const response = await GenericFunctions_1.apiRequest.call(this, 'POST', '/api/post', body, undefined, i);
|
|
302
417
|
const results = (response.results || [response]).filter(Boolean);
|
|
303
418
|
for (const res of results) {
|
|
304
|
-
|
|
419
|
+
const extra = {};
|
|
420
|
+
if (res.ok === false) {
|
|
421
|
+
const hint = (0, GenericFunctions_1.hintFor)(res.platform, String(res.error || ''), res.code);
|
|
422
|
+
if (hint)
|
|
423
|
+
extra.hint = hint;
|
|
424
|
+
}
|
|
425
|
+
if (response.notice)
|
|
426
|
+
extra.notice = response.notice;
|
|
427
|
+
push({ ...res, ...extra });
|
|
305
428
|
}
|
|
306
|
-
|
|
307
|
-
// in the output, so a scheduled flow could publish nothing for weeks and never say so.
|
|
308
|
-
// If not one network went out, that is a failed execution.
|
|
309
|
-
const failed = results.filter((res) => res?.ok === false);
|
|
429
|
+
const failed = results.filter((res) => (res === null || res === void 0 ? void 0 : res.ok) === false);
|
|
310
430
|
if (results.length && failed.length === results.length) {
|
|
311
431
|
const why = failed
|
|
312
|
-
.map((f) => `${f.platform || '?'}: ${f.error || f.code || 'failed'}`)
|
|
432
|
+
.map((f) => `${(0, GenericFunctions_1.nameOf)(f.platform || '?')}: ${f.error || f.code || 'failed'}`)
|
|
313
433
|
.join('; ');
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
(
|
|
434
|
+
const first = failed[0];
|
|
435
|
+
const fix = (0, GenericFunctions_1.hintFor)(first.platform, String(first.error || ''), first.code) ||
|
|
436
|
+
(0, GenericFunctions_1.describeFailure)(first.code, first).description;
|
|
437
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `PostWire published to none of the ${results.length} selected network(s)`, { itemIndex: i, description: `${why}${fix ? ` — ${fix}` : ''}` });
|
|
317
438
|
}
|
|
318
439
|
}
|
|
319
440
|
catch (error) {
|
|
320
441
|
if (this.continueOnFail()) {
|
|
321
|
-
out.push({
|
|
442
|
+
out.push({
|
|
443
|
+
json: {
|
|
444
|
+
error: error.message,
|
|
445
|
+
description: (_g = error.description) !== null && _g !== void 0 ? _g : undefined,
|
|
446
|
+
},
|
|
447
|
+
pairedItem: { item: i },
|
|
448
|
+
});
|
|
322
449
|
continue;
|
|
323
450
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
451
|
+
const e = error;
|
|
452
|
+
if (error instanceof n8n_workflow_1.NodeApiError)
|
|
453
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), {}, {
|
|
454
|
+
message: e.message,
|
|
455
|
+
description: (_h = e.description) !== null && _h !== void 0 ? _h : undefined,
|
|
456
|
+
httpCode: (_j = e.httpCode) !== null && _j !== void 0 ? _j : undefined,
|
|
457
|
+
itemIndex: i,
|
|
458
|
+
});
|
|
459
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, {
|
|
460
|
+
itemIndex: i,
|
|
461
|
+
description: (_k = error.description) !== null && _k !== void 0 ? _k : undefined,
|
|
462
|
+
});
|
|
331
463
|
}
|
|
332
464
|
}
|
|
333
465
|
return [out];
|
|
334
466
|
}
|
|
335
467
|
}
|
|
336
468
|
exports.PostWire = PostWire;
|
|
469
|
+
//# sourceMappingURL=PostWire.node.js.map
|