@opencoredev/social-sdk 0.1.2 → 0.2.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.
Files changed (154) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.d.ts +14 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +235 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cloud/common.d.ts +26 -0
  7. package/dist/cloud/common.d.ts.map +1 -0
  8. package/dist/cloud/common.js +335 -0
  9. package/dist/cloud/common.js.map +1 -0
  10. package/dist/cloud/lifecycle.d.ts +11 -0
  11. package/dist/cloud/lifecycle.d.ts.map +1 -0
  12. package/dist/cloud/lifecycle.js +113 -0
  13. package/dist/cloud/lifecycle.js.map +1 -0
  14. package/dist/cloud/media.d.ts +24 -0
  15. package/dist/cloud/media.d.ts.map +1 -0
  16. package/dist/cloud/media.js +101 -0
  17. package/dist/cloud/media.js.map +1 -0
  18. package/dist/cloud/outcomes.d.ts +10 -0
  19. package/dist/cloud/outcomes.d.ts.map +1 -0
  20. package/dist/cloud/outcomes.js +196 -0
  21. package/dist/cloud/outcomes.js.map +1 -0
  22. package/dist/cloud/post-for-me.d.ts +70 -0
  23. package/dist/cloud/post-for-me.d.ts.map +1 -0
  24. package/dist/cloud/post-for-me.js +397 -0
  25. package/dist/cloud/post-for-me.js.map +1 -0
  26. package/dist/cloud/zernio.d.ts +112 -0
  27. package/dist/cloud/zernio.d.ts.map +1 -0
  28. package/dist/cloud/zernio.js +633 -0
  29. package/dist/cloud/zernio.js.map +1 -0
  30. package/dist/core/adapter.d.ts +159 -0
  31. package/dist/core/adapter.d.ts.map +1 -0
  32. package/dist/core/adapter.js +4 -0
  33. package/dist/core/adapter.js.map +1 -0
  34. package/dist/core/client.d.ts +142 -0
  35. package/dist/core/client.d.ts.map +1 -0
  36. package/dist/core/client.js +1286 -0
  37. package/dist/core/client.js.map +1 -0
  38. package/dist/core/concurrency.d.ts +10 -0
  39. package/dist/core/concurrency.d.ts.map +1 -0
  40. package/dist/core/concurrency.js +80 -0
  41. package/dist/core/concurrency.js.map +1 -0
  42. package/dist/core/errors.d.ts +45 -0
  43. package/dist/core/errors.d.ts.map +1 -0
  44. package/dist/core/errors.js +51 -0
  45. package/dist/core/errors.js.map +1 -0
  46. package/dist/core/idempotency.d.ts +34 -0
  47. package/dist/core/idempotency.d.ts.map +1 -0
  48. package/dist/core/idempotency.js +59 -0
  49. package/dist/core/idempotency.js.map +1 -0
  50. package/dist/core/index.d.ts +7 -0
  51. package/dist/core/index.d.ts.map +1 -0
  52. package/dist/core/index.js +7 -0
  53. package/dist/core/index.js.map +1 -0
  54. package/dist/core/pagination.d.ts +12 -0
  55. package/dist/core/pagination.d.ts.map +1 -0
  56. package/dist/core/pagination.js +82 -0
  57. package/dist/core/pagination.js.map +1 -0
  58. package/dist/core/types.d.ts +397 -0
  59. package/dist/core/types.d.ts.map +1 -0
  60. package/dist/core/types.js +17 -0
  61. package/dist/core/types.js.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +2 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/platforms/bluesky.d.ts +262 -0
  67. package/dist/platforms/bluesky.d.ts.map +1 -0
  68. package/dist/platforms/bluesky.js +1777 -0
  69. package/dist/platforms/bluesky.js.map +1 -0
  70. package/dist/platforms/instagram.d.ts +130 -0
  71. package/dist/platforms/instagram.d.ts.map +1 -0
  72. package/dist/platforms/instagram.js +1032 -0
  73. package/dist/platforms/instagram.js.map +1 -0
  74. package/dist/platforms/linkedin.d.ts +109 -0
  75. package/dist/platforms/linkedin.d.ts.map +1 -0
  76. package/dist/platforms/linkedin.js +891 -0
  77. package/dist/platforms/linkedin.js.map +1 -0
  78. package/dist/platforms/threads.d.ts +135 -0
  79. package/dist/platforms/threads.d.ts.map +1 -0
  80. package/dist/platforms/threads.js +946 -0
  81. package/dist/platforms/threads.js.map +1 -0
  82. package/dist/platforms/tiktok.d.ts +32 -0
  83. package/dist/platforms/tiktok.d.ts.map +1 -0
  84. package/dist/platforms/tiktok.js +594 -0
  85. package/dist/platforms/tiktok.js.map +1 -0
  86. package/dist/platforms/x-engagement.d.ts +17 -0
  87. package/dist/platforms/x-engagement.d.ts.map +1 -0
  88. package/dist/platforms/x-engagement.js +51 -0
  89. package/dist/platforms/x-engagement.js.map +1 -0
  90. package/dist/platforms/x.d.ts +300 -0
  91. package/dist/platforms/x.d.ts.map +1 -0
  92. package/dist/platforms/x.js +1288 -0
  93. package/dist/platforms/x.js.map +1 -0
  94. package/dist/platforms/youtube-upload.d.ts +33 -0
  95. package/dist/platforms/youtube-upload.d.ts.map +1 -0
  96. package/dist/platforms/youtube-upload.js +297 -0
  97. package/dist/platforms/youtube-upload.js.map +1 -0
  98. package/dist/platforms/youtube.d.ts +109 -0
  99. package/dist/platforms/youtube.d.ts.map +1 -0
  100. package/dist/platforms/youtube.js +1101 -0
  101. package/dist/platforms/youtube.js.map +1 -0
  102. package/dist/server/connections.d.ts +124 -0
  103. package/dist/server/connections.d.ts.map +1 -0
  104. package/dist/server/connections.js +336 -0
  105. package/dist/server/connections.js.map +1 -0
  106. package/dist/server/credentials.d.ts +52 -0
  107. package/dist/server/credentials.d.ts.map +1 -0
  108. package/dist/server/credentials.js +109 -0
  109. package/dist/server/credentials.js.map +1 -0
  110. package/dist/server/index.d.ts +5 -0
  111. package/dist/server/index.d.ts.map +1 -0
  112. package/dist/server/index.js +5 -0
  113. package/dist/server/index.js.map +1 -0
  114. package/dist/server/oauth.d.ts +53 -0
  115. package/dist/server/oauth.d.ts.map +1 -0
  116. package/dist/server/oauth.js +554 -0
  117. package/dist/server/oauth.js.map +1 -0
  118. package/dist/server/webhooks.d.ts +58 -0
  119. package/dist/server/webhooks.d.ts.map +1 -0
  120. package/dist/server/webhooks.js +205 -0
  121. package/dist/server/webhooks.js.map +1 -0
  122. package/dist/testing/index.d.ts +47 -0
  123. package/dist/testing/index.d.ts.map +1 -0
  124. package/dist/testing/index.js +565 -0
  125. package/dist/testing/index.js.map +1 -0
  126. package/dist/testing.d.ts +2 -0
  127. package/dist/testing.d.ts.map +1 -0
  128. package/dist/testing.js +2 -0
  129. package/dist/testing.js.map +1 -0
  130. package/dist/transport/binary.d.ts +3 -0
  131. package/dist/transport/binary.d.ts.map +1 -0
  132. package/dist/transport/binary.js +30 -0
  133. package/dist/transport/binary.js.map +1 -0
  134. package/dist/transport/budget.d.ts +4 -0
  135. package/dist/transport/budget.d.ts.map +1 -0
  136. package/dist/transport/budget.js +27 -0
  137. package/dist/transport/budget.js.map +1 -0
  138. package/dist/transport/http.d.ts +45 -0
  139. package/dist/transport/http.d.ts.map +1 -0
  140. package/dist/transport/http.js +260 -0
  141. package/dist/transport/http.js.map +1 -0
  142. package/dist/transport/json.d.ts +9 -0
  143. package/dist/transport/json.d.ts.map +1 -0
  144. package/dist/transport/json.js +17 -0
  145. package/dist/transport/json.js.map +1 -0
  146. package/dist/transport/upload.d.ts +26 -0
  147. package/dist/transport/upload.d.ts.map +1 -0
  148. package/dist/transport/upload.js +154 -0
  149. package/dist/transport/upload.js.map +1 -0
  150. package/dist/transport/validation.d.ts +6 -0
  151. package/dist/transport/validation.d.ts.map +1 -0
  152. package/dist/transport/validation.js +25 -0
  153. package/dist/transport/validation.js.map +1 -0
  154. package/package.json +2 -1
