@memberjunction/actions-bizapps-social 2.111.1 → 2.112.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 (204) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +6 -6
  3. package/dist/base/base-social.action.d.ts.map +1 -1
  4. package/dist/base/base-social.action.js +18 -24
  5. package/dist/base/base-social.action.js.map +1 -1
  6. package/dist/providers/buffer/buffer-base.action.d.ts.map +1 -1
  7. package/dist/providers/buffer/buffer-base.action.js +35 -34
  8. package/dist/providers/buffer/buffer-base.action.js.map +1 -1
  9. package/dist/providers/facebook/actions/boost-post.action.d.ts.map +1 -1
  10. package/dist/providers/facebook/actions/boost-post.action.js +33 -33
  11. package/dist/providers/facebook/actions/boost-post.action.js.map +1 -1
  12. package/dist/providers/facebook/actions/create-album.action.d.ts.map +1 -1
  13. package/dist/providers/facebook/actions/create-album.action.js +34 -36
  14. package/dist/providers/facebook/actions/create-album.action.js.map +1 -1
  15. package/dist/providers/facebook/actions/create-post.action.d.ts.map +1 -1
  16. package/dist/providers/facebook/actions/create-post.action.js +20 -20
  17. package/dist/providers/facebook/actions/create-post.action.js.map +1 -1
  18. package/dist/providers/facebook/actions/get-page-insights.action.d.ts.map +1 -1
  19. package/dist/providers/facebook/actions/get-page-insights.action.js +25 -27
  20. package/dist/providers/facebook/actions/get-page-insights.action.js.map +1 -1
  21. package/dist/providers/facebook/actions/get-page-posts.action.d.ts.map +1 -1
  22. package/dist/providers/facebook/actions/get-page-posts.action.js +19 -23
  23. package/dist/providers/facebook/actions/get-page-posts.action.js.map +1 -1
  24. package/dist/providers/facebook/actions/get-post-insights.action.d.ts.map +1 -1
  25. package/dist/providers/facebook/actions/get-post-insights.action.js +28 -32
  26. package/dist/providers/facebook/actions/get-post-insights.action.js.map +1 -1
  27. package/dist/providers/facebook/actions/respond-to-comments.action.d.ts.map +1 -1
  28. package/dist/providers/facebook/actions/respond-to-comments.action.js +42 -44
  29. package/dist/providers/facebook/actions/respond-to-comments.action.js.map +1 -1
  30. package/dist/providers/facebook/actions/schedule-post.action.d.ts.map +1 -1
  31. package/dist/providers/facebook/actions/schedule-post.action.js +29 -29
  32. package/dist/providers/facebook/actions/schedule-post.action.js.map +1 -1
  33. package/dist/providers/facebook/actions/search-posts.action.d.ts.map +1 -1
  34. package/dist/providers/facebook/actions/search-posts.action.js +37 -39
  35. package/dist/providers/facebook/actions/search-posts.action.js.map +1 -1
  36. package/dist/providers/facebook/facebook-base.action.d.ts.map +1 -1
  37. package/dist/providers/facebook/facebook-base.action.js +44 -59
  38. package/dist/providers/facebook/facebook-base.action.js.map +1 -1
  39. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.d.ts.map +1 -1
  40. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js +33 -31
  41. package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js.map +1 -1
  42. package/dist/providers/hootsuite/actions/create-scheduled-post.action.d.ts.map +1 -1
  43. package/dist/providers/hootsuite/actions/create-scheduled-post.action.js +28 -32
  44. package/dist/providers/hootsuite/actions/create-scheduled-post.action.js.map +1 -1
  45. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.d.ts.map +1 -1
  46. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js +19 -19
  47. package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js.map +1 -1
  48. package/dist/providers/hootsuite/actions/get-analytics.action.d.ts.map +1 -1
  49. package/dist/providers/hootsuite/actions/get-analytics.action.js +24 -26
  50. package/dist/providers/hootsuite/actions/get-analytics.action.js.map +1 -1
  51. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.d.ts.map +1 -1
  52. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js +22 -22
  53. package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js.map +1 -1
  54. package/dist/providers/hootsuite/actions/get-social-profiles.action.d.ts.map +1 -1
  55. package/dist/providers/hootsuite/actions/get-social-profiles.action.js +32 -34
  56. package/dist/providers/hootsuite/actions/get-social-profiles.action.js.map +1 -1
  57. package/dist/providers/hootsuite/actions/search-posts.action.d.ts.map +1 -1
  58. package/dist/providers/hootsuite/actions/search-posts.action.js +43 -52
  59. package/dist/providers/hootsuite/actions/search-posts.action.js.map +1 -1
  60. package/dist/providers/hootsuite/actions/update-scheduled-post.action.d.ts.map +1 -1
  61. package/dist/providers/hootsuite/actions/update-scheduled-post.action.js +30 -28
  62. package/dist/providers/hootsuite/actions/update-scheduled-post.action.js.map +1 -1
  63. package/dist/providers/hootsuite/hootsuite-base.action.d.ts.map +1 -1
  64. package/dist/providers/hootsuite/hootsuite-base.action.js +18 -20
  65. package/dist/providers/hootsuite/hootsuite-base.action.js.map +1 -1
  66. package/dist/providers/instagram/actions/create-post.action.d.ts.map +1 -1
  67. package/dist/providers/instagram/actions/create-post.action.js +27 -26
  68. package/dist/providers/instagram/actions/create-post.action.js.map +1 -1
  69. package/dist/providers/instagram/actions/create-story.action.d.ts.map +1 -1
  70. package/dist/providers/instagram/actions/create-story.action.js +35 -35
  71. package/dist/providers/instagram/actions/create-story.action.js.map +1 -1
  72. package/dist/providers/instagram/actions/get-account-insights.action.d.ts.map +1 -1
  73. package/dist/providers/instagram/actions/get-account-insights.action.js +38 -59
  74. package/dist/providers/instagram/actions/get-account-insights.action.js.map +1 -1
  75. package/dist/providers/instagram/actions/get-business-posts.action.d.ts.map +1 -1
  76. package/dist/providers/instagram/actions/get-business-posts.action.js +29 -29
  77. package/dist/providers/instagram/actions/get-business-posts.action.js.map +1 -1
  78. package/dist/providers/instagram/actions/get-comments.action.d.ts.map +1 -1
  79. package/dist/providers/instagram/actions/get-comments.action.js +36 -36
  80. package/dist/providers/instagram/actions/get-comments.action.js.map +1 -1
  81. package/dist/providers/instagram/actions/get-post-insights.action.d.ts.map +1 -1
  82. package/dist/providers/instagram/actions/get-post-insights.action.js +23 -25
  83. package/dist/providers/instagram/actions/get-post-insights.action.js.map +1 -1
  84. package/dist/providers/instagram/actions/schedule-post.action.d.ts.map +1 -1
  85. package/dist/providers/instagram/actions/schedule-post.action.js +25 -25
  86. package/dist/providers/instagram/actions/schedule-post.action.js.map +1 -1
  87. package/dist/providers/instagram/actions/search-posts.action.d.ts.map +1 -1
  88. package/dist/providers/instagram/actions/search-posts.action.js +56 -60
  89. package/dist/providers/instagram/actions/search-posts.action.js.map +1 -1
  90. package/dist/providers/instagram/instagram-base.action.d.ts.map +1 -1
  91. package/dist/providers/instagram/instagram-base.action.js +25 -27
  92. package/dist/providers/instagram/instagram-base.action.js.map +1 -1
  93. package/dist/providers/linkedin/actions/create-article.action.d.ts.map +1 -1
  94. package/dist/providers/linkedin/actions/create-article.action.js +55 -45
  95. package/dist/providers/linkedin/actions/create-article.action.js.map +1 -1
  96. package/dist/providers/linkedin/actions/create-post.action.d.ts.map +1 -1
  97. package/dist/providers/linkedin/actions/create-post.action.js +31 -29
  98. package/dist/providers/linkedin/actions/create-post.action.js.map +1 -1
  99. package/dist/providers/linkedin/actions/get-followers.action.d.ts.map +1 -1
  100. package/dist/providers/linkedin/actions/get-followers.action.js +28 -28
  101. package/dist/providers/linkedin/actions/get-followers.action.js.map +1 -1
  102. package/dist/providers/linkedin/actions/get-organization-posts.action.d.ts.map +1 -1
  103. package/dist/providers/linkedin/actions/get-organization-posts.action.js +20 -20
  104. package/dist/providers/linkedin/actions/get-organization-posts.action.js.map +1 -1
  105. package/dist/providers/linkedin/actions/get-personal-posts.action.d.ts.map +1 -1
  106. package/dist/providers/linkedin/actions/get-personal-posts.action.js +19 -19
  107. package/dist/providers/linkedin/actions/get-personal-posts.action.js.map +1 -1
  108. package/dist/providers/linkedin/actions/get-post-analytics.action.d.ts.map +1 -1
  109. package/dist/providers/linkedin/actions/get-post-analytics.action.js +25 -23
  110. package/dist/providers/linkedin/actions/get-post-analytics.action.js.map +1 -1
  111. package/dist/providers/linkedin/actions/schedule-post.action.d.ts.map +1 -1
  112. package/dist/providers/linkedin/actions/schedule-post.action.js +32 -30
  113. package/dist/providers/linkedin/actions/schedule-post.action.js.map +1 -1
  114. package/dist/providers/linkedin/actions/search-posts.action.d.ts.map +1 -1
  115. package/dist/providers/linkedin/actions/search-posts.action.js +28 -30
  116. package/dist/providers/linkedin/actions/search-posts.action.js.map +1 -1
  117. package/dist/providers/linkedin/linkedin-base.action.d.ts.map +1 -1
  118. package/dist/providers/linkedin/linkedin-base.action.js +33 -38
  119. package/dist/providers/linkedin/linkedin-base.action.js.map +1 -1
  120. package/dist/providers/tiktok/tiktok-base.action.d.ts.map +1 -1
  121. package/dist/providers/tiktok/tiktok-base.action.js +25 -26
  122. package/dist/providers/tiktok/tiktok-base.action.js.map +1 -1
  123. package/dist/providers/twitter/actions/create-thread.action.d.ts.map +1 -1
  124. package/dist/providers/twitter/actions/create-thread.action.js +25 -29
  125. package/dist/providers/twitter/actions/create-thread.action.js.map +1 -1
  126. package/dist/providers/twitter/actions/create-tweet.action.d.ts.map +1 -1
  127. package/dist/providers/twitter/actions/create-tweet.action.js +23 -23
  128. package/dist/providers/twitter/actions/create-tweet.action.js.map +1 -1
  129. package/dist/providers/twitter/actions/delete-tweet.action.d.ts.map +1 -1
  130. package/dist/providers/twitter/actions/delete-tweet.action.js +19 -19
  131. package/dist/providers/twitter/actions/delete-tweet.action.js.map +1 -1
  132. package/dist/providers/twitter/actions/get-analytics.action.d.ts.map +1 -1
  133. package/dist/providers/twitter/actions/get-analytics.action.js +40 -47
  134. package/dist/providers/twitter/actions/get-analytics.action.js.map +1 -1
  135. package/dist/providers/twitter/actions/get-mentions.action.d.ts.map +1 -1
  136. package/dist/providers/twitter/actions/get-mentions.action.js +30 -31
  137. package/dist/providers/twitter/actions/get-mentions.action.js.map +1 -1
  138. package/dist/providers/twitter/actions/get-timeline.action.d.ts.map +1 -1
  139. package/dist/providers/twitter/actions/get-timeline.action.js +29 -29
  140. package/dist/providers/twitter/actions/get-timeline.action.js.map +1 -1
  141. package/dist/providers/twitter/actions/schedule-tweet.action.d.ts.map +1 -1
  142. package/dist/providers/twitter/actions/schedule-tweet.action.js +26 -26
  143. package/dist/providers/twitter/actions/schedule-tweet.action.js.map +1 -1
  144. package/dist/providers/twitter/actions/search-tweets.action.d.ts.map +1 -1
  145. package/dist/providers/twitter/actions/search-tweets.action.js +56 -58
  146. package/dist/providers/twitter/actions/search-tweets.action.js.map +1 -1
  147. package/dist/providers/twitter/twitter-base.action.d.ts.map +1 -1
  148. package/dist/providers/twitter/twitter-base.action.js +58 -68
  149. package/dist/providers/twitter/twitter-base.action.js.map +1 -1
  150. package/dist/providers/youtube/youtube-base.action.d.ts +1 -1
  151. package/dist/providers/youtube/youtube-base.action.d.ts.map +1 -1
  152. package/dist/providers/youtube/youtube-base.action.js +22 -25
  153. package/dist/providers/youtube/youtube-base.action.js.map +1 -1
  154. package/package.json +5 -6
  155. package/src/base/base-social.action.ts +217 -224
  156. package/src/providers/buffer/buffer-base.action.ts +435 -441
  157. package/src/providers/facebook/actions/boost-post.action.ts +350 -386
  158. package/src/providers/facebook/actions/create-album.action.ts +291 -307
  159. package/src/providers/facebook/actions/create-post.action.ts +224 -227
  160. package/src/providers/facebook/actions/get-page-insights.action.ts +383 -403
  161. package/src/providers/facebook/actions/get-page-posts.action.ts +214 -225
  162. package/src/providers/facebook/actions/get-post-insights.action.ts +300 -316
  163. package/src/providers/facebook/actions/respond-to-comments.action.ts +319 -336
  164. package/src/providers/facebook/actions/schedule-post.action.ts +289 -292
  165. package/src/providers/facebook/actions/search-posts.action.ts +399 -413
  166. package/src/providers/facebook/facebook-base.action.ts +653 -670
  167. package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
  168. package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +184 -189
  169. package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +160 -161
  170. package/src/providers/hootsuite/actions/get-analytics.action.ts +249 -254
  171. package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +206 -207
  172. package/src/providers/hootsuite/actions/get-social-profiles.action.ts +206 -205
  173. package/src/providers/hootsuite/actions/search-posts.action.ts +351 -369
  174. package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +211 -209
  175. package/src/providers/hootsuite/hootsuite-base.action.ts +301 -307
  176. package/src/providers/instagram/actions/create-post.action.ts +276 -296
  177. package/src/providers/instagram/actions/create-story.action.ts +378 -394
  178. package/src/providers/instagram/actions/get-account-insights.action.ts +384 -420
  179. package/src/providers/instagram/actions/get-business-posts.action.ts +233 -242
  180. package/src/providers/instagram/actions/get-comments.action.ts +365 -377
  181. package/src/providers/instagram/actions/get-post-insights.action.ts +265 -273
  182. package/src/providers/instagram/actions/schedule-post.action.ts +233 -235
  183. package/src/providers/instagram/actions/search-posts.action.ts +512 -538
  184. package/src/providers/instagram/instagram-base.action.ts +368 -393
  185. package/src/providers/linkedin/actions/create-article.action.ts +275 -266
  186. package/src/providers/linkedin/actions/create-post.action.ts +179 -177
  187. package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
  188. package/src/providers/linkedin/actions/get-organization-posts.action.ts +146 -147
  189. package/src/providers/linkedin/actions/get-personal-posts.action.ts +138 -139
  190. package/src/providers/linkedin/actions/get-post-analytics.action.ts +190 -189
  191. package/src/providers/linkedin/actions/schedule-post.action.ts +191 -189
  192. package/src/providers/linkedin/actions/search-posts.action.ts +275 -283
  193. package/src/providers/linkedin/linkedin-base.action.ts +407 -421
  194. package/src/providers/tiktok/tiktok-base.action.ts +305 -320
  195. package/src/providers/twitter/actions/create-thread.action.ts +203 -207
  196. package/src/providers/twitter/actions/create-tweet.action.ts +187 -188
  197. package/src/providers/twitter/actions/delete-tweet.action.ts +128 -129
  198. package/src/providers/twitter/actions/get-analytics.action.ts +402 -411
  199. package/src/providers/twitter/actions/get-mentions.action.ts +218 -219
  200. package/src/providers/twitter/actions/get-timeline.action.ts +232 -233
  201. package/src/providers/twitter/actions/schedule-tweet.action.ts +221 -222
  202. package/src/providers/twitter/actions/search-tweets.action.ts +540 -543
  203. package/src/providers/twitter/twitter-base.action.ts +541 -560
  204. package/src/providers/youtube/youtube-base.action.ts +320 -333
