@classytic/social 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.
Files changed (121) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +368 -0
  4. package/dist/base-Bw7e52V8.mjs +246 -0
  5. package/dist/base-Bw7e52V8.mjs.map +1 -0
  6. package/dist/base-DBtKFiSX.d.mts +226 -0
  7. package/dist/base-DBtKFiSX.d.mts.map +1 -0
  8. package/dist/chunk-DQk6qfdC.mjs +18 -0
  9. package/dist/client/index.d.mts +44 -0
  10. package/dist/client/index.d.mts.map +1 -0
  11. package/dist/client/index.mjs +154 -0
  12. package/dist/client/index.mjs.map +1 -0
  13. package/dist/common/index.d.mts +3 -0
  14. package/dist/common/index.mjs +7 -0
  15. package/dist/contracts-Cdwa4zlg.d.mts +121 -0
  16. package/dist/contracts-Cdwa4zlg.d.mts.map +1 -0
  17. package/dist/contracts-lCa069IK.mjs +221 -0
  18. package/dist/contracts-lCa069IK.mjs.map +1 -0
  19. package/dist/env-Bl0cwwjC.mjs +955 -0
  20. package/dist/env-Bl0cwwjC.mjs.map +1 -0
  21. package/dist/env-DxOZHf0p.d.mts +394 -0
  22. package/dist/env-DxOZHf0p.d.mts.map +1 -0
  23. package/dist/errors-Cm6LeKf7.mjs +32 -0
  24. package/dist/errors-Cm6LeKf7.mjs.map +1 -0
  25. package/dist/facebook-l_4CghaA.mjs +95 -0
  26. package/dist/facebook-l_4CghaA.mjs.map +1 -0
  27. package/dist/http-DpcLSR1M.mjs +197 -0
  28. package/dist/http-DpcLSR1M.mjs.map +1 -0
  29. package/dist/index.d.mts +42 -0
  30. package/dist/index.d.mts.map +1 -0
  31. package/dist/index.mjs +71 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/instagram-BGaeUFU2.mjs +90 -0
  34. package/dist/instagram-BGaeUFU2.mjs.map +1 -0
  35. package/dist/linkedin-70whtVKa.mjs +101 -0
  36. package/dist/linkedin-70whtVKa.mjs.map +1 -0
  37. package/dist/meta-D3vcJU1c.mjs +126 -0
  38. package/dist/meta-D3vcJU1c.mjs.map +1 -0
  39. package/dist/pkce-jq5II68b.mjs +72 -0
  40. package/dist/pkce-jq5II68b.mjs.map +1 -0
  41. package/dist/polling-DZ1apXtA.mjs +25 -0
  42. package/dist/polling-DZ1apXtA.mjs.map +1 -0
  43. package/dist/providers/facebook.d.mts +135 -0
  44. package/dist/providers/facebook.d.mts.map +1 -0
  45. package/dist/providers/facebook.mjs +450 -0
  46. package/dist/providers/facebook.mjs.map +1 -0
  47. package/dist/providers/instagram.d.mts +122 -0
  48. package/dist/providers/instagram.d.mts.map +1 -0
  49. package/dist/providers/instagram.mjs +496 -0
  50. package/dist/providers/instagram.mjs.map +1 -0
  51. package/dist/providers/linkedin.d.mts +145 -0
  52. package/dist/providers/linkedin.d.mts.map +1 -0
  53. package/dist/providers/linkedin.mjs +574 -0
  54. package/dist/providers/linkedin.mjs.map +1 -0
  55. package/dist/providers/reddit.d.mts +102 -0
  56. package/dist/providers/reddit.d.mts.map +1 -0
  57. package/dist/providers/reddit.mjs +657 -0
  58. package/dist/providers/reddit.mjs.map +1 -0
  59. package/dist/providers/telegram.d.mts +139 -0
  60. package/dist/providers/telegram.d.mts.map +1 -0
  61. package/dist/providers/telegram.mjs +517 -0
  62. package/dist/providers/telegram.mjs.map +1 -0
  63. package/dist/providers/tiktok.d.mts +116 -0
  64. package/dist/providers/tiktok.d.mts.map +1 -0
  65. package/dist/providers/tiktok.mjs +676 -0
  66. package/dist/providers/tiktok.mjs.map +1 -0
  67. package/dist/providers/twitter.d.mts +150 -0
  68. package/dist/providers/twitter.d.mts.map +1 -0
  69. package/dist/providers/twitter.mjs +628 -0
  70. package/dist/providers/twitter.mjs.map +1 -0
  71. package/dist/providers/whatsapp.d.mts +79 -0
  72. package/dist/providers/whatsapp.d.mts.map +1 -0
  73. package/dist/providers/whatsapp.mjs +376 -0
  74. package/dist/providers/whatsapp.mjs.map +1 -0
  75. package/dist/providers/youtube.d.mts +153 -0
  76. package/dist/providers/youtube.d.mts.map +1 -0
  77. package/dist/providers/youtube.mjs +902 -0
  78. package/dist/providers/youtube.mjs.map +1 -0
  79. package/dist/reddit-B10kS4Se.mjs +126 -0
  80. package/dist/reddit-B10kS4Se.mjs.map +1 -0
  81. package/dist/schemas/index.d.mts +819 -0
  82. package/dist/schemas/index.d.mts.map +1 -0
  83. package/dist/schemas/index.mjs +31 -0
  84. package/dist/schemas/index.mjs.map +1 -0
  85. package/dist/security-BXhfebWm.d.mts +338 -0
  86. package/dist/security-BXhfebWm.d.mts.map +1 -0
  87. package/dist/shared-Fvc6xQku.mjs +100 -0
  88. package/dist/shared-Fvc6xQku.mjs.map +1 -0
  89. package/dist/telegram-FaUHpZgB.mjs +107 -0
  90. package/dist/telegram-FaUHpZgB.mjs.map +1 -0
  91. package/dist/tiktok-B_bMk4G-.mjs +94 -0
  92. package/dist/tiktok-B_bMk4G-.mjs.map +1 -0
  93. package/dist/twitter-BC22zfuc.mjs +98 -0
  94. package/dist/twitter-BC22zfuc.mjs.map +1 -0
  95. package/dist/types-BFE4psYI.d.mts +102 -0
  96. package/dist/types-BFE4psYI.d.mts.map +1 -0
  97. package/dist/types-Bv27tcT0.d.mts +230 -0
  98. package/dist/types-Bv27tcT0.d.mts.map +1 -0
  99. package/dist/types-BwkKyqpi.d.mts +253 -0
  100. package/dist/types-BwkKyqpi.d.mts.map +1 -0
  101. package/dist/types-CJrHMDV9.mjs +27 -0
  102. package/dist/types-CJrHMDV9.mjs.map +1 -0
  103. package/dist/types-ClbVc2rc.d.mts +117 -0
  104. package/dist/types-ClbVc2rc.d.mts.map +1 -0
  105. package/dist/types-D91N16Ym.d.mts +242 -0
  106. package/dist/types-D91N16Ym.d.mts.map +1 -0
  107. package/dist/types-DfLp_ibQ.d.mts +178 -0
  108. package/dist/types-DfLp_ibQ.d.mts.map +1 -0
  109. package/dist/types-DfjDgEoJ.d.mts +88 -0
  110. package/dist/types-DfjDgEoJ.d.mts.map +1 -0
  111. package/dist/types-Dp5Z9VBr.mjs +23 -0
  112. package/dist/types-Dp5Z9VBr.mjs.map +1 -0
  113. package/dist/types-hriBJTsU.d.mts +129 -0
  114. package/dist/types-hriBJTsU.d.mts.map +1 -0
  115. package/dist/types-rn6UuLL8.d.mts +184 -0
  116. package/dist/types-rn6UuLL8.d.mts.map +1 -0
  117. package/dist/whatsapp-CFp7ryR4.mjs +101 -0
  118. package/dist/whatsapp-CFp7ryR4.mjs.map +1 -0
  119. package/dist/youtube-Bs0fdY7H.mjs +98 -0
  120. package/dist/youtube-Bs0fdY7H.mjs.map +1 -0
  121. package/package.json +148 -0
