@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
@@ -2,7 +2,7 @@ import { RegisterClass } from '@memberjunction/global';
2
2
  import { BaseSocialMediaAction, MediaFile, SocialPost, SearchParams } from '../../base/base-social.action';
3
3
  import axios, { AxiosInstance, AxiosError } from 'axios';
4
4
  import { ActionParam } from '@memberjunction/actions-base';
5
- import { LogStatus, LogError } from '@memberjunction/core';
5
+ import { LogStatus, LogError } from '@memberjunction/global';
6
6
  import { BaseAction } from '@memberjunction/actions';
7
7
 
8
8
  /**
@@ -11,337 +11,331 @@ import { BaseAction } from '@memberjunction/actions';
11
11
  */
12
12
  @RegisterClass(BaseAction, 'HootSuiteBaseAction')
13
13
  export abstract class HootSuiteBaseAction extends BaseSocialMediaAction {
14
- protected get platformName(): string {
15
- return 'HootSuite';
14
+ protected get platformName(): string {
15
+ return 'HootSuite';
16
+ }
17
+
18
+ protected get apiBaseUrl(): string {
19
+ return 'https://platform.hootsuite.com/v1';
20
+ }
21
+
22
+ /**
23
+ * Axios instance for making HTTP requests
24
+ */
25
+ private _axiosInstance: AxiosInstance | null = null;
26
+
27
+ /**
28
+ * Get or create axios instance with interceptors
29
+ */
30
+ protected get axiosInstance(): AxiosInstance {
31
+ if (!this._axiosInstance) {
32
+ this._axiosInstance = axios.create({
33
+ baseURL: this.apiBaseUrl,
34
+ timeout: 30000,
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ Accept: 'application/json',
38
+ },
39
+ });
40
+
41
+ // Add request interceptor for auth
42
+ this._axiosInstance.interceptors.request.use(
43
+ (config) => {
44
+ const token = this.getAccessToken();
45
+ if (token) {
46
+ config.headers.Authorization = `Bearer ${token}`;
47
+ }
48
+ return config;
49
+ },
50
+ (error) => Promise.reject(error)
51
+ );
52
+
53
+ // Add response interceptor for rate limit handling
54
+ this._axiosInstance.interceptors.response.use(
55
+ (response) => {
56
+ // Log rate limit info
57
+ const rateLimitInfo = this.parseRateLimitHeaders(response.headers);
58
+ if (rateLimitInfo) {
59
+ LogStatus(`HootSuite Rate Limit - Remaining: ${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 exceeded
66
+ const retryAfter = error.response.headers['retry-after'];
67
+ const waitTime = retryAfter ? parseInt(retryAfter) : 60;
68
+ await this.handleRateLimit(waitTime);
69
+
70
+ // Retry the request
71
+ return this._axiosInstance!.request(error.config!);
72
+ }
73
+ return Promise.reject(error);
74
+ }
75
+ );
16
76
  }
17
-
18
- protected get apiBaseUrl(): string {
19
- return 'https://platform.hootsuite.com/v1';
77
+ return this._axiosInstance;
78
+ }
79
+
80
+ /**
81
+ * Refresh the access token using the refresh token
82
+ */
83
+ protected async refreshAccessToken(): Promise<void> {
84
+ const refreshToken = this.getRefreshToken();
85
+ if (!refreshToken) {
86
+ throw new Error('No refresh token available for HootSuite');
20
87
  }
21
88
 
22
- /**
23
- * Axios instance for making HTTP requests
24
- */
25
- private _axiosInstance: AxiosInstance | null = null;
26
-
27
- /**
28
- * Get or create axios instance with interceptors
29
- */
30
- protected get axiosInstance(): AxiosInstance {
31
- if (!this._axiosInstance) {
32
- this._axiosInstance = axios.create({
33
- baseURL: this.apiBaseUrl,
34
- timeout: 30000,
35
- headers: {
36
- 'Content-Type': 'application/json',
37
- 'Accept': 'application/json'
38
- }
39
- });
40
-
41
- // Add request interceptor for auth
42
- this._axiosInstance.interceptors.request.use(
43
- (config) => {
44
- const token = this.getAccessToken();
45
- if (token) {
46
- config.headers.Authorization = `Bearer ${token}`;
47
- }
48
- return config;
49
- },
50
- (error) => Promise.reject(error)
51
- );
52
-
53
- // Add response interceptor for rate limit handling
54
- this._axiosInstance.interceptors.response.use(
55
- (response) => {
56
- // Log rate limit info
57
- const rateLimitInfo = this.parseRateLimitHeaders(response.headers);
58
- if (rateLimitInfo) {
59
- LogStatus(`HootSuite Rate Limit - Remaining: ${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 exceeded
66
- const retryAfter = error.response.headers['retry-after'];
67
- const waitTime = retryAfter ? parseInt(retryAfter) : 60;
68
- await this.handleRateLimit(waitTime);
69
-
70
- // Retry the request
71
- return this._axiosInstance!.request(error.config!);
72
- }
73
- return Promise.reject(error);
74
- }
75
- );
89
+ try {
90
+ const response = await axios.post(
91
+ 'https://platform.hootsuite.com/oauth2/token',
92
+ {
93
+ grant_type: 'refresh_token',
94
+ refresh_token: refreshToken,
95
+ client_id: this.getCustomAttribute(2), // Client ID stored in CustomAttribute2
96
+ client_secret: this.getCustomAttribute(3), // Client Secret stored in CustomAttribute3
97
+ },
98
+ {
99
+ headers: {
100
+ 'Content-Type': 'application/x-www-form-urlencoded',
101
+ },
76
102
  }
77
- return this._axiosInstance;
78
- }
103
+ );
79
104
 
80
- /**
81
- * Refresh the access token using the refresh token
82
- */
83
- protected async refreshAccessToken(): Promise<void> {
84
- const refreshToken = this.getRefreshToken();
85
- if (!refreshToken) {
86
- throw new Error('No refresh token available for HootSuite');
87
- }
105
+ const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
88
106
 
89
- try {
90
- const response = await axios.post('https://platform.hootsuite.com/oauth2/token', {
91
- grant_type: 'refresh_token',
92
- refresh_token: refreshToken,
93
- client_id: this.getCustomAttribute(2), // Client ID stored in CustomAttribute2
94
- client_secret: this.getCustomAttribute(3) // Client Secret stored in CustomAttribute3
95
- }, {
96
- headers: {
97
- 'Content-Type': 'application/x-www-form-urlencoded'
98
- }
99
- });
100
-
101
- const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
102
-
103
- // Update stored tokens
104
- await this.updateStoredTokens(
105
- access_token,
106
- newRefreshToken || refreshToken,
107
- expires_in
108
- );
109
-
110
- LogStatus('HootSuite access token refreshed successfully');
111
- } catch (error) {
112
- LogError(`Failed to refresh HootSuite access token: ${error instanceof Error ? error.message : 'Unknown error'}`);
113
- throw error;
114
- }
115
- }
107
+ // Update stored tokens
108
+ await this.updateStoredTokens(access_token, newRefreshToken || refreshToken, expires_in);
116
109
 
117
- /**
118
- * Upload media to HootSuite
119
- */
120
- protected async uploadSingleMedia(file: MediaFile): Promise<string> {
121
- try {
122
- // First, request an upload URL
123
- const uploadRequest = await this.axiosInstance.post('/media', {
124
- mimeType: file.mimeType,
125
- sizeBytes: file.size
126
- });
127
-
128
- const { uploadUrl, mediaId } = uploadRequest.data;
129
-
130
- // Upload the file to the provided URL
131
- const fileData = typeof file.data === 'string'
132
- ? Buffer.from(file.data, 'base64')
133
- : file.data;
134
-
135
- await axios.put(uploadUrl, fileData, {
136
- headers: {
137
- 'Content-Type': file.mimeType,
138
- 'Content-Length': file.size.toString()
139
- }
140
- });
141
-
142
- // Wait for processing
143
- await this.waitForMediaProcessing(mediaId);
144
-
145
- return mediaId;
146
- } catch (error) {
147
- LogError(`Failed to upload media to HootSuite: ${error instanceof Error ? error.message : 'Unknown error'}`);
148
- throw error;
149
- }
110
+ LogStatus('HootSuite access token refreshed successfully');
111
+ } catch (error) {
112
+ LogError(`Failed to refresh HootSuite access token: ${error instanceof Error ? error.message : 'Unknown error'}`);
113
+ throw error;
150
114
  }
151
-
152
- /**
153
- * Wait for media to finish processing
154
- */
155
- private async waitForMediaProcessing(mediaId: string, maxAttempts: number = 30): Promise<void> {
156
- for (let i = 0; i < maxAttempts; i++) {
157
- try {
158
- const response = await this.axiosInstance.get(`/media/${mediaId}`);
159
- const { state } = response.data;
160
-
161
- if (state === 'READY') {
162
- return;
163
- } else if (state === 'FAILED') {
164
- throw new Error('Media processing failed');
165
- }
166
-
167
- // Wait 2 seconds before next check
168
- await new Promise(resolve => setTimeout(resolve, 2000));
169
- } catch (error) {
170
- if (i === maxAttempts - 1) {
171
- throw new Error(`Media processing timeout for ${mediaId}`);
172
- }
173
- }
174
- }
115
+ }
116
+
117
+ /**
118
+ * Upload media to HootSuite
119
+ */
120
+ protected async uploadSingleMedia(file: MediaFile): Promise<string> {
121
+ try {
122
+ // First, request an upload URL
123
+ const uploadRequest = await this.axiosInstance.post('/media', {
124
+ mimeType: file.mimeType,
125
+ sizeBytes: file.size,
126
+ });
127
+
128
+ const { uploadUrl, mediaId } = uploadRequest.data;
129
+
130
+ // Upload the file to the provided URL
131
+ const fileData = typeof file.data === 'string' ? Buffer.from(file.data, 'base64') : file.data;
132
+
133
+ await axios.put(uploadUrl, fileData, {
134
+ headers: {
135
+ 'Content-Type': file.mimeType,
136
+ 'Content-Length': file.size.toString(),
137
+ },
138
+ });
139
+
140
+ // Wait for processing
141
+ await this.waitForMediaProcessing(mediaId);
142
+
143
+ return mediaId;
144
+ } catch (error) {
145
+ LogError(`Failed to upload media to HootSuite: ${error instanceof Error ? error.message : 'Unknown error'}`);
146
+ throw error;
175
147
  }
176
-
177
- /**
178
- * Get social profiles for the authenticated user
179
- */
180
- protected async getSocialProfiles(): Promise<HootSuiteProfile[]> {
181
- try {
182
- const response = await this.axiosInstance.get('/socialProfiles');
183
- return response.data.data || [];
184
- } catch (error) {
185
- LogError(`Failed to get social profiles: ${error instanceof Error ? error.message : 'Unknown error'}`);
186
- throw error;
148
+ }
149
+
150
+ /**
151
+ * Wait for media to finish processing
152
+ */
153
+ private async waitForMediaProcessing(mediaId: string, maxAttempts: number = 30): Promise<void> {
154
+ for (let i = 0; i < maxAttempts; i++) {
155
+ try {
156
+ const response = await this.axiosInstance.get(`/media/${mediaId}`);
157
+ const { state } = response.data;
158
+
159
+ if (state === 'READY') {
160
+ return;
161
+ } else if (state === 'FAILED') {
162
+ throw new Error('Media processing failed');
187
163
  }
188
- }
189
-
190
- /**
191
- * Make a paginated request to HootSuite API
192
- */
193
- protected async makePaginatedRequest<T>(
194
- endpoint: string,
195
- params: Record<string, any> = {}
196
- ): Promise<T[]> {
197
- const results: T[] = [];
198
- let cursor: string | null = null;
199
- const limit = params.limit || 50;
200
-
201
- do {
202
- const queryParams: any = { ...params, limit };
203
- if (cursor) {
204
- queryParams.cursor = cursor;
205
- }
206
-
207
- const response = await this.axiosInstance.get(endpoint, { params: queryParams });
208
- const data = response.data;
209
-
210
- if (data.data && Array.isArray(data.data)) {
211
- results.push(...data.data);
212
- }
213
-
214
- cursor = data.cursor || null;
215
164
 
216
- // Check if we've reached the desired number of results
217
- if (params.maxResults && results.length >= params.maxResults) {
218
- return results.slice(0, params.maxResults);
219
- }
220
-
221
- } while (cursor);
222
-
223
- return results;
224
- }
225
-
226
- /**
227
- * Format date for HootSuite API (ISO 8601)
228
- */
229
- protected formatHootSuiteDate(date: Date | string): string {
230
- if (typeof date === 'string') {
231
- date = new Date(date);
165
+ // Wait 2 seconds before next check
166
+ await new Promise((resolve) => setTimeout(resolve, 2000));
167
+ } catch (error) {
168
+ if (i === maxAttempts - 1) {
169
+ throw new Error(`Media processing timeout for ${mediaId}`);
232
170
  }
233
- return date.toISOString();
171
+ }
234
172
  }
235
-
236
- /**
237
- * Parse HootSuite date string
238
- */
239
- protected parseHootSuiteDate(dateString: string): Date {
240
- return new Date(dateString);
241
- }
242
-
243
- /**
244
- * Convert HootSuite post to common format
245
- */
246
- protected normalizePost(hootsuitePost: HootSuitePost): SocialPost {
247
- return {
248
- id: hootsuitePost.id,
249
- platform: 'HootSuite',
250
- profileId: hootsuitePost.socialProfileIds.join(','), // Multiple profiles possible
251
- content: hootsuitePost.text,
252
- mediaUrls: hootsuitePost.mediaIds || [],
253
- publishedAt: this.parseHootSuiteDate(hootsuitePost.createdTime),
254
- scheduledFor: hootsuitePost.scheduledTime ? this.parseHootSuiteDate(hootsuitePost.scheduledTime) : undefined,
255
- platformSpecificData: {
256
- state: hootsuitePost.state,
257
- tags: hootsuitePost.tags,
258
- location: hootsuitePost.location,
259
- socialProfileIds: hootsuitePost.socialProfileIds
260
- }
261
- };
173
+ }
174
+
175
+ /**
176
+ * Get social profiles for the authenticated user
177
+ */
178
+ protected async getSocialProfiles(): Promise<HootSuiteProfile[]> {
179
+ try {
180
+ const response = await this.axiosInstance.get('/socialProfiles');
181
+ return response.data.data || [];
182
+ } catch (error) {
183
+ LogError(`Failed to get social profiles: ${error instanceof Error ? error.message : 'Unknown error'}`);
184
+ throw error;
262
185
  }
263
-
264
- /**
265
- * Search for posts - implemented in search action
266
- */
267
- protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
268
- // This is implemented in the search-posts.action.ts
269
- throw new Error('Search posts is implemented in HootSuiteSearchPostsAction');
186
+ }
187
+
188
+ /**
189
+ * Make a paginated request to HootSuite API
190
+ */
191
+ protected async makePaginatedRequest<T>(endpoint: string, params: Record<string, any> = {}): Promise<T[]> {
192
+ const results: T[] = [];
193
+ let cursor: string | null = null;
194
+ const limit = params.limit || 50;
195
+
196
+ do {
197
+ const queryParams: any = { ...params, limit };
198
+ if (cursor) {
199
+ queryParams.cursor = cursor;
200
+ }
201
+
202
+ const response = await this.axiosInstance.get(endpoint, { params: queryParams });
203
+ const data = response.data;
204
+
205
+ if (data.data && Array.isArray(data.data)) {
206
+ results.push(...data.data);
207
+ }
208
+
209
+ cursor = data.cursor || null;
210
+
211
+ // Check if we've reached the desired number of results
212
+ if (params.maxResults && results.length >= params.maxResults) {
213
+ return results.slice(0, params.maxResults);
214
+ }
215
+ } while (cursor);
216
+
217
+ return results;
218
+ }
219
+
220
+ /**
221
+ * Format date for HootSuite API (ISO 8601)
222
+ */
223
+ protected formatHootSuiteDate(date: Date | string): string {
224
+ if (typeof date === 'string') {
225
+ date = new Date(date);
270
226
  }
271
-
272
- /**
273
- * Handle HootSuite-specific errors
274
- */
275
- protected handleHootSuiteError(error: AxiosError): never {
276
- if (error.response) {
277
- const { status, data } = error.response;
278
- const errorData = data as any;
279
-
280
- switch (status) {
281
- case 400:
282
- throw new Error(`Bad Request: ${errorData.message || 'Invalid request parameters'}`);
283
- case 401:
284
- throw new Error('Unauthorized: Invalid or expired access token');
285
- case 403:
286
- throw new Error('Forbidden: Insufficient permissions');
287
- case 404:
288
- throw new Error('Not Found: Resource does not exist');
289
- case 429:
290
- throw new Error('Rate Limit Exceeded: Too many requests');
291
- case 500:
292
- throw new Error('Internal Server Error: HootSuite service error');
293
- default:
294
- throw new Error(`HootSuite API Error (${status}): ${errorData.message || 'Unknown error'}`);
295
- }
296
- } else if (error.request) {
297
- throw new Error('Network Error: No response from HootSuite');
298
- } else {
299
- throw new Error(`Request Error: ${error.message}`);
300
- }
227
+ return date.toISOString();
228
+ }
229
+
230
+ /**
231
+ * Parse HootSuite date string
232
+ */
233
+ protected parseHootSuiteDate(dateString: string): Date {
234
+ return new Date(dateString);
235
+ }
236
+
237
+ /**
238
+ * Convert HootSuite post to common format
239
+ */
240
+ protected normalizePost(hootsuitePost: HootSuitePost): SocialPost {
241
+ return {
242
+ id: hootsuitePost.id,
243
+ platform: 'HootSuite',
244
+ profileId: hootsuitePost.socialProfileIds.join(','), // Multiple profiles possible
245
+ content: hootsuitePost.text,
246
+ mediaUrls: hootsuitePost.mediaIds || [],
247
+ publishedAt: this.parseHootSuiteDate(hootsuitePost.createdTime),
248
+ scheduledFor: hootsuitePost.scheduledTime ? this.parseHootSuiteDate(hootsuitePost.scheduledTime) : undefined,
249
+ platformSpecificData: {
250
+ state: hootsuitePost.state,
251
+ tags: hootsuitePost.tags,
252
+ location: hootsuitePost.location,
253
+ socialProfileIds: hootsuitePost.socialProfileIds,
254
+ },
255
+ };
256
+ }
257
+
258
+ /**
259
+ * Search for posts - implemented in search action
260
+ */
261
+ protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
262
+ // This is implemented in the search-posts.action.ts
263
+ throw new Error('Search posts is implemented in HootSuiteSearchPostsAction');
264
+ }
265
+
266
+ /**
267
+ * Handle HootSuite-specific errors
268
+ */
269
+ protected handleHootSuiteError(error: AxiosError): never {
270
+ if (error.response) {
271
+ const { status, data } = error.response;
272
+ const errorData = data as any;
273
+
274
+ switch (status) {
275
+ case 400:
276
+ throw new Error(`Bad Request: ${errorData.message || 'Invalid request parameters'}`);
277
+ case 401:
278
+ throw new Error('Unauthorized: Invalid or expired access token');
279
+ case 403:
280
+ throw new Error('Forbidden: Insufficient permissions');
281
+ case 404:
282
+ throw new Error('Not Found: Resource does not exist');
283
+ case 429:
284
+ throw new Error('Rate Limit Exceeded: Too many requests');
285
+ case 500:
286
+ throw new Error('Internal Server Error: HootSuite service error');
287
+ default:
288
+ throw new Error(`HootSuite API Error (${status}): ${errorData.message || 'Unknown error'}`);
289
+ }
290
+ } else if (error.request) {
291
+ throw new Error('Network Error: No response from HootSuite');
292
+ } else {
293
+ throw new Error(`Request Error: ${error.message}`);
301
294
  }
295
+ }
302
296
  }
303
297
 
304
298
  /**
305
299
  * HootSuite-specific interfaces
306
300
  */
307
301
  export interface HootSuiteProfile {
308
- id: string;
309
- socialNetworkId: string;
310
- socialNetworkUserId: string;
311
- avatarUrl: string;
312
- displayName: string;
313
- type: string;
314
- ownerId: string;
302
+ id: string;
303
+ socialNetworkId: string;
304
+ socialNetworkUserId: string;
305
+ avatarUrl: string;
306
+ displayName: string;
307
+ type: string;
308
+ ownerId: string;
315
309
  }
316
310
 
317
311
  export interface HootSuitePost {
318
- id: string;
319
- socialProfileIds: string[];
320
- text: string;
321
- scheduledTime?: string;
322
- createdTime: string;
323
- state: 'SCHEDULED' | 'PUBLISHED' | 'FAILED' | 'DRAFT';
324
- mediaIds?: string[];
325
- tags?: string[];
326
- location?: {
327
- latitude: number;
328
- longitude: number;
329
- };
312
+ id: string;
313
+ socialProfileIds: string[];
314
+ text: string;
315
+ scheduledTime?: string;
316
+ createdTime: string;
317
+ state: 'SCHEDULED' | 'PUBLISHED' | 'FAILED' | 'DRAFT';
318
+ mediaIds?: string[];
319
+ tags?: string[];
320
+ location?: {
321
+ latitude: number;
322
+ longitude: number;
323
+ };
330
324
  }
331
325
 
332
326
  export interface HootSuiteAnalytics {
333
- postId: string;
334
- metrics: {
335
- likes: number;
336
- comments: number;
337
- shares: number;
338
- clicks: number;
339
- impressions: number;
340
- engagements: number;
341
- reach: number;
342
- };
343
- period: {
344
- start: string;
345
- end: string;
346
- };
347
- }
327
+ postId: string;
328
+ metrics: {
329
+ likes: number;
330
+ comments: number;
331
+ shares: number;
332
+ clicks: number;
333
+ impressions: number;
334
+ engagements: number;
335
+ reach: number;
336
+ };
337
+ period: {
338
+ start: string;
339
+ end: string;
340
+ };
341
+ }