@@ -1,6 +1,6 @@
1
1
  import { RegisterClass } from '@memberjunction/global';
2
2
  import { BaseSocialMediaAction, SocialPost, SocialAnalytics, MediaFile } from '../../base/base-social.action';
3
- import { UserInfo, LogError, LogStatus } from '@memberjunction/core';
3
+ import { UserInfo, LogError, LogStatus } from '@memberjunction/global';
4
4
  import axios, { AxiosInstance, AxiosError } from 'axios';
5
5
  import FormData from 'form-data';
6
6
  import { BaseAction } from '@memberjunction/actions';
@@ -11,481 +11,475 @@ import { BaseAction } from '@memberjunction/actions';
11
11
  */
12
12
  @RegisterClass(BaseAction, 'BufferBaseAction')
13
13
  export abstract class BufferBaseAction extends BaseSocialMediaAction {
14
- protected get platformName(): string {
15
- return 'Buffer';
14
+ protected get platformName(): string {
15
+ return 'Buffer';
16
+ }
17
+
18
+ protected get apiBaseUrl(): string {
19
+ return 'https://api.bufferapp.com/1';
20
+ }
21
+
22
+ private axiosInstance: AxiosInstance | null = null;
23
+
24
+ /**
25
+ * Get axios instance with authentication
26
+ */
27
+ protected getAxiosInstance(): AxiosInstance {
28
+ if (!this.axiosInstance) {
29
+ this.axiosInstance = axios.create({
30
+ baseURL: this.apiBaseUrl,
31
+ timeout: 30000,
32
+ headers: {
33
+ Accept: 'application/json',
34
+ 'Content-Type': 'application/json',
35
+ },
36
+ });
37
+
38
+ // Add request interceptor for auth
39
+ this.axiosInstance.interceptors.request.use(
40
+ (config) => {
41
+ const token = this.getAccessToken();
42
+ if (token) {
43
+ config.headers.Authorization = `Bearer ${token}`;
44
+ }
45
+ return config;
46
+ },
47
+ (error) => {
48
+ return Promise.reject(error);
49
+ }
50
+ );
51
+
52
+ // Add response interceptor for rate limiting
53
+ this.axiosInstance.interceptors.response.use(
54
+ (response) => {
55
+ // Log rate limit headers if present
56
+ const headers = response.headers;
57
+ const rateLimitInfo = this.parseRateLimitHeaders(headers);
58
+ if (rateLimitInfo) {
59
+ LogStatus(`Buffer API - Remaining requests: ${rateLimitInfo.remaining}/${rateLimitInfo.limit}, Reset: ${rateLimitInfo.reset}`);
60
+ }
61
+ return response;
62
+ },
63
+ async (error: AxiosError) => {
64
+ if (error.response?.status === 429) {
65
+ // Rate limit hit
66
+ const retryAfter = error.response.headers['retry-after'];
67
+ await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : 60);
68
+ // Retry the request
69
+ return this.axiosInstance!.request(error.config!);
70
+ }
71
+ return Promise.reject(error);
72
+ }
73
+ );
16
74
  }
17
-
18
- protected get apiBaseUrl(): string {
19
- return 'https://api.bufferapp.com/1';
75
+ return this.axiosInstance;
76
+ }
77
+
78
+ /**
79
+ * Refresh access token using refresh token
80
+ */
81
+ protected async refreshAccessToken(): Promise<void> {
82
+ const refreshToken = this.getRefreshToken();
83
+ if (!refreshToken) {
84
+ throw new Error('No refresh token available for Buffer');
20
85
  }
21
86
 
22
- private axiosInstance: AxiosInstance | null = null;
23
-
24
- /**
25
- * Get axios instance with authentication
26
- */
27
- protected getAxiosInstance(): AxiosInstance {
28
- if (!this.axiosInstance) {
29
- this.axiosInstance = axios.create({
30
- baseURL: this.apiBaseUrl,
31
- timeout: 30000,
32
- headers: {
33
- 'Accept': 'application/json',
34
- 'Content-Type': 'application/json'
35
- }
36
- });
37
-
38
- // Add request interceptor for auth
39
- this.axiosInstance.interceptors.request.use(
40
- (config) => {
41
- const token = this.getAccessToken();
42
- if (token) {
43
- config.headers.Authorization = `Bearer ${token}`;
44
- }
45
- return config;
46
- },
47
- (error) => {
48
- return Promise.reject(error);
49
- }
50
- );
51
-
52
- // Add response interceptor for rate limiting
53
- this.axiosInstance.interceptors.response.use(
54
- (response) => {
55
- // Log rate limit headers if present
56
- const headers = response.headers;
57
- const rateLimitInfo = this.parseRateLimitHeaders(headers);
58
- if (rateLimitInfo) {
59
- LogStatus(`Buffer API - Remaining requests: ${rateLimitInfo.remaining}/${rateLimitInfo.limit}, Reset: ${rateLimitInfo.reset}`);
60
- }
61
- return response;
62
- },
63
- async (error: AxiosError) => {
64
- if (error.response?.status === 429) {
65
- // Rate limit hit
66
- const retryAfter = error.response.headers['retry-after'];
67
- await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : 60);
68
- // Retry the request
69
- return this.axiosInstance!.request(error.config!);
70
- }
71
- return Promise.reject(error);
72
- }
73
- );
74
- }
75
- return this.axiosInstance;
87
+ try {
88
+ const response = await axios.post(`${this.apiBaseUrl}/oauth2/token`, {
89
+ client_id: this.getCustomAttribute(1), // Store Buffer client ID in CustomAttribute1
90
+ client_secret: this.getCustomAttribute(2), // Store Buffer client secret in CustomAttribute2
91
+ refresh_token: refreshToken,
92
+ grant_type: 'refresh_token',
93
+ });
94
+
95
+ const { access_token, expires_in } = response.data;
96
+
97
+ await this.updateStoredTokens(
98
+ access_token,
99
+ refreshToken, // Buffer doesn't rotate refresh tokens
100
+ expires_in
101
+ );
102
+
103
+ LogStatus('Buffer access token refreshed successfully');
104
+ } catch (error) {
105
+ LogError('Failed to refresh Buffer access token:', error);
106
+ throw new Error('Failed to refresh Buffer access token');
76
107
  }
77
-
78
- /**
79
- * Refresh access token using refresh token
80
- */
81
- protected async refreshAccessToken(): Promise<void> {
82
- const refreshToken = this.getRefreshToken();
83
- if (!refreshToken) {
84
- throw new Error('No refresh token available for Buffer');
85
- }
86
-
87
- try {
88
- const response = await axios.post(`${this.apiBaseUrl}/oauth2/token`, {
89
- client_id: this.getCustomAttribute(1), // Store Buffer client ID in CustomAttribute1
90
- client_secret: this.getCustomAttribute(2), // Store Buffer client secret in CustomAttribute2
91
- refresh_token: refreshToken,
92
- grant_type: 'refresh_token'
93
- });
94
-
95
- const { access_token, expires_in } = response.data;
96
-
97
- await this.updateStoredTokens(
98
- access_token,
99
- refreshToken, // Buffer doesn't rotate refresh tokens
100
- expires_in
101
- );
102
-
103
- LogStatus('Buffer access token refreshed successfully');
104
- } catch (error) {
105
- LogError('Failed to refresh Buffer access token:', error);
106
- throw new Error('Failed to refresh Buffer access token');
107
- }
108
+ }
109
+
110
+ /**
111
+ * Get Buffer profiles for the authenticated user
112
+ */
113
+ protected async getProfiles(): Promise<any[]> {
114
+ try {
115
+ const response = await this.getAxiosInstance().get('/profiles.json');
116
+ return response.data || [];
117
+ } catch (error) {
118
+ LogError('Failed to get Buffer profiles:', error);
119
+ throw error;
108
120
  }
109
-
110
- /**
111
- * Get Buffer profiles for the authenticated user
112
- */
113
- protected async getProfiles(): Promise<any[]> {
114
- try {
115
- const response = await this.getAxiosInstance().get('/profiles.json');
116
- return response.data || [];
117
- } catch (error) {
118
- LogError('Failed to get Buffer profiles:', error);
119
- throw error;
120
- }
121
+ }
122
+
123
+ /**
124
+ * Upload media to Buffer
125
+ */
126
+ protected async uploadSingleMedia(file: MediaFile): Promise<string> {
127
+ try {
128
+ const formData = new FormData();
129
+
130
+ // Convert base64 to buffer if needed
131
+ const buffer = typeof file.data === 'string' ? Buffer.from(file.data, 'base64') : file.data;
132
+
133
+ formData.append('media', buffer, {
134
+ filename: file.filename,
135
+ contentType: file.mimeType,
136
+ });
137
+
138
+ const response = await this.getAxiosInstance().post('/updates/media/upload.json', formData, {
139
+ headers: {
140
+ ...formData.getHeaders(),
141
+ },
142
+ });
143
+
144
+ if (response.data && response.data.media && response.data.media[0]) {
145
+ return response.data.media[0].picture; // URL of uploaded media
146
+ }
147
+
148
+ throw new Error('Failed to upload media to Buffer');
149
+ } catch (error) {
150
+ LogError('Buffer media upload failed:', error);
151
+ throw error;
121
152
  }
122
-
123
- /**
124
- * Upload media to Buffer
125
- */
126
- protected async uploadSingleMedia(file: MediaFile): Promise<string> {
127
- try {
128
- const formData = new FormData();
129
-
130
- // Convert base64 to buffer if needed
131
- const buffer = typeof file.data === 'string'
132
- ? Buffer.from(file.data, 'base64')
133
- : file.data;
134
-
135
- formData.append('media', buffer, {
136
- filename: file.filename,
137
- contentType: file.mimeType
138
- });
139
-
140
- const response = await this.getAxiosInstance().post('/updates/media/upload.json', formData, {
141
- headers: {
142
- ...formData.getHeaders()
143
- }
144
- });
145
-
146
- if (response.data && response.data.media && response.data.media[0]) {
147
- return response.data.media[0].picture; // URL of uploaded media
148
- }
149
-
150
- throw new Error('Failed to upload media to Buffer');
151
- } catch (error) {
152
- LogError('Buffer media upload failed:', error);
153
- throw error;
154
- }
153
+ }
154
+
155
+ /**
156
+ * Create a Buffer update (post)
157
+ */
158
+ protected async createUpdate(
159
+ profileIds: string[],
160
+ text: string,
161
+ media?: { link?: string; description?: string; picture?: string }[],
162
+ scheduledAt?: Date,
163
+ options?: {
164
+ shorten?: boolean;
165
+ now?: boolean;
166
+ top?: boolean;
167
+ attachment?: boolean;
155
168
  }
156
-
157
- /**
158
- * Create a Buffer update (post)
159
- */
160
- protected async createUpdate(
161
- profileIds: string[],
162
- text: string,
163
- media?: { link?: string; description?: string; picture?: string }[],
164
- scheduledAt?: Date,
165
- options?: {
166
- shorten?: boolean;
167
- now?: boolean;
168
- top?: boolean;
169
- attachment?: boolean;
170
- }
171
- ): Promise<any> {
172
- const data: any = {
173
- profile_ids: profileIds,
174
- text: text,
175
- shorten: options?.shorten !== false // Default to true
176
- };
177
-
178
- if (media && media.length > 0) {
179
- data.media = media;
180
- }
181
-
182
- if (scheduledAt) {
183
- data.scheduled_at = Math.floor(scheduledAt.getTime() / 1000); // Unix timestamp
184
- } else if (options?.now) {
185
- data.now = true;
186
- }
187
-
188
- if (options?.top) {
189
- data.top = true;
190
- }
191
-
192
- if (options?.attachment !== undefined) {
193
- data.attachment = options.attachment;
194
- }
195
-
196
- try {
197
- const response = await this.getAxiosInstance().post('/updates/create.json', data);
198
- return response.data;
199
- } catch (error) {
200
- LogError('Failed to create Buffer update:', error);
201
- throw error;
202
- }
169
+ ): Promise<any> {
170
+ const data: any = {
171
+ profile_ids: profileIds,
172
+ text: text,
173
+ shorten: options?.shorten !== false, // Default to true
174
+ };
175
+
176
+ if (media && media.length > 0) {
177
+ data.media = media;
203
178
  }
204
179
 
205
- /**
206
- * Get updates (posts) from Buffer
207
- */
208
- protected async getUpdates(
209
- profileId: string,
210
- status: 'pending' | 'sent',
211
- options?: {
212
- page?: number;
213
- count?: number;
214
- since?: Date;
215
- utc?: boolean;
216
- }
217
- ): Promise<any> {
218
- const params: any = {
219
- page: options?.page || 1,
220
- count: options?.count || 10,
221
- utc: options?.utc !== false // Default to true
222
- };
223
-
224
- if (options?.since) {
225
- params.since = Math.floor(options.since.getTime() / 1000);
226
- }
227
-
228
- try {
229
- const response = await this.getAxiosInstance().get(
230
- `/profiles/${profileId}/updates/${status}.json`,
231
- { params }
232
- );
233
- return response.data;
234
- } catch (error) {
235
- LogError(`Failed to get ${status} updates from Buffer:`, error);
236
- throw error;
237
- }
180
+ if (scheduledAt) {
181
+ data.scheduled_at = Math.floor(scheduledAt.getTime() / 1000); // Unix timestamp
182
+ } else if (options?.now) {
183
+ data.now = true;
238
184
  }
239
185
 
240
- /**
241
- * Delete a Buffer update
242
- */
243
- protected async deleteUpdate(updateId: string): Promise<boolean> {
244
- try {
245
- const response = await this.getAxiosInstance().post(`/updates/${updateId}/destroy.json`);
246
- return response.data.success === true;
247
- } catch (error) {
248
- LogError('Failed to delete Buffer update:', error);
249
- throw error;
250
- }
186
+ if (options?.top) {
187
+ data.top = true;
251
188
  }
252
189
 
253
- /**
254
- * Reorder updates in the queue
255
- */
256
- protected async reorderUpdates(profileId: string, updateIds: string[], offset?: number): Promise<any> {
257
- const data: any = {
258
- order: updateIds
259
- };
260
-
261
- if (offset !== undefined) {
262
- data.offset = offset;
263
- }
190
+ if (options?.attachment !== undefined) {
191
+ data.attachment = options.attachment;
192
+ }
264
193
 
265
- try {
266
- const response = await this.getAxiosInstance().post(
267
- `/profiles/${profileId}/updates/reorder.json`,
268
- data
269
- );
270
- return response.data;
271
- } catch (error) {
272
- LogError('Failed to reorder Buffer updates:', error);
273
- throw error;
274
- }
194
+ try {
195
+ const response = await this.getAxiosInstance().post('/updates/create.json', data);
196
+ return response.data;
197
+ } catch (error) {
198
+ LogError('Failed to create Buffer update:', error);
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Get updates (posts) from Buffer
205
+ */
206
+ protected async getUpdates(
207
+ profileId: string,
208
+ status: 'pending' | 'sent',
209
+ options?: {
210
+ page?: number;
211
+ count?: number;
212
+ since?: Date;
213
+ utc?: boolean;
214
+ }
215
+ ): Promise<any> {
216
+ const params: any = {
217
+ page: options?.page || 1,
218
+ count: options?.count || 10,
219
+ utc: options?.utc !== false, // Default to true
220
+ };
221
+
222
+ if (options?.since) {
223
+ params.since = Math.floor(options.since.getTime() / 1000);
275
224
  }
276
225
 
277
- /**
278
- * Get analytics for sent posts
279
- */
280
- protected async getAnalytics(updateId: string): Promise<any> {
281
- try {
282
- const response = await this.getAxiosInstance().get(`/updates/${updateId}/interactions.json`);
283
- return response.data;
284
- } catch (error) {
285
- LogError('Failed to get Buffer analytics:', error);
286
- throw error;
287
- }
226
+ try {
227
+ const response = await this.getAxiosInstance().get(`/profiles/${profileId}/updates/${status}.json`, { params });
228
+ return response.data;
229
+ } catch (error) {
230
+ LogError(`Failed to get ${status} updates from Buffer:`, error);
231
+ throw error;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Delete a Buffer update
237
+ */
238
+ protected async deleteUpdate(updateId: string): Promise<boolean> {
239
+ try {
240
+ const response = await this.getAxiosInstance().post(`/updates/${updateId}/destroy.json`);
241
+ return response.data.success === true;
242
+ } catch (error) {
243
+ LogError('Failed to delete Buffer update:', error);
244
+ throw error;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Reorder updates in the queue
250
+ */
251
+ protected async reorderUpdates(profileId: string, updateIds: string[], offset?: number): Promise<any> {
252
+ const data: any = {
253
+ order: updateIds,
254
+ };
255
+
256
+ if (offset !== undefined) {
257
+ data.offset = offset;
288
258
  }
289
259
 
290
- /**
291
- * Search posts implementation for Buffer
292
- * Buffer doesn't have a native search API, so we'll fetch posts and filter client-side
293
- */
294
- protected async searchPosts(params: {
295
- query?: string;
296
- hashtags?: string[];
297
- startDate?: Date;
298
- endDate?: Date;
299
- limit?: number;
300
- offset?: number;
301
- profileIds?: string[];
302
- }): Promise<SocialPost[]> {
303
- const posts: SocialPost[] = [];
304
- const profileIds = params.profileIds || [];
305
-
306
- // If no profile IDs provided, get all profiles
307
- if (profileIds.length === 0) {
308
- const profiles = await this.getProfiles();
309
- profileIds.push(...profiles.map(p => p.id));
310
- }
260
+ try {
261
+ const response = await this.getAxiosInstance().post(`/profiles/${profileId}/updates/reorder.json`, data);
262
+ return response.data;
263
+ } catch (error) {
264
+ LogError('Failed to reorder Buffer updates:', error);
265
+ throw error;
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Get analytics for sent posts
271
+ */
272
+ protected async getAnalytics(updateId: string): Promise<any> {
273
+ try {
274
+ const response = await this.getAxiosInstance().get(`/updates/${updateId}/interactions.json`);
275
+ return response.data;
276
+ } catch (error) {
277
+ LogError('Failed to get Buffer analytics:', error);
278
+ throw error;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Search posts implementation for Buffer
284
+ * Buffer doesn't have a native search API, so we'll fetch posts and filter client-side
285
+ */
286
+ protected async searchPosts(params: {
287
+ query?: string;
288
+ hashtags?: string[];
289
+ startDate?: Date;
290
+ endDate?: Date;
291
+ limit?: number;
292
+ offset?: number;
293
+ profileIds?: string[];
294
+ }): Promise<SocialPost[]> {
295
+ const posts: SocialPost[] = [];
296
+ const profileIds = params.profileIds || [];
297
+
298
+ // If no profile IDs provided, get all profiles
299
+ if (profileIds.length === 0) {
300
+ const profiles = await this.getProfiles();
301
+ profileIds.push(...profiles.map((p) => p.id));
302
+ }
311
303
 
312
- // Fetch sent posts from each profile
313
- for (const profileId of profileIds) {
314
- try {
315
- let page = 1;
316
- let hasMore = true;
317
-
318
- while (hasMore && posts.length < (params.limit || 100)) {
319
- const result = await this.getUpdates(profileId, 'sent', {
320
- page: page,
321
- count: 100,
322
- since: params.startDate
323
- });
324
-
325
- if (result.updates && result.updates.length > 0) {
326
- for (const update of result.updates) {
327
- const post = this.normalizePost(update);
328
-
329
- // Apply filters
330
- if (this.matchesSearchCriteria(post, params)) {
331
- posts.push(post);
332
-
333
- if (posts.length >= (params.limit || 100)) {
334
- hasMore = false;
335
- break;
336
- }
337
- }
338
- }
339
-
340
- page++;
341
- hasMore = result.updates.length === 100;
342
- } else {
343
- hasMore = false;
344
- }
304
+ // Fetch sent posts from each profile
305
+ for (const profileId of profileIds) {
306
+ try {
307
+ let page = 1;
308
+ let hasMore = true;
309
+
310
+ while (hasMore && posts.length < (params.limit || 100)) {
311
+ const result = await this.getUpdates(profileId, 'sent', {
312
+ page: page,
313
+ count: 100,
314
+ since: params.startDate,
315
+ });
316
+
317
+ if (result.updates && result.updates.length > 0) {
318
+ for (const update of result.updates) {
319
+ const post = this.normalizePost(update);
320
+
321
+ // Apply filters
322
+ if (this.matchesSearchCriteria(post, params)) {
323
+ posts.push(post);
324
+
325
+ if (posts.length >= (params.limit || 100)) {
326
+ hasMore = false;
327
+ break;
345
328
  }
346
- } catch (error) {
347
- LogError(`Failed to search posts for profile ${profileId}:`, error);
329
+ }
348
330
  }
349
- }
350
-
351
- // Sort by published date descending
352
- posts.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
353
331
 
354
- // Apply offset if specified
355
- if (params.offset) {
356
- return posts.slice(params.offset, params.offset + (params.limit || 100));
332
+ page++;
333
+ hasMore = result.updates.length === 100;
334
+ } else {
335
+ hasMore = false;
336
+ }
357
337
  }
358
-
359
- return posts.slice(0, params.limit || 100);
338
+ } catch (error) {
339
+ LogError(`Failed to search posts for profile ${profileId}:`, error);
340
+ }
360
341
  }
361
342
 
362
- /**
363
- * Check if a post matches search criteria
364
- */
365
- private matchesSearchCriteria(post: SocialPost, params: any): boolean {
366
- // Check date range
367
- if (params.endDate && post.publishedAt > params.endDate) {
368
- return false;
369
- }
343
+ // Sort by published date descending
344
+ posts.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
370
345
 
371
- // Check query text
372
- if (params.query) {
373
- const query = params.query.toLowerCase();
374
- const content = post.content.toLowerCase();
375
- if (!content.includes(query)) {
376
- return false;
377
- }
378
- }
346
+ // Apply offset if specified
347
+ if (params.offset) {
348
+ return posts.slice(params.offset, params.offset + (params.limit || 100));
349
+ }
379
350
 
380
- // Check hashtags
381
- if (params.hashtags && params.hashtags.length > 0) {
382
- const postHashtags = this.extractHashtags(post.content);
383
- const hasMatchingHashtag = params.hashtags.some(tag =>
384
- postHashtags.includes(tag.toLowerCase().replace('#', ''))
385
- );
386
- if (!hasMatchingHashtag) {
387
- return false;
388
- }
389
- }
351
+ return posts.slice(0, params.limit || 100);
352
+ }
390
353
 
391
- return true;
354
+ /**
355
+ * Check if a post matches search criteria
356
+ */
357
+ private matchesSearchCriteria(post: SocialPost, params: any): boolean {
358
+ // Check date range
359
+ if (params.endDate && post.publishedAt > params.endDate) {
360
+ return false;
392
361
  }
393
362
 
394
- /**
395
- * Extract hashtags from post content
396
- */
397
- protected extractHashtags(content: string): string[] {
398
- const regex = /#(\w+)/g;
399
- const hashtags: string[] = [];
400
- let match;
401
-
402
- while ((match = regex.exec(content)) !== null) {
403
- hashtags.push(match[1].toLowerCase());
404
- }
405
-
406
- return hashtags;
363
+ // Check query text
364
+ if (params.query) {
365
+ const query = params.query.toLowerCase();
366
+ const content = post.content.toLowerCase();
367
+ if (!content.includes(query)) {
368
+ return false;
369
+ }
407
370
  }
408
371
 
409
- /**
410
- * Normalize Buffer post to common format
411
- */
412
- protected normalizePost(bufferPost: any): SocialPost {
413
- const media: string[] = [];
414
-
415
- if (bufferPost.media) {
416
- if (bufferPost.media.picture) {
417
- media.push(bufferPost.media.picture);
418
- }
419
- if (bufferPost.media.link) {
420
- media.push(bufferPost.media.link);
421
- }
422
- }
423
-
424
- return {
425
- id: bufferPost.id,
426
- platform: 'Buffer',
427
- profileId: bufferPost.profile_id,
428
- content: bufferPost.text || '',
429
- mediaUrls: media,
430
- publishedAt: new Date(bufferPost.sent_at * 1000), // Convert Unix timestamp
431
- scheduledFor: bufferPost.due_at ? new Date(bufferPost.due_at * 1000) : undefined,
432
- analytics: bufferPost.statistics ? this.normalizeAnalytics(bufferPost.statistics) : undefined,
433
- platformSpecificData: {
434
- profileService: bufferPost.profile_service,
435
- status: bufferPost.status,
436
- userId: bufferPost.user_id,
437
- viaName: bufferPost.via,
438
- sourceUrl: bufferPost.source_url,
439
- day: bufferPost.day,
440
- dueTime: bufferPost.due_time,
441
- mediaDescription: bufferPost.media?.description,
442
- mediaTitle: bufferPost.media?.title,
443
- mediaLink: bufferPost.media?.link
444
- }
445
- };
372
+ // Check hashtags
373
+ if (params.hashtags && params.hashtags.length > 0) {
374
+ const postHashtags = this.extractHashtags(post.content);
375
+ const hasMatchingHashtag = params.hashtags.some((tag) => postHashtags.includes(tag.toLowerCase().replace('#', '')));
376
+ if (!hasMatchingHashtag) {
377
+ return false;
378
+ }
446
379
  }
447
380
 
448
- /**
449
- * Normalize Buffer analytics to common format
450
- */
451
- protected normalizeAnalytics(bufferStats: any): SocialAnalytics {
452
- return {
453
- impressions: bufferStats.reach || 0,
454
- engagements: (bufferStats.clicks || 0) + (bufferStats.favorites || 0) +
455
- (bufferStats.mentions || 0) + (bufferStats.retweets || 0) +
456
- (bufferStats.shares || 0) + (bufferStats.comments || 0),
457
- clicks: bufferStats.clicks || 0,
458
- shares: bufferStats.shares || bufferStats.retweets || 0,
459
- comments: bufferStats.comments || bufferStats.mentions || 0,
460
- likes: bufferStats.favorites || bufferStats.likes || 0,
461
- reach: bufferStats.reach || 0,
462
- saves: undefined,
463
- videoViews: undefined,
464
- platformMetrics: bufferStats
465
- };
381
+ return true;
382
+ }
383
+
384
+ /**
385
+ * Extract hashtags from post content
386
+ */
387
+ protected extractHashtags(content: string): string[] {
388
+ const regex = /#(\w+)/g;
389
+ const hashtags: string[] = [];
390
+ let match;
391
+
392
+ while ((match = regex.exec(content)) !== null) {
393
+ hashtags.push(match[1].toLowerCase());
466
394
  }
467
395
 
468
- /**
469
- * Map Buffer error to our error codes
470
- */
471
- protected mapBufferError(error: any): string {
472
- if (error.response) {
473
- const status = error.response.status;
474
- const data = error.response.data;
475
-
476
- if (status === 401) {
477
- return 'INVALID_TOKEN';
478
- } else if (status === 429) {
479
- return 'RATE_LIMIT';
480
- } else if (status === 403) {
481
- return 'INSUFFICIENT_PERMISSIONS';
482
- } else if (status === 404) {
483
- return 'POST_NOT_FOUND';
484
- } else if (data?.error?.includes('media')) {
485
- return 'INVALID_MEDIA';
486
- }
487
- }
396
+ return hashtags;
397
+ }
398
+
399
+ /**
400
+ * Normalize Buffer post to common format
401
+ */
402
+ protected normalizePost(bufferPost: any): SocialPost {
403
+ const media: string[] = [];
404
+
405
+ if (bufferPost.media) {
406
+ if (bufferPost.media.picture) {
407
+ media.push(bufferPost.media.picture);
408
+ }
409
+ if (bufferPost.media.link) {
410
+ media.push(bufferPost.media.link);
411
+ }
412
+ }
488
413
 
489
- return 'PLATFORM_ERROR';
414
+ return {
415
+ id: bufferPost.id,
416
+ platform: 'Buffer',
417
+ profileId: bufferPost.profile_id,
418
+ content: bufferPost.text || '',
419
+ mediaUrls: media,
420
+ publishedAt: new Date(bufferPost.sent_at * 1000), // Convert Unix timestamp
421
+ scheduledFor: bufferPost.due_at ? new Date(bufferPost.due_at * 1000) : undefined,
422
+ analytics: bufferPost.statistics ? this.normalizeAnalytics(bufferPost.statistics) : undefined,
423
+ platformSpecificData: {
424
+ profileService: bufferPost.profile_service,
425
+ status: bufferPost.status,
426
+ userId: bufferPost.user_id,
427
+ viaName: bufferPost.via,
428
+ sourceUrl: bufferPost.source_url,
429
+ day: bufferPost.day,
430
+ dueTime: bufferPost.due_time,
431
+ mediaDescription: bufferPost.media?.description,
432
+ mediaTitle: bufferPost.media?.title,
433
+ mediaLink: bufferPost.media?.link,
434
+ },
435
+ };
436
+ }
437
+
438
+ /**
439
+ * Normalize Buffer analytics to common format
440
+ */
441
+ protected normalizeAnalytics(bufferStats: any): SocialAnalytics {
442
+ return {
443
+ impressions: bufferStats.reach || 0,
444
+ engagements:
445
+ (bufferStats.clicks || 0) +
446
+ (bufferStats.favorites || 0) +
447
+ (bufferStats.mentions || 0) +
448
+ (bufferStats.retweets || 0) +
449
+ (bufferStats.shares || 0) +
450
+ (bufferStats.comments || 0),
451
+ clicks: bufferStats.clicks || 0,
452
+ shares: bufferStats.shares || bufferStats.retweets || 0,
453
+ comments: bufferStats.comments || bufferStats.mentions || 0,
454
+ likes: bufferStats.favorites || bufferStats.likes || 0,
455
+ reach: bufferStats.reach || 0,
456
+ saves: undefined,
457
+ videoViews: undefined,
458
+ platformMetrics: bufferStats,
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Map Buffer error to our error codes
464
+ */
465
+ protected mapBufferError(error: any): string {
466
+ if (error.response) {
467
+ const status = error.response.status;
468
+ const data = error.response.data;
469
+
470
+ if (status === 401) {
471
+ return 'INVALID_TOKEN';
472
+ } else if (status === 429) {
473
+ return 'RATE_LIMIT';
474
+ } else if (status === 403) {
475
+ return 'INSUFFICIENT_PERMISSIONS';
476
+ } else if (status === 404) {
477
+ return 'POST_NOT_FOUND';
478
+ } else if (data?.error?.includes('media')) {
479
+ return 'INVALID_MEDIA';
480
+ }
490
481
  }
491
- }
482
+
483
+ return 'PLATFORM_ERROR';
484
+ }
485
+ }