@@ -0,0 +1,955 @@
1
+ import { t as SocialError } from "./errors-Cm6LeKf7.mjs";
2
+ import { TwitterProvider } from "./providers/twitter.mjs";
3
+ import { FacebookProvider } from "./providers/facebook.mjs";
4
+ import { LinkedInProvider } from "./providers/linkedin.mjs";
5
+ import { RedditProvider } from "./providers/reddit.mjs";
6
+ import { InstagramProvider } from "./providers/instagram.mjs";
7
+ import { YouTubeProvider } from "./providers/youtube.mjs";
8
+ import { TikTokProvider } from "./providers/tiktok.mjs";
9
+ import { TelegramProvider } from "./providers/telegram.mjs";
10
+ import { WhatsAppProvider } from "./providers/whatsapp.mjs";
11
+ import { SocialClient } from "./client/index.mjs";
12
+
13
+ //#region src/client/twitter.ts
14
+ /**
15
+ * TwitterClient — credential-bound wrapper over `TwitterProvider`.
16
+ */
17
+ var TwitterClient = class {
18
+ provider;
19
+ constructor(config, opts = {}) {
20
+ this.config = config;
21
+ this.provider = new TwitterProvider({
22
+ port: opts.port,
23
+ redirectUri: config.redirectUri,
24
+ environment: opts.environment
25
+ });
26
+ }
27
+ /** OAuth: get the consent URL. Async variant uses S256 PKCE (preferred). */
28
+ async authUrl(state) {
29
+ return this.provider.getAuthUrlAsync(state, this.config);
30
+ }
31
+ /** OAuth: exchange the callback code for tokens. Stores them on `this.config.tokens`. */
32
+ async exchangeCode(code, state) {
33
+ const tokens = await this.provider.exchangeCode(code, this.config, state);
34
+ this.config.tokens = tokens;
35
+ return tokens;
36
+ }
37
+ /** Refresh the access token. Stores updated tokens. */
38
+ async refresh() {
39
+ const refreshToken = this.config.tokens?.refresh_token;
40
+ if (!refreshToken) throw new SocialError("twitter", "No refresh_token available");
41
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
42
+ this.config.tokens = {
43
+ ...this.config.tokens,
44
+ ...tokens
45
+ };
46
+ return this.config.tokens;
47
+ }
48
+ /**
49
+ * Post a tweet (text + optional media).
50
+ *
51
+ * Twitter requires media to be pre-uploaded via the v1.1 chunked endpoint —
52
+ * pass each pre-uploaded media's ID as `media[i].id`. Items with only a
53
+ * `url` are skipped (no automatic upload). For end-to-end upload see
54
+ * `provider.uploadPhoto()` / `provider.uploadVideo()` on the underlying
55
+ * `TwitterProvider`.
56
+ */
57
+ async post(input) {
58
+ try {
59
+ const accessToken = this.assertAccessToken();
60
+ const mediaIds = input.media?.map((m) => m.id).filter((id) => !!id);
61
+ if ((input.media?.length ?? 0) - (mediaIds?.length ?? 0) > 0 && mediaIds?.length === 0) throw new SocialError("twitter", "Twitter requires pre-uploaded media IDs. Upload via TwitterProvider.uploadPhoto/uploadVideo first, then pass `media[i].id`.", { statusCode: 400 });
62
+ const result = await this.provider.createTweet(accessToken, {
63
+ text: input.text ?? "",
64
+ mediaIds: mediaIds?.length ? mediaIds : void 0,
65
+ replyTo: input.perPlatform?.twitter?.replyTo,
66
+ quoteTweetId: input.perPlatform?.twitter?.quoteTweet
67
+ });
68
+ return {
69
+ platform: "twitter",
70
+ ok: true,
71
+ id: result.id,
72
+ url: result.id ? `https://twitter.com/i/status/${result.id}` : void 0,
73
+ raw: result
74
+ };
75
+ } catch (err) {
76
+ return errorResult("twitter", err);
77
+ }
78
+ }
79
+ /** Delete a tweet by ID. */
80
+ async delete(tweetId) {
81
+ const accessToken = this.assertAccessToken();
82
+ await this.provider.deleteTweet(accessToken, tweetId);
83
+ }
84
+ /** Get a tweet by ID. */
85
+ async get(tweetId) {
86
+ const accessToken = this.assertAccessToken();
87
+ return this.provider.getTweet(accessToken, tweetId);
88
+ }
89
+ /** Send a direct message. */
90
+ async sendDm(participantId, text) {
91
+ const accessToken = this.assertAccessToken();
92
+ return this.provider.sendDirectMessage(accessToken, {
93
+ participantId,
94
+ text
95
+ });
96
+ }
97
+ /** Get the authenticated user's profile. */
98
+ async me() {
99
+ const accessToken = this.assertAccessToken();
100
+ return this.provider.getAccountInfo(accessToken);
101
+ }
102
+ assertAccessToken() {
103
+ const t = this.config.tokens?.access_token;
104
+ if (!t) throw new SocialError("twitter", "Not authenticated. Call exchangeCode() first or pass tokens in config.", { statusCode: 401 });
105
+ return t;
106
+ }
107
+ };
108
+ function errorResult(platform, err) {
109
+ if (err instanceof SocialError) return {
110
+ platform,
111
+ ok: false,
112
+ error: {
113
+ message: err.message,
114
+ code: err.errorCode,
115
+ retryable: err.retryable
116
+ }
117
+ };
118
+ return {
119
+ platform,
120
+ ok: false,
121
+ error: { message: err instanceof Error ? err.message : String(err) }
122
+ };
123
+ }
124
+
125
+ //#endregion
126
+ //#region src/client/facebook.ts
127
+ /**
128
+ * FacebookClient — credential-bound wrapper over `FacebookProvider`.
129
+ */
130
+ var FacebookClient = class {
131
+ provider;
132
+ constructor(config, opts = {}) {
133
+ this.config = config;
134
+ this.provider = new FacebookProvider({
135
+ port: opts.port,
136
+ redirectUri: config.redirectUri,
137
+ environment: opts.environment
138
+ });
139
+ }
140
+ async authUrl(state) {
141
+ return this.provider.getAuthUrl(state, this.config);
142
+ }
143
+ async exchangeCode(code) {
144
+ const tokens = await this.provider.exchangeCode(code, this.config);
145
+ this.config.tokens = tokens;
146
+ return tokens;
147
+ }
148
+ async refresh() {
149
+ const refreshToken = this.config.tokens?.refresh_token ?? this.config.tokens?.access_token;
150
+ if (!refreshToken) throw new SocialError("facebook", "No refresh_token available");
151
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
152
+ this.config.tokens = {
153
+ ...this.config.tokens,
154
+ ...tokens
155
+ };
156
+ return this.config.tokens;
157
+ }
158
+ /**
159
+ * List Pages the authenticated user manages. Each result contains a
160
+ * page-level access token. Set `pageId` + `pageAccessToken` on the config to
161
+ * select a target page for posting.
162
+ */
163
+ async listPages() {
164
+ return this.provider.getPages(this.userToken());
165
+ }
166
+ /**
167
+ * Convenience: pick a page by ID and bind its token to this client.
168
+ */
169
+ async usePage(pageId) {
170
+ const match = (await this.listPages()).find((p) => p.id === pageId);
171
+ if (!match) throw new SocialError("facebook", `Page ${pageId} not found in /me/accounts`, { statusCode: 404 });
172
+ this.config.pageId = match.id;
173
+ this.config.pageAccessToken = match.access_token;
174
+ }
175
+ /** Create a Page post — text, link, or photo depending on input. */
176
+ async post(input) {
177
+ try {
178
+ const { pageId, token } = this.assertPage(input);
179
+ const scheduledAt = input.scheduledAt;
180
+ const photo = input.media?.find((m) => m.type === "image" && m.url);
181
+ if (photo?.url) {
182
+ const r = await this.provider.createPhotoPost(token, pageId, photo.url, {
183
+ caption: input.text,
184
+ scheduledAt
185
+ });
186
+ return {
187
+ platform: "facebook",
188
+ ok: true,
189
+ id: r.postId,
190
+ url: `https://facebook.com/${r.postId}`,
191
+ raw: r
192
+ };
193
+ }
194
+ if (input.link) {
195
+ const r = await this.provider.createLinkPost(token, pageId, input.link, {
196
+ message: input.text,
197
+ scheduledAt
198
+ });
199
+ return {
200
+ platform: "facebook",
201
+ ok: true,
202
+ id: r.postId,
203
+ url: `https://facebook.com/${r.postId}`,
204
+ raw: r
205
+ };
206
+ }
207
+ if (input.text) {
208
+ const r = await this.provider.createPost(token, pageId, input.text, { scheduledAt });
209
+ return {
210
+ platform: "facebook",
211
+ ok: true,
212
+ id: r.postId,
213
+ url: `https://facebook.com/${r.postId}`,
214
+ raw: r
215
+ };
216
+ }
217
+ throw new SocialError("facebook", "Provide either text, link, or photo media", { statusCode: 400 });
218
+ } catch (err) {
219
+ return errorResult("facebook", err);
220
+ }
221
+ }
222
+ async deletePost(postId) {
223
+ const { token } = this.assertPage();
224
+ await this.provider.deletePost(token, postId);
225
+ }
226
+ async getPost(postId) {
227
+ const { token } = this.assertPage();
228
+ return this.provider.getPost(token, postId);
229
+ }
230
+ async getFeed(limit) {
231
+ const { pageId, token } = this.assertPage();
232
+ return this.provider.getPageFeed(token, pageId, limit);
233
+ }
234
+ userToken() {
235
+ const t = this.config.tokens?.access_token;
236
+ if (!t) throw new SocialError("facebook", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
237
+ return t;
238
+ }
239
+ assertPage(input) {
240
+ const pageId = input?.perPlatform?.facebook?.pageId ?? this.config.pageId;
241
+ const token = input?.perPlatform?.facebook?.pageAccessToken ?? this.config.pageAccessToken;
242
+ if (!pageId || !token) throw new SocialError("facebook", "No Page selected. Call usePage(pageId) or set pageId/pageAccessToken in config.", { statusCode: 400 });
243
+ return {
244
+ pageId,
245
+ token
246
+ };
247
+ }
248
+ };
249
+
250
+ //#endregion
251
+ //#region src/client/linkedin.ts
252
+ /**
253
+ * LinkedInClient — credential-bound wrapper over `LinkedInProvider`.
254
+ */
255
+ var LinkedInClient = class {
256
+ provider;
257
+ constructor(config, opts = {}) {
258
+ this.config = config;
259
+ this.provider = new LinkedInProvider({
260
+ port: opts.port,
261
+ redirectUri: config.redirectUri,
262
+ environment: opts.environment
263
+ });
264
+ }
265
+ async authUrl(state) {
266
+ return this.provider.getAuthUrl(state, this.config);
267
+ }
268
+ async exchangeCode(code) {
269
+ const tokens = await this.provider.exchangeCode(code, this.config);
270
+ this.config.tokens = tokens;
271
+ return tokens;
272
+ }
273
+ async refresh() {
274
+ const refreshToken = this.config.tokens?.refresh_token;
275
+ if (!refreshToken) throw new SocialError("linkedin", "No refresh_token available");
276
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
277
+ this.config.tokens = {
278
+ ...this.config.tokens,
279
+ ...tokens
280
+ };
281
+ return this.config.tokens;
282
+ }
283
+ /** Resolve and cache the authenticated user's URN. */
284
+ async resolveAuthor() {
285
+ if (this.config.authorUrn) return this.config.authorUrn;
286
+ const accessToken = this.assertAccessToken();
287
+ const info = await this.provider.getAccountInfo(accessToken);
288
+ if (!info?.id) throw new SocialError("linkedin", "Could not resolve author URN", { statusCode: 502 });
289
+ const urn = `urn:li:person:${info.id}`;
290
+ this.config.authorUrn = urn;
291
+ return urn;
292
+ }
293
+ /** Create a post (text, article, or image). */
294
+ async post(input) {
295
+ try {
296
+ const accessToken = this.assertAccessToken();
297
+ const author = input.perPlatform?.linkedin?.authorUrn ?? await this.resolveAuthor();
298
+ if (input.link) {
299
+ const r = await this.provider.createArticlePost(accessToken, author, input.text ?? "", input.link);
300
+ return {
301
+ platform: "linkedin",
302
+ ok: true,
303
+ id: r.postId,
304
+ raw: r
305
+ };
306
+ }
307
+ if (!input.text) throw new SocialError("linkedin", "Provide text for the post", { statusCode: 400 });
308
+ const r = await this.provider.createTextPost(accessToken, author, input.text);
309
+ return {
310
+ platform: "linkedin",
311
+ ok: true,
312
+ id: r.postId,
313
+ raw: r
314
+ };
315
+ } catch (err) {
316
+ return errorResult("linkedin", err);
317
+ }
318
+ }
319
+ async deletePost(postUrn) {
320
+ const accessToken = this.assertAccessToken();
321
+ await this.provider.deletePost(accessToken, postUrn);
322
+ }
323
+ async getPost(postUrn) {
324
+ const accessToken = this.assertAccessToken();
325
+ return this.provider.getPost(accessToken, postUrn);
326
+ }
327
+ assertAccessToken() {
328
+ const t = this.config.tokens?.access_token;
329
+ if (!t) throw new SocialError("linkedin", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
330
+ return t;
331
+ }
332
+ };
333
+
334
+ //#endregion
335
+ //#region src/client/reddit.ts
336
+ /**
337
+ * RedditClient — credential-bound wrapper over `RedditProvider`.
338
+ *
339
+ * Reddit posts always require a `subreddit` and `title`. Pass them via
340
+ * `input.perPlatform.reddit`.
341
+ */
342
+ var RedditClient = class {
343
+ provider;
344
+ constructor(config, opts = {}) {
345
+ this.config = config;
346
+ this.provider = new RedditProvider({
347
+ port: opts.port,
348
+ redirectUri: config.redirectUri,
349
+ environment: opts.environment
350
+ });
351
+ }
352
+ async authUrl(state) {
353
+ return this.provider.getAuthUrl(state, this.config);
354
+ }
355
+ async exchangeCode(code) {
356
+ const tokens = await this.provider.exchangeCode(code, this.config);
357
+ this.config.tokens = tokens;
358
+ return tokens;
359
+ }
360
+ async refresh() {
361
+ const refreshToken = this.config.tokens?.refresh_token;
362
+ if (!refreshToken) throw new SocialError("reddit", "No refresh_token available");
363
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
364
+ this.config.tokens = {
365
+ ...this.config.tokens,
366
+ ...tokens
367
+ };
368
+ return this.config.tokens;
369
+ }
370
+ /** Submit to a subreddit. Requires `subreddit` + `title` in `perPlatform.reddit`. */
371
+ async post(input) {
372
+ try {
373
+ const accessToken = this.assertAccessToken();
374
+ const r = input.perPlatform?.reddit;
375
+ if (!r?.subreddit || !r.title) throw new SocialError("reddit", "Reddit posts require `perPlatform.reddit.subreddit` and `perPlatform.reddit.title`", { statusCode: 400 });
376
+ const kind = r.kind ?? (input.link ? "link" : "self");
377
+ const result = await this.provider.createPost(accessToken, {
378
+ subreddit: r.subreddit,
379
+ title: r.title,
380
+ kind,
381
+ text: input.text,
382
+ url: input.link
383
+ });
384
+ return {
385
+ platform: "reddit",
386
+ ok: true,
387
+ id: result.id,
388
+ url: result.url,
389
+ raw: result
390
+ };
391
+ } catch (err) {
392
+ return errorResult("reddit", err);
393
+ }
394
+ }
395
+ async deletePost(fullname) {
396
+ const accessToken = this.assertAccessToken();
397
+ await this.provider.deleteContent(accessToken, fullname);
398
+ }
399
+ async getPost(subreddit, postId) {
400
+ const accessToken = this.assertAccessToken();
401
+ return this.provider.getPost(accessToken, subreddit, postId);
402
+ }
403
+ assertAccessToken() {
404
+ const t = this.config.tokens?.access_token;
405
+ if (!t) throw new SocialError("reddit", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
406
+ return t;
407
+ }
408
+ };
409
+
410
+ //#endregion
411
+ //#region src/client/instagram.ts
412
+ /**
413
+ * InstagramClient — credential-bound wrapper over `InstagramProvider`.
414
+ */
415
+ var InstagramClient = class {
416
+ provider;
417
+ constructor(config, opts = {}) {
418
+ this.config = config;
419
+ this.provider = new InstagramProvider({
420
+ port: opts.port,
421
+ redirectUri: config.redirectUri,
422
+ environment: opts.environment
423
+ });
424
+ }
425
+ async authUrl(state) {
426
+ return this.provider.getAuthUrl(state, this.config);
427
+ }
428
+ async exchangeCode(code) {
429
+ const tokens = await this.provider.exchangeCode(code, this.config);
430
+ this.config.tokens = tokens;
431
+ return tokens;
432
+ }
433
+ async refresh() {
434
+ const refreshToken = this.config.tokens?.refresh_token ?? this.config.tokens?.access_token;
435
+ if (!refreshToken) throw new SocialError("instagram", "No refresh_token available");
436
+ const tokens = await this.provider.refreshToken(refreshToken);
437
+ this.config.tokens = {
438
+ ...this.config.tokens,
439
+ ...tokens
440
+ };
441
+ return this.config.tokens;
442
+ }
443
+ /** Resolve and cache the Instagram Business User ID. */
444
+ async resolveIgUser() {
445
+ if (this.config.igUserId) return this.config.igUserId;
446
+ const accessToken = this.assertAccessToken();
447
+ const info = await this.provider.getAccountInfo(accessToken);
448
+ if (!info?.id) throw new SocialError("instagram", "Could not resolve Instagram Business Account ID", { statusCode: 502 });
449
+ this.config.igUserId = info.id;
450
+ return info.id;
451
+ }
452
+ /**
453
+ * Post to Instagram. Behavior:
454
+ * - 1 image → `uploadPhoto`
455
+ * - 1 video → `uploadVideo` (Reel)
456
+ * - 2-10 mixed media → `uploadCarousel`
457
+ *
458
+ * Text-only posts are not supported by Instagram.
459
+ */
460
+ async post(input) {
461
+ try {
462
+ const accessToken = this.assertAccessToken();
463
+ const igUserId = input.perPlatform?.instagram?.igUserId ?? await this.resolveIgUser();
464
+ const media = input.media ?? [];
465
+ if (media.length === 0) throw new SocialError("instagram", "Instagram requires at least one media item (image or video)", { statusCode: 400 });
466
+ if (media.length === 1) {
467
+ const m = media[0];
468
+ if (!m.url) throw new SocialError("instagram", "Instagram requires a public URL for each media item (no pre-uploaded media support)", { statusCode: 400 });
469
+ if (m.type === "image") {
470
+ const r = await this.provider.uploadPhoto({
471
+ imageUrl: m.url,
472
+ caption: input.text,
473
+ tokens: {
474
+ access_token: accessToken,
475
+ igUserId
476
+ }
477
+ });
478
+ return {
479
+ platform: "instagram",
480
+ ok: true,
481
+ id: r.mediaId,
482
+ raw: r
483
+ };
484
+ }
485
+ const r = await this.provider.uploadVideo({
486
+ videoUrl: m.url,
487
+ caption: input.text,
488
+ tokens: {
489
+ access_token: accessToken,
490
+ igUserId
491
+ }
492
+ });
493
+ return {
494
+ platform: "instagram",
495
+ ok: true,
496
+ id: r.platformVideoId ?? r.platformPostId,
497
+ raw: r
498
+ };
499
+ }
500
+ const items = media.filter((m) => !!m.url);
501
+ if (items.length !== media.length) throw new SocialError("instagram", "Instagram carousels require URLs for every media item", { statusCode: 400 });
502
+ const r = await this.provider.uploadCarousel({
503
+ items: items.map((m) => ({
504
+ type: m.type === "image" ? "IMAGE" : "VIDEO",
505
+ url: m.url
506
+ })),
507
+ caption: input.text,
508
+ tokens: {
509
+ access_token: accessToken,
510
+ igUserId
511
+ }
512
+ });
513
+ return {
514
+ platform: "instagram",
515
+ ok: true,
516
+ id: r.mediaId,
517
+ raw: r
518
+ };
519
+ } catch (err) {
520
+ return errorResult("instagram", err);
521
+ }
522
+ }
523
+ async getMedia(mediaId) {
524
+ const accessToken = this.assertAccessToken();
525
+ return this.provider.getMedia(accessToken, mediaId);
526
+ }
527
+ async listMedia(opts) {
528
+ const accessToken = this.assertAccessToken();
529
+ const igUserId = this.config.igUserId ?? await this.resolveIgUser();
530
+ return this.provider.listMedia(accessToken, igUserId, opts);
531
+ }
532
+ assertAccessToken() {
533
+ const t = this.config.tokens?.access_token;
534
+ if (!t) throw new SocialError("instagram", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
535
+ return t;
536
+ }
537
+ };
538
+
539
+ //#endregion
540
+ //#region src/client/youtube.ts
541
+ /**
542
+ * YouTubeClient — credential-bound wrapper over `YouTubeProvider`.
543
+ *
544
+ * YouTube doesn't support text-only posts; use `upload()` to publish videos.
545
+ */
546
+ var YouTubeClient = class {
547
+ provider;
548
+ constructor(config, opts = {}) {
549
+ this.config = config;
550
+ this.provider = new YouTubeProvider({
551
+ port: opts.port,
552
+ redirectUri: config.redirectUri,
553
+ environment: opts.environment
554
+ });
555
+ }
556
+ authUrl(state) {
557
+ return this.provider.getAuthUrl(state, this.config);
558
+ }
559
+ async exchangeCode(code) {
560
+ const tokens = await this.provider.exchangeCode(code, this.config);
561
+ this.config.tokens = tokens;
562
+ return tokens;
563
+ }
564
+ async refresh() {
565
+ const refreshToken = this.config.tokens?.refresh_token;
566
+ if (!refreshToken) throw new SocialError("youtube", "No refresh_token available");
567
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
568
+ this.config.tokens = {
569
+ ...this.config.tokens,
570
+ ...tokens
571
+ };
572
+ return this.config.tokens;
573
+ }
574
+ /** Upload a video. */
575
+ async upload(input) {
576
+ try {
577
+ this.assertAccessToken();
578
+ const r = await this.provider.uploadVideo({
579
+ filePath: input.filePath,
580
+ videoUrl: input.videoUrl,
581
+ title: input.title ?? "",
582
+ description: input.description,
583
+ tags: input.tags,
584
+ privacy: input.privacy,
585
+ scheduledAt: input.scheduledAt,
586
+ credentials: this.config,
587
+ tokens: this.config.tokens
588
+ });
589
+ return {
590
+ platform: "youtube",
591
+ ok: true,
592
+ id: r.platformVideoId ?? r.platformPostId,
593
+ url: r.platformUrl ?? (r.platformVideoId ? `https://youtu.be/${r.platformVideoId}` : void 0),
594
+ raw: r
595
+ };
596
+ } catch (err) {
597
+ return errorResult("youtube", err);
598
+ }
599
+ }
600
+ async deleteVideo(videoId) {
601
+ const accessToken = this.assertAccessToken();
602
+ await this.provider.deleteVideo(accessToken, videoId, this.config);
603
+ }
604
+ async getVideo(videoId) {
605
+ const accessToken = this.assertAccessToken();
606
+ return this.provider.getVideo(accessToken, videoId, this.config);
607
+ }
608
+ async listVideos(opts) {
609
+ const accessToken = this.assertAccessToken();
610
+ return this.provider.listVideos(accessToken, this.config, opts);
611
+ }
612
+ assertAccessToken() {
613
+ const t = this.config.tokens?.access_token;
614
+ if (!t) throw new SocialError("youtube", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
615
+ return t;
616
+ }
617
+ };
618
+
619
+ //#endregion
620
+ //#region src/client/tiktok.ts
621
+ /**
622
+ * TikTokClient — credential-bound wrapper over `TikTokProvider`.
623
+ */
624
+ var TikTokClient = class {
625
+ provider;
626
+ constructor(config, opts = {}) {
627
+ this.config = config;
628
+ this.provider = new TikTokProvider({
629
+ port: opts.port,
630
+ redirectUri: config.redirectUri,
631
+ environment: opts.environment
632
+ });
633
+ }
634
+ authUrl(state) {
635
+ return this.provider.getAuthUrl(state, this.config);
636
+ }
637
+ async exchangeCode(code, state) {
638
+ const tokens = await this.provider.exchangeCode(code, this.config, state);
639
+ this.config.tokens = tokens;
640
+ return tokens;
641
+ }
642
+ async refresh() {
643
+ const refreshToken = this.config.tokens?.refresh_token;
644
+ if (!refreshToken) throw new SocialError("tiktok", "No refresh_token available");
645
+ const tokens = await this.provider.refreshToken(refreshToken, this.config);
646
+ this.config.tokens = {
647
+ ...this.config.tokens,
648
+ ...tokens
649
+ };
650
+ return this.config.tokens;
651
+ }
652
+ /** Upload a video. */
653
+ async upload(input) {
654
+ try {
655
+ this.assertAccessToken();
656
+ const r = await this.provider.uploadVideo({
657
+ filePath: input.filePath,
658
+ videoUrl: input.videoUrl,
659
+ title: input.title ?? input.caption,
660
+ description: input.description ?? input.caption,
661
+ privacy: input.privacy,
662
+ scheduledAt: input.scheduledAt,
663
+ credentials: this.config,
664
+ tokens: this.config.tokens
665
+ });
666
+ return {
667
+ platform: "tiktok",
668
+ ok: true,
669
+ id: r.platformVideoId ?? r.platformPostId,
670
+ url: r.platformUrl ?? null,
671
+ raw: r
672
+ };
673
+ } catch (err) {
674
+ return errorResult("tiktok", err);
675
+ }
676
+ }
677
+ async listVideos(opts) {
678
+ const accessToken = this.assertAccessToken();
679
+ return this.provider.listVideos(accessToken, opts);
680
+ }
681
+ assertAccessToken() {
682
+ const t = this.config.tokens?.access_token;
683
+ if (!t) throw new SocialError("tiktok", "Not authenticated. Call exchangeCode() first.", { statusCode: 401 });
684
+ return t;
685
+ }
686
+ };
687
+
688
+ //#endregion
689
+ //#region src/client/telegram.ts
690
+ /**
691
+ * TelegramClient — credential-bound wrapper over `TelegramProvider`.
692
+ *
693
+ * Telegram uses Bot Tokens (no OAuth). The bot must be added as admin to the
694
+ * target channel/group.
695
+ */
696
+ var TelegramClient = class {
697
+ provider;
698
+ constructor(config, opts = {}) {
699
+ this.config = config;
700
+ this.provider = new TelegramProvider({ port: opts.port });
701
+ }
702
+ /** Send a text message. Falls back to `config.chatId` if `to` is omitted. */
703
+ async send(text, to) {
704
+ try {
705
+ const chatId = this.assertChatId(to);
706
+ const result = await this.provider.sendMessage(this.config.botToken, chatId, text);
707
+ return {
708
+ platform: "telegram",
709
+ ok: true,
710
+ id: String(result.message_id ?? ""),
711
+ raw: result
712
+ };
713
+ } catch (err) {
714
+ return errorResultMsg$1(err);
715
+ }
716
+ }
717
+ async sendPhoto(photoUrl, opts = {}) {
718
+ const chatId = this.assertChatId(opts.to);
719
+ return this.provider.sendPhoto(this.config.botToken, chatId, photoUrl, { caption: opts.caption });
720
+ }
721
+ async sendVideo(videoUrl, opts = {}) {
722
+ const chatId = this.assertChatId(opts.to);
723
+ return this.provider.sendVideo(this.config.botToken, chatId, videoUrl, { caption: opts.caption });
724
+ }
725
+ async sendDocument(documentUrl, opts = {}) {
726
+ const chatId = this.assertChatId(opts.to);
727
+ return this.provider.sendDocument(this.config.botToken, chatId, documentUrl, { caption: opts.caption });
728
+ }
729
+ async deleteMessage(messageId, to) {
730
+ const chatId = this.assertChatId(to);
731
+ return this.provider.deleteMessage(this.config.botToken, chatId, Number(messageId));
732
+ }
733
+ async getChat(chatId) {
734
+ return this.provider.getChat(this.config.botToken, this.assertChatId(chatId));
735
+ }
736
+ async discoverChats() {
737
+ return this.provider.discoverChats(this.config.botToken);
738
+ }
739
+ async me() {
740
+ return this.provider.getAccountInfo(this.config.botToken);
741
+ }
742
+ assertChatId(to) {
743
+ const id = to ?? this.config.chatId;
744
+ if (id === void 0 || id === null || id === "") throw new SocialError("telegram", "No chat ID provided. Pass `to` or set `chatId` in config.", { statusCode: 400 });
745
+ return id;
746
+ }
747
+ };
748
+ function errorResultMsg$1(err) {
749
+ if (err instanceof SocialError) return {
750
+ platform: "telegram",
751
+ ok: false,
752
+ error: {
753
+ message: err.message,
754
+ code: err.errorCode
755
+ }
756
+ };
757
+ return {
758
+ platform: "telegram",
759
+ ok: false,
760
+ error: { message: err instanceof Error ? err.message : String(err) }
761
+ };
762
+ }
763
+
764
+ //#endregion
765
+ //#region src/client/whatsapp.ts
766
+ /**
767
+ * WhatsAppClient — credential-bound wrapper over `WhatsAppProvider`.
768
+ *
769
+ * WhatsApp uses permanent System User tokens (no OAuth).
770
+ */
771
+ var WhatsAppClient = class {
772
+ provider;
773
+ constructor(config, opts = {}) {
774
+ this.config = config;
775
+ this.provider = new WhatsAppProvider({ port: opts.port });
776
+ }
777
+ /** Send a text message to an E.164 phone number. */
778
+ async send(text, to) {
779
+ try {
780
+ const phoneNumberId = this.assertPhoneNumberId();
781
+ const result = await this.provider.sendMessage(this.config.accessToken, phoneNumberId, {
782
+ to,
783
+ type: "text",
784
+ text: { body: text }
785
+ });
786
+ return {
787
+ platform: "whatsapp",
788
+ ok: true,
789
+ id: result.messages?.[0]?.id,
790
+ raw: result
791
+ };
792
+ } catch (err) {
793
+ return errorResultMsg(err);
794
+ }
795
+ }
796
+ async sendImage(to, imageUrl, caption) {
797
+ const phoneNumberId = this.assertPhoneNumberId();
798
+ return this.provider.sendImage(this.config.accessToken, phoneNumberId, {
799
+ to,
800
+ imageUrl,
801
+ caption
802
+ });
803
+ }
804
+ async sendVideo(to, videoUrl, caption) {
805
+ const phoneNumberId = this.assertPhoneNumberId();
806
+ return this.provider.sendVideo(this.config.accessToken, phoneNumberId, {
807
+ to,
808
+ videoUrl,
809
+ caption
810
+ });
811
+ }
812
+ async sendDocument(to, documentUrl, opts = {}) {
813
+ const phoneNumberId = this.assertPhoneNumberId();
814
+ return this.provider.sendDocument(this.config.accessToken, phoneNumberId, {
815
+ to,
816
+ documentUrl,
817
+ caption: opts.caption,
818
+ filename: opts.filename
819
+ });
820
+ }
821
+ async listPhoneNumbers() {
822
+ return this.provider.getPhoneNumbers(this.config.accessToken, this.config.businessAccountId);
823
+ }
824
+ async listTemplates() {
825
+ return this.provider.getTemplates(this.config.accessToken, this.config.businessAccountId);
826
+ }
827
+ assertPhoneNumberId() {
828
+ if (!this.config.phoneNumberId) throw new SocialError("whatsapp", "phoneNumberId is required. Set it in config or call listPhoneNumbers() first.", { statusCode: 400 });
829
+ return this.config.phoneNumberId;
830
+ }
831
+ };
832
+ function errorResultMsg(err) {
833
+ if (err instanceof SocialError) return {
834
+ platform: "whatsapp",
835
+ ok: false,
836
+ error: {
837
+ message: err.message,
838
+ code: err.errorCode
839
+ }
840
+ };
841
+ return {
842
+ platform: "whatsapp",
843
+ ok: false,
844
+ error: { message: err instanceof Error ? err.message : String(err) }
845
+ };
846
+ }
847
+
848
+ //#endregion
849
+ //#region src/client/env.ts
850
+ /**
851
+ * Auto-configure a `SocialClient` from environment variables.
852
+ *
853
+ * Recognized variables (every platform is optional):
854
+ *
855
+ * YOUTUBE_CLIENT_ID, YOUTUBE_CLIENT_SECRET, YOUTUBE_REDIRECT_URI,
856
+ * YOUTUBE_ACCESS_TOKEN, YOUTUBE_REFRESH_TOKEN
857
+ *
858
+ * TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET, TWITTER_REDIRECT_URI,
859
+ * TWITTER_ACCESS_TOKEN, TWITTER_REFRESH_TOKEN
860
+ *
861
+ * FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, FACEBOOK_REDIRECT_URI,
862
+ * FACEBOOK_ACCESS_TOKEN, FACEBOOK_PAGE_ID, FACEBOOK_PAGE_ACCESS_TOKEN
863
+ *
864
+ * INSTAGRAM_APP_ID, INSTAGRAM_APP_SECRET, INSTAGRAM_REDIRECT_URI,
865
+ * INSTAGRAM_ACCESS_TOKEN, INSTAGRAM_USER_ID
866
+ *
867
+ * LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET, LINKEDIN_REDIRECT_URI,
868
+ * LINKEDIN_ACCESS_TOKEN, LINKEDIN_AUTHOR_URN
869
+ *
870
+ * REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USER_AGENT,
871
+ * REDDIT_REDIRECT_URI, REDDIT_ACCESS_TOKEN, REDDIT_REFRESH_TOKEN
872
+ *
873
+ * TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET, TIKTOK_REDIRECT_URI,
874
+ * TIKTOK_ACCESS_TOKEN, TIKTOK_REFRESH_TOKEN
875
+ *
876
+ * TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
877
+ *
878
+ * WHATSAPP_ACCESS_TOKEN, WHATSAPP_BUSINESS_ACCOUNT_ID, WHATSAPP_PHONE_NUMBER_ID
879
+ */
880
+ function tokensFrom(env, prefix) {
881
+ const access = env[`${prefix}_ACCESS_TOKEN`];
882
+ if (!access) return void 0;
883
+ return {
884
+ access_token: access,
885
+ refresh_token: env[`${prefix}_REFRESH_TOKEN`]
886
+ };
887
+ }
888
+ /**
889
+ * Build a `SocialClient` from `process.env` (or any compatible record).
890
+ * Only platforms whose required vars are present are enabled.
891
+ */
892
+ function fromEnv(env = typeof process !== "undefined" ? process.env : {}, opts) {
893
+ const config = {};
894
+ if (env.YOUTUBE_CLIENT_ID && env.YOUTUBE_CLIENT_SECRET) config.youtube = {
895
+ clientId: env.YOUTUBE_CLIENT_ID,
896
+ clientSecret: env.YOUTUBE_CLIENT_SECRET,
897
+ redirectUri: env.YOUTUBE_REDIRECT_URI,
898
+ tokens: tokensFrom(env, "YOUTUBE")
899
+ };
900
+ if (env.TWITTER_CLIENT_ID) config.twitter = {
901
+ clientId: env.TWITTER_CLIENT_ID,
902
+ clientSecret: env.TWITTER_CLIENT_SECRET,
903
+ redirectUri: env.TWITTER_REDIRECT_URI,
904
+ tokens: tokensFrom(env, "TWITTER")
905
+ };
906
+ if (env.FACEBOOK_APP_ID && env.FACEBOOK_APP_SECRET) config.facebook = {
907
+ appId: env.FACEBOOK_APP_ID,
908
+ appSecret: env.FACEBOOK_APP_SECRET,
909
+ redirectUri: env.FACEBOOK_REDIRECT_URI,
910
+ tokens: tokensFrom(env, "FACEBOOK"),
911
+ pageId: env.FACEBOOK_PAGE_ID,
912
+ pageAccessToken: env.FACEBOOK_PAGE_ACCESS_TOKEN
913
+ };
914
+ if (env.INSTAGRAM_APP_ID && env.INSTAGRAM_APP_SECRET) config.instagram = {
915
+ appId: env.INSTAGRAM_APP_ID,
916
+ appSecret: env.INSTAGRAM_APP_SECRET,
917
+ redirectUri: env.INSTAGRAM_REDIRECT_URI,
918
+ tokens: tokensFrom(env, "INSTAGRAM"),
919
+ igUserId: env.INSTAGRAM_USER_ID
920
+ };
921
+ if (env.LINKEDIN_CLIENT_ID && env.LINKEDIN_CLIENT_SECRET) config.linkedin = {
922
+ clientId: env.LINKEDIN_CLIENT_ID,
923
+ clientSecret: env.LINKEDIN_CLIENT_SECRET,
924
+ redirectUri: env.LINKEDIN_REDIRECT_URI,
925
+ tokens: tokensFrom(env, "LINKEDIN"),
926
+ authorUrn: env.LINKEDIN_AUTHOR_URN
927
+ };
928
+ if (env.REDDIT_CLIENT_ID && env.REDDIT_CLIENT_SECRET && env.REDDIT_USER_AGENT) config.reddit = {
929
+ clientId: env.REDDIT_CLIENT_ID,
930
+ clientSecret: env.REDDIT_CLIENT_SECRET,
931
+ userAgent: env.REDDIT_USER_AGENT,
932
+ redirectUri: env.REDDIT_REDIRECT_URI,
933
+ tokens: tokensFrom(env, "REDDIT")
934
+ };
935
+ if (env.TIKTOK_CLIENT_KEY && env.TIKTOK_CLIENT_SECRET) config.tiktok = {
936
+ clientKey: env.TIKTOK_CLIENT_KEY,
937
+ clientSecret: env.TIKTOK_CLIENT_SECRET,
938
+ redirectUri: env.TIKTOK_REDIRECT_URI,
939
+ tokens: tokensFrom(env, "TIKTOK")
940
+ };
941
+ if (env.TELEGRAM_BOT_TOKEN) config.telegram = {
942
+ botToken: env.TELEGRAM_BOT_TOKEN,
943
+ chatId: env.TELEGRAM_CHAT_ID
944
+ };
945
+ if (env.WHATSAPP_ACCESS_TOKEN && env.WHATSAPP_BUSINESS_ACCOUNT_ID) config.whatsapp = {
946
+ accessToken: env.WHATSAPP_ACCESS_TOKEN,
947
+ businessAccountId: env.WHATSAPP_BUSINESS_ACCOUNT_ID,
948
+ phoneNumberId: env.WHATSAPP_PHONE_NUMBER_ID
949
+ };
950
+ return new SocialClient(config, opts);
951
+ }
952
+
953
+ //#endregion
954
+ export { YouTubeClient as a, LinkedInClient as c, TikTokClient as i, FacebookClient as l, WhatsAppClient as n, InstagramClient as o, TelegramClient as r, RedditClient as s, fromEnv as t, TwitterClient as u };
955
+ //# sourceMappingURL=env-Bl0cwwjC.mjs.map