n8n-nodes-sofia-post 0.0.0-stage → 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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sofia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # n8n-nodes-sofia-post
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Publish, schedule and follow your social media posts with [Sofia](https://www.sofia-post.com) from any n8n workflow.
4
+
5
+ [Installation](#installation) · [Credentials](#credentials) · [Operations](#operations) · [Compatibility](#compatibility) · [Resources](#resources)
6
+
7
+ ## Installation
8
+
9
+ Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation, with the package name `n8n-nodes-sofia-post`.
10
+
11
+ ## Credentials
12
+
13
+ The **Sofia API** credential takes a personal key:
14
+
15
+ 1. In Sofia, open **Settings › API › Personal access** and create a key. It starts with `sofia_pk_live_`.
16
+ 2. In n8n, create a **Sofia API** credential and paste the key in **Personal Key**. Leave **API URL** as is.
17
+
18
+ One key acts for you on every Workspace you belong to, so one credential reaches all your establishments. If Sofia refuses the key (it was revoked or deleted), create a new one and paste it in the same credential.
19
+
20
+ ## Operations
21
+
22
+ ### Sofia
23
+
24
+ - **Post › Create**: publish now, schedule, or save a draft on one or several connected accounts. One post goes to each chosen account, with the same text. Media are public URLs separated by commas; `.mp4`, `.mov`, `.webm` and `.m4v` are sent as videos, anything else as images. **Schedule At** is read in the workflow's time zone.
25
+ - **Post › Get**: read a post and the state of each network. An unknown post returns no item.
26
+
27
+ The output of a post carries its `id`, `status`, `platforms`, the first `permalink`, every network's refusal on one line in `errors`, and the detail of each network in `targets`.
28
+
29
+ ### Sofia Trigger
30
+
31
+ - **Post Published**: a post went live on at least one network.
32
+ - **Post Failed**: a network refused a post, even if others published it.
33
+
34
+ The trigger polls Sofia. It starts with the posts that appear after the workflow is activated: posts published before are not replayed. **Fetch Test Event** shows the latest post so you can map its fields. Choose an establishment to watch only that one.
35
+
36
+ ## Compatibility
37
+
38
+ Built and tested against `n8n-workflow` 2.x, with n8n nodes API version 1.
39
+
40
+ ## Resources
41
+
42
+ - [Sofia developer documentation](https://www.sofia-post.com/developers)
43
+ - [n8n community nodes documentation](https://docs.n8n.io/integrations/#community-nodes)
44
+
45
+ This package is built from the Sofia monorepo and published from this repository by GitHub Actions, with an npm provenance statement.
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // apps/sophia-n8n/credentials/SofiaApi.credentials.ts
21
+ var SofiaApi_credentials_exports = {};
22
+ __export(SofiaApi_credentials_exports, {
23
+ SofiaApi: () => SofiaApi
24
+ });
25
+ module.exports = __toCommonJS(SofiaApi_credentials_exports);
26
+ var SofiaApi = class {
27
+ constructor() {
28
+ this.name = "sofiaApi";
29
+ this.displayName = "Sofia API";
30
+ this.icon = { light: "file:../icons/sofia.svg", dark: "file:../icons/sofia.svg" };
31
+ this.documentationUrl = "https://github.com/badjilounes/n8n-nodes-sofia-post?tab=readme-ov-file#credentials";
32
+ this.properties = [
33
+ {
34
+ displayName: "Personal Key",
35
+ name: "apiKey",
36
+ type: "string",
37
+ typeOptions: { password: true },
38
+ required: true,
39
+ default: "",
40
+ placeholder: "sofia_pk_live_\u2026",
41
+ description: "Create it in Sofia \u203A Settings \u203A API"
42
+ },
43
+ {
44
+ displayName: "API URL",
45
+ name: "apiUrl",
46
+ type: "string",
47
+ default: "https://api.sofia-post.com/api/v1",
48
+ description: "Leave as is, unless Sofia support gives you another address"
49
+ }
50
+ ];
51
+ this.authenticate = {
52
+ type: "generic",
53
+ properties: {
54
+ headers: {
55
+ Authorization: "=Bearer {{$credentials.apiKey}}"
56
+ }
57
+ }
58
+ };
59
+ this.test = {
60
+ request: {
61
+ baseURL: "={{$credentials.apiUrl}}",
62
+ url: "/me"
63
+ }
64
+ };
65
+ }
66
+ };
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ SofiaApi
70
+ });
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512"><defs><style>.cls-1{fill:url(#sofia-gradient);}.cls-1,.cls-2{stroke-width:0px;}.cls-2{fill:#fff;}</style><linearGradient id="sofia-gradient" x1="256" y1="0" x2="256" y2="512" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ce762e"/><stop offset=".27" stop-color="#cf682c"/><stop offset=".67" stop-color="#d1592b"/><stop offset="1" stop-color="#d2542b"/></linearGradient></defs><rect class="cls-1" y="0" width="512" height="512"/><path class="cls-2" d="m340.9,272.48c-165.63,0-168.14-.42-170.22-.42h-.83c-41.72-3.34-73.85-38.8-73.85-80.94,0-45.06,36.3-81.36,81.36-81.36h175.23c35.05,0,63.42,28.37,63.42,63.83v33.79h-238.23c-9.6-.42-15.85-7.51-15.85-16.27s6.68-15.85,15.44-16.27h206.1v-.83c0-17.94-13.77-31.71-31.71-31.71h-174.39c-26.7,0-48.81,21.69-48.81,48.81,0,25.03,18.77,45.06,42.56,48.4,166.47,0,168.14.42,171.06.42,41.72,3.34,73.85,39.22,73.85,80.94,0,45.06-36.3,81.36-80.94,81.36h-175.65c-35.05,0-63.42-28.37-63.42-63.42v-34.21h239.06c20.44,1.25,20.44,31.29,0,32.54h-206.52v.83c0,17.94,13.77,31.71,31.71,31.71h174.81c26.28,0,48.81-20.86,48.81-48.81,0-25.03-18.77-45.06-42.97-48.4h0Z"/></svg>
@@ -0,0 +1,574 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // apps/sophia-n8n/nodes/Sofia/Sofia.node.ts
21
+ var Sofia_node_exports = {};
22
+ __export(Sofia_node_exports, {
23
+ Sofia: () => Sofia
24
+ });
25
+ module.exports = __toCommonJS(Sofia_node_exports);
26
+ var import_n8n_workflow2 = require("n8n-workflow");
27
+
28
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/read-fields.ts
29
+ function asFields(value) {
30
+ return typeof value === "object" && value ? value : {};
31
+ }
32
+ function listData(body) {
33
+ const data = asFields(body)["data"];
34
+ return Array.isArray(data) ? data.map(asFields) : [];
35
+ }
36
+ function textOf(fields, key) {
37
+ const value = fields[key];
38
+ return typeof value === "string" && value !== "" ? value : void 0;
39
+ }
40
+ function textListOf(fields, key) {
41
+ const value = fields[key];
42
+ const items = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
43
+ return items.map((item) => String(item).trim()).filter((item) => item !== "");
44
+ }
45
+
46
+ // libs/sophia-n8n/feature-n8n/src/lib/accounts/list-accounts.ts
47
+ var PLATFORM_NAMES = {
48
+ instagram: "Instagram",
49
+ facebook: "Facebook",
50
+ x: "X",
51
+ linkedin: "LinkedIn",
52
+ tiktok: "TikTok"
53
+ };
54
+ function labelOf(account) {
55
+ const platform = String(account["platform"]);
56
+ const handle = textOf(account, "handle");
57
+ const who = handle === void 0 ? textOf(account, "name") ?? account["id"] : `@${handle}`;
58
+ return `${PLATFORM_NAMES[platform] ?? platform} \u2014 ${String(who)}`;
59
+ }
60
+ async function listAccounts(api, establishmentId) {
61
+ const items = listData(
62
+ await api.get(`/establishments/${encodeURIComponent(establishmentId)}/accounts`)
63
+ );
64
+ return items.map((account) => ({
65
+ id: String(account["id"]),
66
+ platform: String(account["platform"]),
67
+ label: labelOf(account),
68
+ status: String(account["status"])
69
+ }));
70
+ }
71
+
72
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/post-output.ts
73
+ function toTarget(fields) {
74
+ return {
75
+ platform: String(fields["platform"]),
76
+ accountId: String(fields["accountId"] ?? ""),
77
+ state: String(fields["state"]),
78
+ publishedAt: textOf(fields, "publishedAt"),
79
+ permalink: textOf(fields, "permalink"),
80
+ error: textOf(fields, "error")
81
+ };
82
+ }
83
+ function joined(values) {
84
+ const present = values.filter((value) => value !== void 0);
85
+ return present.length > 0 ? present.join(" \xB7 ") : void 0;
86
+ }
87
+ function toPostOutput(body) {
88
+ const post = asFields(body);
89
+ const rawTargets = post["targets"];
90
+ const targets = (Array.isArray(rawTargets) ? rawTargets : []).map(asFields).map(toTarget);
91
+ return {
92
+ id: String(post["id"]),
93
+ status: String(post["status"]),
94
+ establishmentId: String(post["establishmentId"] ?? ""),
95
+ createdAt: textOf(post, "createdAt"),
96
+ scheduledAt: textOf(post, "scheduledAt"),
97
+ platforms: targets.map((target) => target.platform).join(", "),
98
+ permalink: targets.find((target) => target.permalink !== void 0)?.permalink,
99
+ errors: joined(
100
+ targets.map((t) => t.error === void 0 ? void 0 : `${t.platform}: ${t.error}`)
101
+ ),
102
+ targets
103
+ };
104
+ }
105
+
106
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/create/post-body.ts
107
+ var VIDEO_EXTENSIONS = [".mp4", ".mov", ".webm", ".m4v"];
108
+ function pathnameOf(url) {
109
+ try {
110
+ return new URL(url).pathname.toLowerCase();
111
+ } catch {
112
+ throw new PostInputError(`\xAB ${url} \xBB is not a media address: it must start with https://.`);
113
+ }
114
+ }
115
+ function inferMediaType(url) {
116
+ const pathname = pathnameOf(url);
117
+ return VIDEO_EXTENSIONS.some((extension) => pathname.endsWith(extension)) ? "video" : "image";
118
+ }
119
+ var PostInputError = class extends Error {
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = "PostInputError";
123
+ }
124
+ };
125
+ function timing(input) {
126
+ if (input.draft && input.scheduledAt !== void 0) {
127
+ throw new PostInputError(
128
+ "A draft has no date: leave \xAB Schedule at \xBB empty, or turn \xAB Draft \xBB off."
129
+ );
130
+ }
131
+ if (input.draft) {
132
+ return { status: "draft" };
133
+ }
134
+ return input.scheduledAt === void 0 ? {} : { scheduledAt: input.scheduledAt };
135
+ }
136
+ function buildPostBody(input) {
137
+ const media = input.mediaUrls.map((url) => ({ url, type: inferMediaType(url) }));
138
+ return {
139
+ establishmentId: input.establishmentId,
140
+ targets: input.accounts.map((account) => ({
141
+ platform: account.platform,
142
+ accountId: account.id,
143
+ text: input.text,
144
+ ...media.length > 0 ? { media } : {},
145
+ ...input.format === void 0 ? {} : { format: input.format }
146
+ })),
147
+ ...timing(input)
148
+ };
149
+ }
150
+
151
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/create/schedule-instant.ts
152
+ var HAS_OFFSET = /(Z|[+-]\d{2}:?\d{2})$/i;
153
+ function wallClockMs(instantMs, timeZone) {
154
+ const parts = new Intl.DateTimeFormat("en-US", {
155
+ timeZone,
156
+ hourCycle: "h23",
157
+ year: "numeric",
158
+ month: "2-digit",
159
+ day: "2-digit",
160
+ hour: "2-digit",
161
+ minute: "2-digit",
162
+ second: "2-digit"
163
+ }).formatToParts(new Date(instantMs));
164
+ const part = (type) => Number(parts.find((p) => p.type === type)?.value);
165
+ return Date.UTC(
166
+ part("year"),
167
+ part("month") - 1,
168
+ part("day"),
169
+ part("hour"),
170
+ part("minute"),
171
+ part("second")
172
+ );
173
+ }
174
+ function scheduleInstant(value, timeZone) {
175
+ if (HAS_OFFSET.test(value)) {
176
+ return new Date(value).toISOString();
177
+ }
178
+ const asIfUtc = Date.parse(`${value}Z`);
179
+ if (Number.isNaN(asIfUtc)) {
180
+ return value;
181
+ }
182
+ const firstGuess = asIfUtc - (wallClockMs(asIfUtc, timeZone) - asIfUtc);
183
+ const instant = asIfUtc - (wallClockMs(firstGuess, timeZone) - firstGuess);
184
+ return new Date(instant).toISOString();
185
+ }
186
+
187
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/create/create-post.ts
188
+ var TRUE_VALUES = ["true", "yes", "1"];
189
+ function isTrue(value) {
190
+ return value === true || TRUE_VALUES.includes(String(value).toLowerCase());
191
+ }
192
+ function required(input, key, label) {
193
+ const value = textOf(input, key);
194
+ if (value === void 0) {
195
+ throw new PostInputError(`\xAB ${label} \xBB is required.`);
196
+ }
197
+ return value;
198
+ }
199
+ function chosenAccounts(all, ids) {
200
+ if (ids.length === 0) {
201
+ throw new PostInputError("Choose at least one account to publish on.");
202
+ }
203
+ return ids.map((id) => {
204
+ const account = all.find((candidate) => candidate.id === id);
205
+ if (account === void 0) {
206
+ throw new PostInputError(`The account ${id} is not connected to this establishment.`);
207
+ }
208
+ return account;
209
+ });
210
+ }
211
+ async function createPost(api, input, timeZone) {
212
+ const scheduledAt = textOf(input, "scheduledAt");
213
+ const establishmentId = required(input, "establishmentId", "Establishment");
214
+ const text = required(input, "text", "Text");
215
+ const accounts2 = await listAccounts(api, establishmentId);
216
+ const body = buildPostBody({
217
+ establishmentId,
218
+ accounts: chosenAccounts(accounts2, textListOf(input, "accountIds")),
219
+ text,
220
+ mediaUrls: textListOf(input, "mediaUrls"),
221
+ format: textOf(input, "format"),
222
+ scheduledAt: scheduledAt === void 0 ? void 0 : scheduleInstant(scheduledAt, timeZone),
223
+ draft: isTrue(input["draft"])
224
+ });
225
+ return toPostOutput(await api.post("/posts", body));
226
+ }
227
+
228
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/sofia-api-gateway.ts
229
+ var SofiaApiError = class extends Error {
230
+ constructor(status, code, message, retryAfterSeconds) {
231
+ super(message);
232
+ this.status = status;
233
+ this.code = code;
234
+ this.retryAfterSeconds = retryAfterSeconds;
235
+ this.name = "SofiaApiError";
236
+ }
237
+ };
238
+
239
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/get-post.ts
240
+ async function getPost(api, postId) {
241
+ try {
242
+ return [toPostOutput(await api.get(`/posts/${encodeURIComponent(postId)}`))];
243
+ } catch (error) {
244
+ if (error instanceof SofiaApiError && error.status === 404) {
245
+ return [];
246
+ }
247
+ throw error;
248
+ }
249
+ }
250
+
251
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/error-from-response.ts
252
+ var isText = (value) => typeof value === "string";
253
+ function retryAfter(header) {
254
+ const seconds = Number(header);
255
+ return header !== void 0 && Number.isFinite(seconds) ? seconds : void 0;
256
+ }
257
+ function errorFromResponse(status, body, retryAfterHeader) {
258
+ const envelope = asFields(asFields(body)["error"]);
259
+ const code = isText(envelope["code"]) ? envelope["code"] : `http_${status}`;
260
+ const message = isText(envelope["message"]) ? envelope["message"] : `Sofia API answered ${status}.`;
261
+ return new SofiaApiError(status, code, message, retryAfter(retryAfterHeader));
262
+ }
263
+
264
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/n8n-sofia-api-gateway.ts
265
+ var CREDENTIAL_NAME = "sofiaApi";
266
+ var DEFAULT_API_URL = "https://api.sofia-post.com/api/v1";
267
+ function definedOnly(query = {}) {
268
+ return Object.fromEntries(
269
+ Object.entries(query).filter((entry) => entry[1] !== void 0)
270
+ );
271
+ }
272
+ function headerOf(headers = {}, name) {
273
+ const value = headers[name];
274
+ return value === void 0 ? void 0 : String(value);
275
+ }
276
+ var N8nSofiaApiGateway = class _N8nSofiaApiGateway {
277
+ constructor(functions, apiUrl) {
278
+ this.functions = functions;
279
+ this.apiUrl = apiUrl;
280
+ }
281
+ static async connect(functions) {
282
+ const credentials = asFields(await functions.getCredentials(CREDENTIAL_NAME));
283
+ const apiUrl = typeof credentials["apiUrl"] === "string" ? credentials["apiUrl"] : "";
284
+ return new _N8nSofiaApiGateway(functions, apiUrl === "" ? DEFAULT_API_URL : apiUrl);
285
+ }
286
+ get(path, query) {
287
+ return this.send({ method: "GET", url: path, qs: definedOnly(query) });
288
+ }
289
+ post(path, body) {
290
+ return this.send({ method: "POST", url: path, body: asFields(body) });
291
+ }
292
+ async send(request) {
293
+ const response = await this.functions.helpers.httpRequestWithAuthentication.call(
294
+ this.functions,
295
+ CREDENTIAL_NAME,
296
+ {
297
+ ...request,
298
+ baseURL: this.apiUrl,
299
+ json: true,
300
+ headers: { Accept: "application/json" },
301
+ returnFullResponse: true,
302
+ ignoreHttpStatusErrors: true
303
+ }
304
+ );
305
+ if (response.statusCode >= 400) {
306
+ throw errorFromResponse(
307
+ response.statusCode,
308
+ response.body,
309
+ headerOf(response.headers, "retry-after")
310
+ );
311
+ }
312
+ return response.body;
313
+ }
314
+ };
315
+
316
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/to-node-error.ts
317
+ var import_n8n_workflow = require("n8n-workflow");
318
+ var DEFAULT_RETRY_SECONDS = 60;
319
+ function descriptionOf(error) {
320
+ if (error.status === 401) {
321
+ return "Create a new key in Sofia \u203A Settings \u203A API and paste it in the Sofia API credential.";
322
+ }
323
+ if (error.status === 429) {
324
+ return `Try again in ${error.retryAfterSeconds ?? DEFAULT_RETRY_SECONDS} s.`;
325
+ }
326
+ return `Sofia error code: ${error.code}`;
327
+ }
328
+ function toNodeError(node, error, itemIndex) {
329
+ if (error instanceof SofiaApiError) {
330
+ const envelope = { code: error.code, message: error.message };
331
+ return new import_n8n_workflow.NodeApiError(node, envelope, {
332
+ message: error.message,
333
+ description: descriptionOf(error),
334
+ httpCode: String(error.status),
335
+ itemIndex
336
+ });
337
+ }
338
+ if (error instanceof import_n8n_workflow.NodeError) {
339
+ return error;
340
+ }
341
+ if (error instanceof Error) {
342
+ return new import_n8n_workflow.NodeOperationError(node, error, { itemIndex });
343
+ }
344
+ return new import_n8n_workflow.NodeOperationError(node, String(error), { itemIndex });
345
+ }
346
+
347
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/execute-sofia.ts
348
+ function createInput(functions, itemIndex) {
349
+ const additional = functions.getNodeParameter("additionalFields", itemIndex, {});
350
+ return {
351
+ ...additional,
352
+ establishmentId: functions.getNodeParameter("establishmentId", itemIndex),
353
+ accountIds: functions.getNodeParameter("accountIds", itemIndex),
354
+ text: functions.getNodeParameter("text", itemIndex)
355
+ };
356
+ }
357
+ var OPERATIONS = {
358
+ create: async (functions, api, itemIndex) => [
359
+ await createPost(api, createInput(functions, itemIndex), functions.getTimezone())
360
+ ],
361
+ get: (functions, api, itemIndex) => getPost(api, String(functions.getNodeParameter("postId", itemIndex)))
362
+ };
363
+ async function runItem(functions, api, itemIndex) {
364
+ const operation = String(functions.getNodeParameter("operation", itemIndex));
365
+ const run = OPERATIONS[operation];
366
+ if (run === void 0) {
367
+ throw new Error(`The operation \xAB ${operation} \xBB is not supported.`);
368
+ }
369
+ const posts = await run(functions, api, itemIndex);
370
+ return posts.map((post) => ({ json: post, pairedItem: { item: itemIndex } }));
371
+ }
372
+ async function executeSofia(functions) {
373
+ const api = await N8nSofiaApiGateway.connect(functions);
374
+ const output = [];
375
+ const items = functions.getInputData();
376
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
377
+ try {
378
+ output.push(...await runItem(functions, api, itemIndex));
379
+ } catch (error) {
380
+ const nodeError = toNodeError(functions.getNode(), error, itemIndex);
381
+ if (!functions.continueOnFail()) {
382
+ throw nodeError;
383
+ }
384
+ output.push({ json: { error: nodeError.message }, pairedItem: { item: itemIndex } });
385
+ }
386
+ }
387
+ return [output];
388
+ }
389
+
390
+ // libs/sophia-n8n/feature-n8n/src/lib/establishments/list-establishments.ts
391
+ async function listEstablishments(api) {
392
+ const items = listData(await api.get("/establishments"));
393
+ return items.map((item) => ({ id: String(item["id"]), name: String(item["name"] ?? "") }));
394
+ }
395
+
396
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/load-options.ts
397
+ async function establishments() {
398
+ try {
399
+ const api = await N8nSofiaApiGateway.connect(this);
400
+ const items = await listEstablishments(api);
401
+ return items.map((item) => ({ name: item.name, value: item.id }));
402
+ } catch (error) {
403
+ throw toNodeError(this.getNode(), error);
404
+ }
405
+ }
406
+ async function accounts() {
407
+ const establishmentId = textOf({ id: this.getCurrentNodeParameter("establishmentId") }, "id");
408
+ if (establishmentId === void 0) {
409
+ return [];
410
+ }
411
+ try {
412
+ const api = await N8nSofiaApiGateway.connect(this);
413
+ const items = await listAccounts(api, establishmentId);
414
+ return items.map((item) => ({ name: item.label, value: item.id, description: item.status }));
415
+ } catch (error) {
416
+ throw toNodeError(this.getNode(), error);
417
+ }
418
+ }
419
+ var sofiaLoadOptions = {
420
+ getEstablishments: establishments,
421
+ getAccounts: accounts
422
+ };
423
+
424
+ // apps/sophia-n8n/nodes/Sofia/post-properties.ts
425
+ var showFor = (operation) => ({ show: { resource: ["post"], operation: [operation] } });
426
+ var postOperation = {
427
+ displayName: "Operation",
428
+ name: "operation",
429
+ type: "options",
430
+ noDataExpression: true,
431
+ displayOptions: { show: { resource: ["post"] } },
432
+ options: [
433
+ {
434
+ name: "Create",
435
+ value: "create",
436
+ description: "Publish now, schedule or save a draft on the chosen accounts",
437
+ action: "Create a post"
438
+ },
439
+ {
440
+ name: "Get",
441
+ value: "get",
442
+ description: "Read a post and the state of each network",
443
+ action: "Get a post"
444
+ }
445
+ ],
446
+ default: "create"
447
+ };
448
+ var postFields = [
449
+ {
450
+ displayName: "Establishment Name or ID",
451
+ name: "establishmentId",
452
+ type: "options",
453
+ typeOptions: { loadOptionsMethod: "getEstablishments" },
454
+ required: true,
455
+ default: "",
456
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
457
+ displayOptions: showFor("create")
458
+ },
459
+ {
460
+ displayName: "Account Names or IDs",
461
+ name: "accountIds",
462
+ type: "multiOptions",
463
+ typeOptions: { loadOptionsMethod: "getAccounts", loadOptionsDependsOn: ["establishmentId"] },
464
+ required: true,
465
+ default: [],
466
+ description: 'One post per chosen account. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
467
+ displayOptions: showFor("create")
468
+ },
469
+ {
470
+ displayName: "Text",
471
+ name: "text",
472
+ type: "string",
473
+ typeOptions: { rows: 4 },
474
+ required: true,
475
+ default: "",
476
+ description: "The caption, the same on every chosen account",
477
+ displayOptions: showFor("create")
478
+ },
479
+ {
480
+ displayName: "Additional Fields",
481
+ name: "additionalFields",
482
+ type: "collection",
483
+ placeholder: "Add Field",
484
+ default: {},
485
+ displayOptions: showFor("create"),
486
+ options: [
487
+ {
488
+ displayName: "Draft",
489
+ name: "draft",
490
+ type: "boolean",
491
+ default: false,
492
+ description: "Whether to save the post as a draft in Sofia instead of publishing it"
493
+ },
494
+ {
495
+ displayName: "Format",
496
+ name: "format",
497
+ type: "options",
498
+ options: [
499
+ { name: "Carousel", value: "carousel" },
500
+ { name: "Post", value: "post" },
501
+ { name: "Reel", value: "reel" },
502
+ { name: "Story", value: "story" }
503
+ ],
504
+ default: "post",
505
+ description: "Leave out for the format the network would use"
506
+ },
507
+ {
508
+ displayName: "Media URLs",
509
+ name: "mediaUrls",
510
+ type: "string",
511
+ default: "",
512
+ placeholder: "https://example.com/photo.jpg, https://example.com/clip.mp4",
513
+ description: "Public addresses of the images or videos, separated by commas. A .mp4, .mov, .webm or .m4v address is sent as a video."
514
+ },
515
+ {
516
+ displayName: "Schedule At",
517
+ name: "scheduledAt",
518
+ type: "dateTime",
519
+ default: "",
520
+ description: "When to publish, in the workflow's time zone. Leave out to publish now. Not allowed on a draft."
521
+ }
522
+ ]
523
+ },
524
+ {
525
+ displayName: "Post ID",
526
+ name: "postId",
527
+ type: "string",
528
+ required: true,
529
+ default: "",
530
+ description: "The ID returned when the post was created",
531
+ displayOptions: showFor("get")
532
+ }
533
+ ];
534
+
535
+ // apps/sophia-n8n/nodes/Sofia/Sofia.node.ts
536
+ var Sofia = class {
537
+ constructor() {
538
+ this.description = {
539
+ displayName: "Sofia",
540
+ name: "sofia",
541
+ // The Sofia mark sits on its own orange square: the same file reads on both themes.
542
+ icon: { light: "file:../../icons/sofia.svg", dark: "file:../../icons/sofia.svg" },
543
+ group: ["output"],
544
+ version: 1,
545
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
546
+ description: "Publish, schedule and read social media posts with Sofia",
547
+ defaults: { name: "Sofia" },
548
+ usableAsTool: true,
549
+ inputs: [import_n8n_workflow2.NodeConnectionTypes.Main],
550
+ outputs: [import_n8n_workflow2.NodeConnectionTypes.Main],
551
+ credentials: [{ name: "sofiaApi", required: true }],
552
+ properties: [
553
+ {
554
+ displayName: "Resource",
555
+ name: "resource",
556
+ type: "options",
557
+ noDataExpression: true,
558
+ options: [{ name: "Post", value: "post" }],
559
+ default: "post"
560
+ },
561
+ postOperation,
562
+ ...postFields
563
+ ]
564
+ };
565
+ this.methods = { loadOptions: sofiaLoadOptions };
566
+ }
567
+ async execute() {
568
+ return executeSofia(this);
569
+ }
570
+ };
571
+ // Annotate the CommonJS export names for ESM import in node:
572
+ 0 && (module.exports = {
573
+ Sofia
574
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "node": "n8n-nodes-sofia-post.sofia",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Marketing & Content"],
6
+ "resources": {
7
+ "credentialDocumentation": [
8
+ {
9
+ "url": "https://github.com/badjilounes/n8n-nodes-sofia-post?tab=readme-ov-file#credentials"
10
+ }
11
+ ],
12
+ "primaryDocumentation": [
13
+ {
14
+ "url": "https://github.com/badjilounes/n8n-nodes-sofia-post?tab=readme-ov-file"
15
+ }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // apps/sophia-n8n/nodes/SofiaTrigger/SofiaTrigger.node.ts
21
+ var SofiaTrigger_node_exports = {};
22
+ __export(SofiaTrigger_node_exports, {
23
+ SofiaTrigger: () => SofiaTrigger
24
+ });
25
+ module.exports = __toCommonJS(SofiaTrigger_node_exports);
26
+ var import_n8n_workflow2 = require("n8n-workflow");
27
+
28
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/read-fields.ts
29
+ function asFields(value) {
30
+ return typeof value === "object" && value ? value : {};
31
+ }
32
+ function listData(body) {
33
+ const data = asFields(body)["data"];
34
+ return Array.isArray(data) ? data.map(asFields) : [];
35
+ }
36
+ function textOf(fields, key) {
37
+ const value = fields[key];
38
+ return typeof value === "string" && value !== "" ? value : void 0;
39
+ }
40
+
41
+ // libs/sophia-n8n/feature-n8n/src/lib/accounts/list-accounts.ts
42
+ var PLATFORM_NAMES = {
43
+ instagram: "Instagram",
44
+ facebook: "Facebook",
45
+ x: "X",
46
+ linkedin: "LinkedIn",
47
+ tiktok: "TikTok"
48
+ };
49
+ function labelOf(account) {
50
+ const platform = String(account["platform"]);
51
+ const handle = textOf(account, "handle");
52
+ const who = handle === void 0 ? textOf(account, "name") ?? account["id"] : `@${handle}`;
53
+ return `${PLATFORM_NAMES[platform] ?? platform} \u2014 ${String(who)}`;
54
+ }
55
+ async function listAccounts(api, establishmentId) {
56
+ const items = listData(
57
+ await api.get(`/establishments/${encodeURIComponent(establishmentId)}/accounts`)
58
+ );
59
+ return items.map((account) => ({
60
+ id: String(account["id"]),
61
+ platform: String(account["platform"]),
62
+ label: labelOf(account),
63
+ status: String(account["status"])
64
+ }));
65
+ }
66
+
67
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/post-output.ts
68
+ function toTarget(fields) {
69
+ return {
70
+ platform: String(fields["platform"]),
71
+ accountId: String(fields["accountId"] ?? ""),
72
+ state: String(fields["state"]),
73
+ publishedAt: textOf(fields, "publishedAt"),
74
+ permalink: textOf(fields, "permalink"),
75
+ error: textOf(fields, "error")
76
+ };
77
+ }
78
+ function joined(values) {
79
+ const present = values.filter((value) => value !== void 0);
80
+ return present.length > 0 ? present.join(" \xB7 ") : void 0;
81
+ }
82
+ function toPostOutput(body) {
83
+ const post = asFields(body);
84
+ const rawTargets = post["targets"];
85
+ const targets = (Array.isArray(rawTargets) ? rawTargets : []).map(asFields).map(toTarget);
86
+ return {
87
+ id: String(post["id"]),
88
+ status: String(post["status"]),
89
+ establishmentId: String(post["establishmentId"] ?? ""),
90
+ createdAt: textOf(post, "createdAt"),
91
+ scheduledAt: textOf(post, "scheduledAt"),
92
+ platforms: targets.map((target) => target.platform).join(", "),
93
+ permalink: targets.find((target) => target.permalink !== void 0)?.permalink,
94
+ errors: joined(
95
+ targets.map((t) => t.error === void 0 ? void 0 : `${t.platform}: ${t.error}`)
96
+ ),
97
+ targets
98
+ };
99
+ }
100
+
101
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/sofia-api-gateway.ts
102
+ var SofiaApiError = class extends Error {
103
+ constructor(status, code, message, retryAfterSeconds) {
104
+ super(message);
105
+ this.status = status;
106
+ this.code = code;
107
+ this.retryAfterSeconds = retryAfterSeconds;
108
+ this.name = "SofiaApiError";
109
+ }
110
+ };
111
+
112
+ // libs/sophia-n8n/feature-n8n/src/lib/gateway/error-from-response.ts
113
+ var isText = (value) => typeof value === "string";
114
+ function retryAfter(header) {
115
+ const seconds = Number(header);
116
+ return header !== void 0 && Number.isFinite(seconds) ? seconds : void 0;
117
+ }
118
+ function errorFromResponse(status, body, retryAfterHeader) {
119
+ const envelope = asFields(asFields(body)["error"]);
120
+ const code = isText(envelope["code"]) ? envelope["code"] : `http_${status}`;
121
+ const message = isText(envelope["message"]) ? envelope["message"] : `Sofia API answered ${status}.`;
122
+ return new SofiaApiError(status, code, message, retryAfter(retryAfterHeader));
123
+ }
124
+
125
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/n8n-sofia-api-gateway.ts
126
+ var CREDENTIAL_NAME = "sofiaApi";
127
+ var DEFAULT_API_URL = "https://api.sofia-post.com/api/v1";
128
+ function definedOnly(query = {}) {
129
+ return Object.fromEntries(
130
+ Object.entries(query).filter((entry) => entry[1] !== void 0)
131
+ );
132
+ }
133
+ function headerOf(headers = {}, name) {
134
+ const value = headers[name];
135
+ return value === void 0 ? void 0 : String(value);
136
+ }
137
+ var N8nSofiaApiGateway = class _N8nSofiaApiGateway {
138
+ constructor(functions, apiUrl) {
139
+ this.functions = functions;
140
+ this.apiUrl = apiUrl;
141
+ }
142
+ static async connect(functions) {
143
+ const credentials = asFields(await functions.getCredentials(CREDENTIAL_NAME));
144
+ const apiUrl = typeof credentials["apiUrl"] === "string" ? credentials["apiUrl"] : "";
145
+ return new _N8nSofiaApiGateway(functions, apiUrl === "" ? DEFAULT_API_URL : apiUrl);
146
+ }
147
+ get(path, query) {
148
+ return this.send({ method: "GET", url: path, qs: definedOnly(query) });
149
+ }
150
+ post(path, body) {
151
+ return this.send({ method: "POST", url: path, body: asFields(body) });
152
+ }
153
+ async send(request) {
154
+ const response = await this.functions.helpers.httpRequestWithAuthentication.call(
155
+ this.functions,
156
+ CREDENTIAL_NAME,
157
+ {
158
+ ...request,
159
+ baseURL: this.apiUrl,
160
+ json: true,
161
+ headers: { Accept: "application/json" },
162
+ returnFullResponse: true,
163
+ ignoreHttpStatusErrors: true
164
+ }
165
+ );
166
+ if (response.statusCode >= 400) {
167
+ throw errorFromResponse(
168
+ response.statusCode,
169
+ response.body,
170
+ headerOf(response.headers, "retry-after")
171
+ );
172
+ }
173
+ return response.body;
174
+ }
175
+ };
176
+
177
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/to-node-error.ts
178
+ var import_n8n_workflow = require("n8n-workflow");
179
+ var DEFAULT_RETRY_SECONDS = 60;
180
+ function descriptionOf(error) {
181
+ if (error.status === 401) {
182
+ return "Create a new key in Sofia \u203A Settings \u203A API and paste it in the Sofia API credential.";
183
+ }
184
+ if (error.status === 429) {
185
+ return `Try again in ${error.retryAfterSeconds ?? DEFAULT_RETRY_SECONDS} s.`;
186
+ }
187
+ return `Sofia error code: ${error.code}`;
188
+ }
189
+ function toNodeError(node, error, itemIndex) {
190
+ if (error instanceof SofiaApiError) {
191
+ const envelope = { code: error.code, message: error.message };
192
+ return new import_n8n_workflow.NodeApiError(node, envelope, {
193
+ message: error.message,
194
+ description: descriptionOf(error),
195
+ httpCode: String(error.status),
196
+ itemIndex
197
+ });
198
+ }
199
+ if (error instanceof import_n8n_workflow.NodeError) {
200
+ return error;
201
+ }
202
+ if (error instanceof Error) {
203
+ return new import_n8n_workflow.NodeOperationError(node, error, { itemIndex });
204
+ }
205
+ return new import_n8n_workflow.NodeOperationError(node, String(error), { itemIndex });
206
+ }
207
+
208
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/list-posts-by-status.ts
209
+ var PAGE_SIZE = "100";
210
+ async function listPostsByStatus(api, input) {
211
+ const page = await api.get("/posts", {
212
+ status: input.status,
213
+ establishmentId: input.establishmentId,
214
+ limit: PAGE_SIZE
215
+ });
216
+ return listData(page).map(toPostOutput);
217
+ }
218
+
219
+ // libs/sophia-n8n/feature-n8n/src/lib/posts/new-posts.ts
220
+ function newPostsSince(previouslySeen, page) {
221
+ const seenIds = page.map((post) => post.id);
222
+ if (previouslySeen === void 0) {
223
+ return { fresh: [], seenIds };
224
+ }
225
+ const seen = new Set(previouslySeen);
226
+ return { fresh: page.filter((post) => !seen.has(post.id)), seenIds };
227
+ }
228
+
229
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/poll-sofia.ts
230
+ var STATUS_OF_EVENT = {
231
+ postPublished: "published",
232
+ postFailed: "failed"
233
+ };
234
+ function asItems(posts) {
235
+ return posts.length === 0 ? null : [posts.map((post) => ({ json: post }))];
236
+ }
237
+ function queryOf(functions) {
238
+ const event = String(functions.getNodeParameter("event"));
239
+ const establishmentId = textOf(
240
+ { establishmentId: functions.getNodeParameter("establishmentId", "") },
241
+ "establishmentId"
242
+ );
243
+ return { status: STATUS_OF_EVENT[event] ?? "published", establishmentId };
244
+ }
245
+ async function readPage(functions, query) {
246
+ try {
247
+ return await listPostsByStatus(await N8nSofiaApiGateway.connect(functions), query);
248
+ } catch (error) {
249
+ throw toNodeError(functions.getNode(), error);
250
+ }
251
+ }
252
+ async function pollSofia(functions) {
253
+ const query = queryOf(functions);
254
+ const page = await readPage(functions, query);
255
+ if (functions.getMode() === "manual") {
256
+ return asItems(page.slice(0, 1));
257
+ }
258
+ const state = functions.getWorkflowStaticData("node");
259
+ const key = `${query.status}:${query.establishmentId ?? "*"}`;
260
+ const { fresh, seenIds } = newPostsSince(state.query === key ? state.seenIds : void 0, page);
261
+ state.query = key;
262
+ state.seenIds = seenIds;
263
+ return asItems(fresh);
264
+ }
265
+
266
+ // libs/sophia-n8n/feature-n8n/src/lib/establishments/list-establishments.ts
267
+ async function listEstablishments(api) {
268
+ const items = listData(await api.get("/establishments"));
269
+ return items.map((item) => ({ id: String(item["id"]), name: String(item["name"] ?? "") }));
270
+ }
271
+
272
+ // libs/sophia-n8n/feature-n8n/src/lib/n8n/load-options.ts
273
+ async function establishments() {
274
+ try {
275
+ const api = await N8nSofiaApiGateway.connect(this);
276
+ const items = await listEstablishments(api);
277
+ return items.map((item) => ({ name: item.name, value: item.id }));
278
+ } catch (error) {
279
+ throw toNodeError(this.getNode(), error);
280
+ }
281
+ }
282
+ async function accounts() {
283
+ const establishmentId = textOf({ id: this.getCurrentNodeParameter("establishmentId") }, "id");
284
+ if (establishmentId === void 0) {
285
+ return [];
286
+ }
287
+ try {
288
+ const api = await N8nSofiaApiGateway.connect(this);
289
+ const items = await listAccounts(api, establishmentId);
290
+ return items.map((item) => ({ name: item.label, value: item.id, description: item.status }));
291
+ } catch (error) {
292
+ throw toNodeError(this.getNode(), error);
293
+ }
294
+ }
295
+ var sofiaLoadOptions = {
296
+ getEstablishments: establishments,
297
+ getAccounts: accounts
298
+ };
299
+
300
+ // apps/sophia-n8n/nodes/SofiaTrigger/SofiaTrigger.node.ts
301
+ var SofiaTrigger = class {
302
+ constructor() {
303
+ this.description = {
304
+ displayName: "Sofia Trigger",
305
+ name: "sofiaTrigger",
306
+ // The Sofia mark sits on its own orange square: the same file reads on both themes.
307
+ icon: { light: "file:../../icons/sofia.svg", dark: "file:../../icons/sofia.svg" },
308
+ group: ["trigger"],
309
+ version: 1,
310
+ subtitle: '={{$parameter["event"]}}',
311
+ description: "Starts the workflow when a Sofia post is published or fails",
312
+ defaults: { name: "Sofia Trigger" },
313
+ polling: true,
314
+ inputs: [],
315
+ outputs: [import_n8n_workflow2.NodeConnectionTypes.Main],
316
+ credentials: [{ name: "sofiaApi", required: true }],
317
+ properties: [
318
+ {
319
+ displayName: "Event",
320
+ name: "event",
321
+ type: "options",
322
+ noDataExpression: true,
323
+ options: [
324
+ {
325
+ name: "Post Failed",
326
+ value: "postFailed",
327
+ description: "A network refused the post, even if others published it"
328
+ },
329
+ {
330
+ name: "Post Published",
331
+ value: "postPublished",
332
+ description: "A post went live on at least one network"
333
+ }
334
+ ],
335
+ default: "postPublished"
336
+ },
337
+ {
338
+ displayName: "Establishment Name or ID",
339
+ name: "establishmentId",
340
+ type: "options",
341
+ typeOptions: { loadOptionsMethod: "getEstablishments" },
342
+ default: "",
343
+ description: 'Leave empty to watch every establishment. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.'
344
+ }
345
+ ]
346
+ };
347
+ this.methods = { loadOptions: sofiaLoadOptions };
348
+ }
349
+ async poll() {
350
+ return pollSofia(this);
351
+ }
352
+ };
353
+ // Annotate the CommonJS export names for ESM import in node:
354
+ 0 && (module.exports = {
355
+ SofiaTrigger
356
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "node": "n8n-nodes-sofia-post.sofiaTrigger",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["Marketing & Content"],
6
+ "resources": {
7
+ "credentialDocumentation": [
8
+ {
9
+ "url": "https://github.com/badjilounes/n8n-nodes-sofia-post?tab=readme-ov-file#credentials"
10
+ }
11
+ ],
12
+ "primaryDocumentation": [
13
+ {
14
+ "url": "https://github.com/badjilounes/n8n-nodes-sofia-post?tab=readme-ov-file"
15
+ }
16
+ ]
17
+ }
18
+ }
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "n8n-nodes-sofia-post",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Sofia for n8n: publish, schedule and follow your social media posts from any workflow.",
5
+ "license": "MIT",
6
+ "homepage": "https://www.sofia-post.com/developers",
7
+ "keywords": [
8
+ "n8n-community-node-package",
9
+ "n8n",
10
+ "sofia",
11
+ "social media",
12
+ "instagram",
13
+ "facebook",
14
+ "linkedin",
15
+ "tiktok"
16
+ ],
17
+ "author": {
18
+ "name": "Sofia",
19
+ "email": "contact@sofia-post.com"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/badjilounes/n8n-nodes-sofia-post.git"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "n8n": {
29
+ "n8nNodesApiVersion": 1,
30
+ "strict": true,
31
+ "credentials": [
32
+ "dist/credentials/SofiaApi.credentials.js"
33
+ ],
34
+ "nodes": [
35
+ "dist/nodes/Sofia/Sofia.node.js",
36
+ "dist/nodes/SofiaTrigger/SofiaTrigger.node.js"
37
+ ]
38
+ },
39
+ "devDependencies": {
40
+ "@n8n/node-cli": "0.55.4"
41
+ },
42
+ "peerDependencies": {
43
+ "n8n-workflow": "*"
44
+ }
45
+ }