@@ -0,0 +1,633 @@
1
+ /* oxlint-disable anti-slop/no-runtime-typeof -- validated external boundary or fixture contract. */
2
+ import { managedLifecycle } from "./lifecycle.js";
3
+ import { managedMedia } from "./media.js";
4
+ export { MemoryManagedMediaStore, } from "./media.js";
5
+ import { defineAdapter } from "../core/adapter.js";
6
+ import { SocialError } from "../core/errors.js";
7
+ import { array, object, optionalNumber, optionalString, string } from "../transport/validation.js";
8
+ import { decodeWebhook, verifyZernioWebhook } from "../server/webhooks.js";
9
+ import { accountMatches, capabilityManifest, managedHttp, managedPreparation, managedOptionIssues, optionsObject, platform, publicFields, } from "./common.js";
10
+ import { zernioOutcome } from "./outcomes.js";
11
+ export function zernio(options) {
12
+ const request = managedHttp("https://zernio.com/api", options);
13
+ const mediaPipeline = managedMedia("zernio", options, (body, context) => request("/v1/media/presign", context, body));
14
+ const now = () => (options.clock?.() ?? new Date()).toISOString();
15
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- provider payload is validated at this adapter boundary.
16
+ const account = (value, backend) => {
17
+ const row = object(value);
18
+ const handle = optionalString(row["username"]);
19
+ return {
20
+ ref: {
21
+ kind: "connected-account",
22
+ version: 1,
23
+ backend,
24
+ platform: platform(row["platform"]),
25
+ accountId: string(row["_id"]),
26
+ },
27
+ displayName: optionalString(row["displayName"]) ?? handle ?? string(row["_id"]),
28
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
29
+ ...(handle ? { handle } : {}),
30
+ status: row["isActive"] === true
31
+ ? "connected"
32
+ : row["isActive"] === false
33
+ ? "reconnect-required"
34
+ : "unknown",
35
+ };
36
+ };
37
+ // oxlint-disable-next-line anti-slop/no-unknown-parameters -- validated by account at this boundary.
38
+ const supportedAccount = (value, backend) => {
39
+ try {
40
+ return account(value, backend);
41
+ }
42
+ catch (error) {
43
+ if (error instanceof SocialError && error.code === "unsupported_capability")
44
+ return undefined;
45
+ throw error;
46
+ }
47
+ };
48
+ const analytics = async (ref, context) => {
49
+ accountMatches(ref, context);
50
+ return object(await request("/v1/analytics", context, undefined, {
51
+ postId: ref.postId,
52
+ accountId: ref.accountId,
53
+ platform: ref.platform === "x" ? "twitter" : ref.platform,
54
+ }));
55
+ };
56
+ return defineAdapter({
57
+ id: "zernio",
58
+ capabilities: {
59
+ ...capabilityManifest("zernio", "v1 / OpenAPI 1.25.1", [
60
+ "accounts.read",
61
+ "posts.publish",
62
+ "posts.read",
63
+ "posts.list",
64
+ "posts.status",
65
+ "posts.cancelScheduled",
66
+ "posts.deleteBackendRecord",
67
+ "analytics.read",
68
+ "analytics.account.read",
69
+ "media.upload",
70
+ "webhooks.verify",
71
+ ]),
72
+ capabilities: [
73
+ ...capabilityManifest("zernio", "1.25.1", [
74
+ "accounts.read",
75
+ "posts.publish",
76
+ "posts.read",
77
+ "posts.list",
78
+ "posts.status",
79
+ "posts.cancelScheduled",
80
+ "posts.deleteBackendRecord",
81
+ "analytics.read",
82
+ "analytics.account.read",
83
+ "media.upload",
84
+ "webhooks.verify",
85
+ ]).capabilities,
86
+ ...["x", "threads", "bluesky", "youtube", "linkedin", "facebook"].map((platform) => ({
87
+ platform,
88
+ operation: "posts.removeFromPlatform",
89
+ availability: "available",
90
+ })),
91
+ ...["x", "threads", "bluesky", "youtube", "instagram", "linkedin", "facebook"].flatMap((platform) => ["comments.read", "comments.write"].map((operation) => ({
92
+ platform,
93
+ operation,
94
+ availability: "available",
95
+ }))),
96
+ ...["x", "bluesky", "instagram", "facebook"].flatMap((platform) => ["messages.read", "messages.write"].map((operation) => ({
97
+ platform,
98
+ operation,
99
+ availability: "available",
100
+ notes: "Recipient permissions and reply-window eligibility remain upstream constraints.",
101
+ }))),
102
+ ],
103
+ },
104
+ media: { upload: mediaPipeline.upload },
105
+ accounts: {
106
+ async list(input, context) {
107
+ const page = input.cursor === undefined ? 1 : Number(input.cursor);
108
+ const limit = input.limit ?? 25;
109
+ if (!Number.isSafeInteger(page) ||
110
+ page < 1 ||
111
+ !Number.isSafeInteger(limit) ||
112
+ limit < 1 ||
113
+ limit > 100)
114
+ throw new SocialError({
115
+ code: "invalid_input",
116
+ operation: "accounts.read",
117
+ message: "Use a returned cursor and page size from 1 to 100.",
118
+ });
119
+ const result = object(await request("/v1/accounts", context, undefined, {
120
+ page: String(page),
121
+ limit: String(limit),
122
+ }));
123
+ const pagination = result["pagination"] ? object(result["pagination"]) : {};
124
+ const pages = optionalNumber(pagination["pages"]) ?? optionalNumber(pagination["totalPages"]);
125
+ return {
126
+ items: array(result["accounts"]).flatMap((value) => {
127
+ const parsed = supportedAccount(value, context.backendInstance);
128
+ return parsed ? [parsed] : [];
129
+ }),
130
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
131
+ ...(pages !== undefined && page < pages ? { nextCursor: String(page + 1) } : {}),
132
+ };
133
+ },
134
+ async get(ref, context) {
135
+ accountMatches(ref, context);
136
+ const platform = ref.platform === "x" ? "twitter" : ref.platform;
137
+ let raw;
138
+ // The list endpoint is paginated, so keep reading until the account appears.
139
+ for (let page = 1; page <= 100 && !raw; page++) {
140
+ const response = object(await request("/v1/accounts", context, undefined, {
141
+ platform,
142
+ page: String(page),
143
+ limit: "100",
144
+ }));
145
+ raw = array(response["accounts"])
146
+ .map(object)
147
+ .find((item) => item["_id"] === ref.accountId &&
148
+ (item["platform"] === ref.platform || item["platform"] === platform));
149
+ const pagination = response["pagination"] ? object(response["pagination"]) : {};
150
+ const pages = optionalNumber(pagination["pages"]) ?? optionalNumber(pagination["totalPages"]);
151
+ if (pages === undefined || page >= pages)
152
+ break;
153
+ }
154
+ if (!raw)
155
+ throw new SocialError({
156
+ code: "not_found",
157
+ operation: "accounts.read",
158
+ message: "Provider account was not found.",
159
+ });
160
+ const result = account(raw, context.backendInstance);
161
+ if (result.ref.accountId !== ref.accountId || result.ref.platform !== ref.platform)
162
+ throw new SocialError({
163
+ code: "unauthorized",
164
+ operation: "accounts.read",
165
+ message: "Provider account does not match the requested reference.",
166
+ });
167
+ return result;
168
+ },
169
+ },
170
+ posts: {
171
+ ...managedLifecycle("zernio", request, now),
172
+ async list(account, input, context) {
173
+ accountMatches(account, context);
174
+ const page = input.cursor === undefined ? 1 : Number(input.cursor);
175
+ const limit = input.limit ?? 25;
176
+ if (!Number.isSafeInteger(page) ||
177
+ page < 1 ||
178
+ !Number.isSafeInteger(limit) ||
179
+ limit < 1 ||
180
+ limit > 500)
181
+ throw new SocialError({
182
+ code: "invalid_input",
183
+ operation: "posts.read",
184
+ message: "Zernio post page requires a returned page cursor and a size from 1 to 500.",
185
+ });
186
+ const result = object(await request("/v1/posts", context, undefined, {
187
+ page: String(page),
188
+ limit: String(limit),
189
+ source: "external",
190
+ accountId: account.accountId,
191
+ platform: account.platform === "x" ? "twitter" : account.platform,
192
+ }));
193
+ const rows = array(result["posts"])
194
+ .map(object)
195
+ .flatMap((row) => {
196
+ const destinations = Array.isArray(row["platforms"])
197
+ ? array(row["platforms"]).map(object)
198
+ : [];
199
+ const destination = destinations.find((item) => {
200
+ const value = item["accountId"];
201
+ const id =
202
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
203
+ typeof value === "string"
204
+ ? value
205
+ : // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
206
+ value && typeof value === "object"
207
+ ? object(value)["_id"]
208
+ : undefined;
209
+ return (id === account.accountId &&
210
+ (item["platform"] === account.platform ||
211
+ item["platform"] === (account.platform === "x" ? "twitter" : account.platform)));
212
+ });
213
+ if (destination === undefined)
214
+ return [];
215
+ const platformPostId =
216
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
217
+ typeof destination["platformPostId"] === "string"
218
+ ? destination["platformPostId"]
219
+ : undefined;
220
+ return [
221
+ // oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- provider payload is validated at this adapter boundary.
222
+ {
223
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
224
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
225
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
226
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
227
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
228
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
229
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
230
+ ...(typeof row["_id"] === "string" ? { backendPostId: row["_id"] } : {}),
231
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
232
+ ...(platformPostId === undefined ? {} : { platformPostId }),
233
+ platform: account.platform,
234
+ accountId: account.accountId,
235
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
236
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
237
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
238
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
239
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
240
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
241
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
242
+ ...(typeof destination["platformPostUrl"] === "string"
243
+ ? { platformPostUrl: destination["platformPostUrl"] }
244
+ : {}),
245
+ ...publicFields(row, [
246
+ "content",
247
+ "caption",
248
+ "createdAt",
249
+ "publishedAt",
250
+ "status",
251
+ "isExternal",
252
+ "syncStatus",
253
+ ]),
254
+ },
255
+ ];
256
+ });
257
+ const pagination = result["pagination"] === undefined ? {} : object(result["pagination"]);
258
+ const totalPages = optionalNumber(pagination["pages"]) ?? optionalNumber(pagination["totalPages"]);
259
+ return {
260
+ // oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- provider payload is validated at this adapter boundary.
261
+ items: rows,
262
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
263
+ ...(totalPages !== undefined && page < totalPages
264
+ ? { nextCursor: String(page + 1) }
265
+ : {}),
266
+ };
267
+ },
268
+ prepareTarget(target) {
269
+ const issues = [...managedPreparation(target), ...managedOptionIssues(target, "zernio")];
270
+ const config = optionsObject(target);
271
+ if (target.account.platform === "threads" && config["replyControl"] !== undefined)
272
+ issues.push({
273
+ code: "threads.reply_control_unsupported",
274
+ message: "Zernio's documented create-post schema does not expose this reply-control option.",
275
+ severity: "error",
276
+ targetIndex: target.targetIndex,
277
+ });
278
+ if (target.account.platform === "linkedin" && config["visibility"] === "connections")
279
+ issues.push({
280
+ code: "linkedin.visibility_unsupported",
281
+ message: "This managed adapter cannot guarantee connections-only visibility.",
282
+ severity: "error",
283
+ targetIndex: target.targetIndex,
284
+ });
285
+ return issues;
286
+ },
287
+ async publishTarget(target, context) {
288
+ accountMatches(target.account, context);
289
+ const media = [];
290
+ for (const item of target.content.media ?? [])
291
+ media.push({
292
+ type: item.kind,
293
+ url: await mediaPipeline.resolve(item, target.account, context),
294
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
295
+ ...(item.altText === undefined ? {} : { altText: item.altText }),
296
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
297
+ ...(item.mimeType === undefined ? {} : { mimeType: item.mimeType }),
298
+ });
299
+ const config = optionsObject(target);
300
+ const native = {};
301
+ if (target.account.platform === "youtube") {
302
+ native["title"] = string(config["title"]);
303
+ native["visibility"] = string(config["visibility"]);
304
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
305
+ if (typeof config["madeForKids"] === "boolean")
306
+ native["madeForKids"] = config["madeForKids"];
307
+ }
308
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
309
+ if (target.account.platform === "instagram" && typeof config["shareToFeed"] === "boolean")
310
+ native["shareToFeed"] = config["shareToFeed"];
311
+ if (target.account.platform === "x" &&
312
+ config["replySettings"] !== undefined &&
313
+ config["replySettings"] !== "everyone")
314
+ native["replySettings"] = string(config["replySettings"]);
315
+ if (target.account.platform === "tiktok") {
316
+ native["privacyLevel"] = string(config["privacy"]);
317
+ native["contentPreviewConfirmed"] = true;
318
+ native["expressConsentGiven"] = true;
319
+ native["autoAddMusic"] = false;
320
+ native["allowDuet"] = !config["disableDuet"];
321
+ native["allowStitch"] = !config["disableStitch"];
322
+ // oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- provider payload is validated at this adapter boundary.
323
+ native["isBrandOrganicPost"] = config["ownBrand"];
324
+ // oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- provider payload is validated at this adapter boundary.
325
+ native["videoMadeWithAi"] = config["aiGenerated"];
326
+ // oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- provider payload is validated at this adapter boundary.
327
+ native["draft"] = config["draft"];
328
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
329
+ if (typeof config["photoCoverIndex"] === "number")
330
+ native["photoCoverIndex"] = config["photoCoverIndex"];
331
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
332
+ if (typeof config["disableComments"] === "boolean")
333
+ native["allowComment"] = !config["disableComments"];
334
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
335
+ if (typeof config["brandedContent"] === "boolean")
336
+ native["brandPartnerPromote"] = config["brandedContent"];
337
+ }
338
+ const response = await request("/v1/posts", context, {
339
+ content: target.content.text ?? "",
340
+ mediaItems: media,
341
+ platforms: [
342
+ {
343
+ platform: target.account.platform === "x" ? "twitter" : target.account.platform,
344
+ accountId: target.account.accountId,
345
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
346
+ ...(Object.keys(native).length ? { platformSpecificData: native } : {}),
347
+ },
348
+ ],
349
+ ...(target.schedule
350
+ ? {
351
+ scheduledFor: target.schedule.at,
352
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
353
+ ...(target.schedule.timeZone ? { timezone: target.schedule.timeZone } : {}),
354
+ }
355
+ : { publishNow: true }),
356
+ });
357
+ return zernioOutcome(response, {
358
+ account: target.account,
359
+ targetIndex: target.targetIndex,
360
+ observedAt: now(),
361
+ });
362
+ },
363
+ async getDelivery(ref, context) {
364
+ accountMatches(ref, context);
365
+ const response = await request(`/v1/posts/${encodeURIComponent(ref.deliveryId)}`, context);
366
+ return zernioOutcome(response, {
367
+ account: {
368
+ kind: "connected-account",
369
+ version: 1,
370
+ backend: ref.backend,
371
+ platform: ref.platform,
372
+ accountId: ref.accountId,
373
+ },
374
+ targetIndex: 0,
375
+ observedAt: now(),
376
+ });
377
+ },
378
+ async get(ref, context) {
379
+ return publicFields(await analytics(ref, context), [
380
+ "postId",
381
+ "content",
382
+ "publishedAt",
383
+ "platform",
384
+ "platformPostUrl",
385
+ "isExternal",
386
+ "syncStatus",
387
+ ]);
388
+ },
389
+ },
390
+ analytics: {
391
+ async getAccountMetrics(ref, context) {
392
+ accountMatches(ref, context);
393
+ // Zernio documents followersCount on GET /v1/accounts (when the
394
+ // analytics add-on is enabled); there is no accountId-specific GET.
395
+ const result = object(await request("/v1/accounts", context, undefined, {
396
+ platform: ref.platform === "x" ? "twitter" : ref.platform,
397
+ }));
398
+ const row = array(result["accounts"])
399
+ .map(object)
400
+ .find((item) => item["_id"] === ref.accountId &&
401
+ item["platform"] === (ref.platform === "x" ? "twitter" : ref.platform));
402
+ if (!row)
403
+ throw new SocialError({
404
+ code: "unauthorized",
405
+ operation: "analytics.account.read",
406
+ message: "Provider account does not match the requested reference.",
407
+ });
408
+ const followers = optionalNumber(row["followersCount"]);
409
+ if (followers === undefined)
410
+ return [];
411
+ const measuredAt = optionalString(row["followersLastUpdated"]);
412
+ return [
413
+ {
414
+ name: "followers",
415
+ value: followers,
416
+ unit: "count",
417
+ period: "lifetime",
418
+ ...(measuredAt
419
+ ? { measuredAt, freshness: "reported" }
420
+ : { freshness: "unknown" }),
421
+ fetchedAt: now(),
422
+ source: `zernio:${ref.platform}:account`,
423
+ },
424
+ ];
425
+ },
426
+ async getPostMetrics(ref, context) {
427
+ const result = await analytics(ref, context);
428
+ if (result["syncStatus"] === "unavailable" || result["analytics"] === undefined)
429
+ return [];
430
+ const source = object(result["analytics"]);
431
+ const measuredAt = optionalString(source["lastUpdated"]);
432
+ // Do not label request time as measurement time when upstream freshness is absent.
433
+ const metrics = [];
434
+ for (const name of [
435
+ "impressions",
436
+ "reach",
437
+ "likes",
438
+ "comments",
439
+ "shares",
440
+ "saves",
441
+ "clicks",
442
+ "views",
443
+ "follows",
444
+ "reposts",
445
+ ]) {
446
+ const value = optionalNumber(source[name]);
447
+ if (value !== undefined)
448
+ metrics.push({
449
+ name,
450
+ value,
451
+ unit: "count",
452
+ period: "lifetime",
453
+ ...(measuredAt ? { measuredAt, freshness: "reported" } : { freshness: "unknown" }),
454
+ fetchedAt: now(),
455
+ source: `zernio:${ref.platform}`,
456
+ });
457
+ }
458
+ return metrics;
459
+ },
460
+ },
461
+ comments: {
462
+ async list(ref, input, context) {
463
+ accountMatches(ref, context);
464
+ const limit = input.limit ?? 25;
465
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
466
+ throw new SocialError({
467
+ code: "invalid_input",
468
+ operation: "comments.read",
469
+ message: "Zernio comment page size must be between 1 and 100.",
470
+ });
471
+ const result = object(await request(`/v1/inbox/comments/${encodeURIComponent(ref.postId)}`, context, undefined, {
472
+ accountId: ref.accountId,
473
+ limit: String(limit),
474
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
475
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
476
+ }));
477
+ const pagination = result["pagination"] ? object(result["pagination"]) : {};
478
+ const cursor = optionalString(pagination["cursor"]);
479
+ const hasMore = pagination["hasMore"];
480
+ return {
481
+ items: array(result["comments"]).map((entry) => publicFields(entry, [
482
+ "id",
483
+ "message",
484
+ "createdTime",
485
+ "likeCount",
486
+ "replyCount",
487
+ "parentId",
488
+ "rootUri",
489
+ "rootCid",
490
+ "cid",
491
+ "canReply",
492
+ "canDelete",
493
+ "canHide",
494
+ ])),
495
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
496
+ ...(hasMore === false || !cursor ? {} : { nextCursor: cursor }),
497
+ };
498
+ },
499
+ async reply(ref, content, context) {
500
+ accountMatches(ref, context);
501
+ const result = object(await request(`/v1/inbox/comments/${encodeURIComponent(ref.postId)}`, context, {
502
+ accountId: ref.accountId,
503
+ message: content.text,
504
+ commentId: ref.commentId,
505
+ }));
506
+ if (result["success"] !== true)
507
+ throw new SocialError({
508
+ code: "ambiguous_outcome",
509
+ operation: "comments.write",
510
+ message: "Comment reply lacks a confirmed success result; reconcile before retrying.",
511
+ });
512
+ return { ...ref, commentId: string(object(result["data"])["commentId"]) };
513
+ },
514
+ },
515
+ messages: {
516
+ async listConversations(ref, input, context) {
517
+ accountMatches(ref, context);
518
+ const limit = input.limit ?? 50;
519
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
520
+ throw new SocialError({
521
+ code: "invalid_input",
522
+ operation: "messages.read",
523
+ message: "Zernio conversation page size must be between 1 and 100.",
524
+ });
525
+ const result = object(await request("/v1/inbox/conversations", context, undefined, {
526
+ accountId: ref.accountId,
527
+ limit: String(limit),
528
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
529
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
530
+ }));
531
+ const pagination = result["pagination"] === undefined ? {} : object(result["pagination"]);
532
+ const cursor = optionalString(pagination["nextCursor"]);
533
+ return {
534
+ items: array(result["data"]).map((entry) => publicFields(entry, [
535
+ "id",
536
+ "platform",
537
+ "accountId",
538
+ "participantId",
539
+ "participantName",
540
+ "lastMessage",
541
+ "updatedTime",
542
+ "unreadCount",
543
+ ])),
544
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
545
+ ...(cursor ? { nextCursor: cursor } : {}),
546
+ };
547
+ },
548
+ async listMessages(ref, input, context) {
549
+ accountMatches(ref, context);
550
+ const limit = input.limit ?? 100;
551
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
552
+ throw new SocialError({
553
+ code: "invalid_input",
554
+ operation: "messages.read",
555
+ message: "Zernio message page size must be between 1 and 100.",
556
+ });
557
+ const result = object(await request(`/v1/inbox/conversations/${encodeURIComponent(ref.conversationId)}/messages`, context, undefined, {
558
+ accountId: ref.accountId,
559
+ limit: String(limit),
560
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
561
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
562
+ }));
563
+ const pagination = result["pagination"] ? object(result["pagination"]) : {};
564
+ const cursor = optionalString(pagination["nextCursor"]);
565
+ return {
566
+ items: array(result["messages"]).map((entry) => publicFields(entry, [
567
+ "id",
568
+ "message",
569
+ "senderId",
570
+ "senderName",
571
+ "direction",
572
+ "createdAt",
573
+ "deliveryStatus",
574
+ ])),
575
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
576
+ ...(cursor ? { nextCursor: cursor } : {}),
577
+ };
578
+ },
579
+ async send(ref, content, context) {
580
+ accountMatches(ref, context);
581
+ const result = object(await request(`/v1/inbox/conversations/${encodeURIComponent(ref.conversationId)}/messages`, context, { accountId: ref.accountId, message: content.text }));
582
+ if (result["success"] !== true)
583
+ throw new SocialError({
584
+ code: "ambiguous_outcome",
585
+ operation: "messages.write",
586
+ message: "Message outcome is unconfirmed. Reconcile before retrying.",
587
+ });
588
+ const data = object(result["data"]);
589
+ const messageId = optionalString(data["messageId"]);
590
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
591
+ return { state: "sent", ...(messageId ? { messageId } : {}) };
592
+ },
593
+ },
594
+ webhooks: {
595
+ async verify(input) {
596
+ await verifyZernioWebhook({ ...input, secret: options.webhookSecret ?? "" });
597
+ return { valid: true, method: "hmac" };
598
+ },
599
+ async decode(input, context) {
600
+ return {
601
+ ...(await decodeWebhook({
602
+ provider: "zernio",
603
+ backend: context.backendInstance,
604
+ body: input.body,
605
+ receivedAt: now(),
606
+ })),
607
+ };
608
+ },
609
+ },
610
+ native: {
611
+ async createConnection(input, context) {
612
+ platform(input.platform);
613
+ const response = object(await request(`/v1/connect/${input.platform === "x" ? "twitter" : encodeURIComponent(input.platform)}`, context, undefined, { profileId: input.profileId, redirect_url: input.redirectUrl }));
614
+ return {
615
+ url: string(response["authUrl"]),
616
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
617
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
618
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
619
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
620
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
621
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
622
+ // oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
623
+ ...(typeof response["state"] === "string" ? { providerState: response["state"] } : {}),
624
+ };
625
+ },
626
+ async listProfiles(context) {
627
+ const result = object(await request("/v1/profiles", context));
628
+ return array(result["profiles"]).map((entry) => publicFields(entry, ["_id", "name", "description"]));
629
+ },
630
+ },
631
+ });
632
+ }
633
+ //# sourceMappingURL=zernio.js.map