@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, SocialAnalytics } 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
  /**
@@ -12,471 +12,457 @@ import { BaseAction } from '@memberjunction/actions';
12
12
  */
13
13
  @RegisterClass(BaseAction, 'LinkedInBaseAction')
14
14
  export abstract class LinkedInBaseAction extends BaseSocialMediaAction {
15
- protected get platformName(): string {
16
- return 'LinkedIn';
15
+ protected get platformName(): string {
16
+ return 'LinkedIn';
17
+ }
18
+
19
+ protected get apiBaseUrl(): string {
20
+ return 'https://api.linkedin.com/v2';
21
+ }
22
+
23
+ /**
24
+ * Axios instance for making HTTP requests
25
+ */
26
+ private _axiosInstance: AxiosInstance | null = null;
27
+
28
+ /**
29
+ * Get or create axios instance with interceptors
30
+ */
31
+ protected get axiosInstance(): AxiosInstance {
32
+ if (!this._axiosInstance) {
33
+ this._axiosInstance = axios.create({
34
+ baseURL: this.apiBaseUrl,
35
+ timeout: 30000,
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ Accept: 'application/json',
39
+ 'X-Restli-Protocol-Version': '2.0.0', // LinkedIn specific header
40
+ },
41
+ });
42
+
43
+ // Add request interceptor for auth
44
+ this._axiosInstance.interceptors.request.use(
45
+ (config) => {
46
+ const token = this.getAccessToken();
47
+ if (token) {
48
+ config.headers.Authorization = `Bearer ${token}`;
49
+ }
50
+ return config;
51
+ },
52
+ (error) => Promise.reject(error)
53
+ );
54
+
55
+ // Add response interceptor for rate limit handling
56
+ this._axiosInstance.interceptors.response.use(
57
+ (response) => {
58
+ // Log rate limit info
59
+ const rateLimitInfo = this.parseRateLimitHeaders(response.headers);
60
+ if (rateLimitInfo) {
61
+ LogStatus(`LinkedIn Rate Limit - Remaining: ${rateLimitInfo.remaining}/${rateLimitInfo.limit}, Reset: ${rateLimitInfo.reset}`);
62
+ }
63
+ return response;
64
+ },
65
+ async (error: AxiosError) => {
66
+ if (error.response?.status === 429) {
67
+ // Rate limit exceeded
68
+ const retryAfter = error.response.headers['retry-after'];
69
+ const waitTime = retryAfter ? parseInt(retryAfter) : 60;
70
+ await this.handleRateLimit(waitTime);
71
+
72
+ // Retry the request
73
+ return this._axiosInstance!.request(error.config!);
74
+ }
75
+ return Promise.reject(error);
76
+ }
77
+ );
17
78
  }
18
-
19
- protected get apiBaseUrl(): string {
20
- return 'https://api.linkedin.com/v2';
79
+ return this._axiosInstance;
80
+ }
81
+
82
+ /**
83
+ * Refresh the access token using the refresh token
84
+ */
85
+ protected async refreshAccessToken(): Promise<void> {
86
+ const refreshToken = this.getRefreshToken();
87
+ if (!refreshToken) {
88
+ throw new Error('No refresh token available for LinkedIn');
21
89
  }
22
90
 
23
- /**
24
- * Axios instance for making HTTP requests
25
- */
26
- private _axiosInstance: AxiosInstance | null = null;
27
-
28
- /**
29
- * Get or create axios instance with interceptors
30
- */
31
- protected get axiosInstance(): AxiosInstance {
32
- if (!this._axiosInstance) {
33
- this._axiosInstance = axios.create({
34
- baseURL: this.apiBaseUrl,
35
- timeout: 30000,
36
- headers: {
37
- 'Content-Type': 'application/json',
38
- 'Accept': 'application/json',
39
- 'X-Restli-Protocol-Version': '2.0.0' // LinkedIn specific header
40
- }
41
- });
42
-
43
- // Add request interceptor for auth
44
- this._axiosInstance.interceptors.request.use(
45
- (config) => {
46
- const token = this.getAccessToken();
47
- if (token) {
48
- config.headers.Authorization = `Bearer ${token}`;
49
- }
50
- return config;
51
- },
52
- (error) => Promise.reject(error)
53
- );
54
-
55
- // Add response interceptor for rate limit handling
56
- this._axiosInstance.interceptors.response.use(
57
- (response) => {
58
- // Log rate limit info
59
- const rateLimitInfo = this.parseRateLimitHeaders(response.headers);
60
- if (rateLimitInfo) {
61
- LogStatus(`LinkedIn Rate Limit - Remaining: ${rateLimitInfo.remaining}/${rateLimitInfo.limit}, Reset: ${rateLimitInfo.reset}`);
62
- }
63
- return response;
64
- },
65
- async (error: AxiosError) => {
66
- if (error.response?.status === 429) {
67
- // Rate limit exceeded
68
- const retryAfter = error.response.headers['retry-after'];
69
- const waitTime = retryAfter ? parseInt(retryAfter) : 60;
70
- await this.handleRateLimit(waitTime);
71
-
72
- // Retry the request
73
- return this._axiosInstance!.request(error.config!);
74
- }
75
- return Promise.reject(error);
76
- }
77
- );
91
+ try {
92
+ const response = await axios.post(
93
+ 'https://www.linkedin.com/oauth/v2/accessToken',
94
+ new URLSearchParams({
95
+ grant_type: 'refresh_token',
96
+ refresh_token: refreshToken,
97
+ client_id: this.getCustomAttribute(2) || '', // Client ID stored in CustomAttribute2
98
+ client_secret: this.getCustomAttribute(3) || '', // Client Secret stored in CustomAttribute3
99
+ }).toString(),
100
+ {
101
+ headers: {
102
+ 'Content-Type': 'application/x-www-form-urlencoded',
103
+ },
78
104
  }
79
- return this._axiosInstance;
80
- }
105
+ );
81
106
 
82
- /**
83
- * Refresh the access token using the refresh token
84
- */
85
- protected async refreshAccessToken(): Promise<void> {
86
- const refreshToken = this.getRefreshToken();
87
- if (!refreshToken) {
88
- throw new Error('No refresh token available for LinkedIn');
89
- }
107
+ const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
90
108
 
91
- try {
92
- const response = await axios.post('https://www.linkedin.com/oauth/v2/accessToken',
93
- new URLSearchParams({
94
- grant_type: 'refresh_token',
95
- refresh_token: refreshToken,
96
- client_id: this.getCustomAttribute(2) || '', // Client ID stored in CustomAttribute2
97
- client_secret: this.getCustomAttribute(3) || '' // Client Secret stored in CustomAttribute3
98
- }).toString(),
99
- {
100
- headers: {
101
- 'Content-Type': 'application/x-www-form-urlencoded'
102
- }
103
- }
104
- );
105
-
106
- const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
107
-
108
- // Update stored tokens
109
- await this.updateStoredTokens(
110
- access_token,
111
- newRefreshToken || refreshToken,
112
- expires_in
113
- );
114
-
115
- LogStatus('LinkedIn access token refreshed successfully');
116
- } catch (error) {
117
- LogError(`Failed to refresh LinkedIn access token: ${error instanceof Error ? error.message : 'Unknown error'}`);
118
- throw error;
119
- }
120
- }
109
+ // Update stored tokens
110
+ await this.updateStoredTokens(access_token, newRefreshToken || refreshToken, expires_in);
121
111
 
122
- /**
123
- * Get the authenticated user's profile URN
124
- */
125
- protected async getCurrentUserUrn(): Promise<string> {
126
- try {
127
- const response = await this.axiosInstance.get('/me');
128
- return `urn:li:person:${response.data.id}`;
129
- } catch (error) {
130
- LogError(`Failed to get current user URN: ${error instanceof Error ? error.message : 'Unknown error'}`);
131
- throw error;
132
- }
112
+ LogStatus('LinkedIn access token refreshed successfully');
113
+ } catch (error) {
114
+ LogError(`Failed to refresh LinkedIn access token: ${error instanceof Error ? error.message : 'Unknown error'}`);
115
+ throw error;
133
116
  }
134
-
135
- /**
136
- * Get organizations the user has admin access to
137
- */
138
- protected async getAdminOrganizations(): Promise<LinkedInOrganization[]> {
139
- try {
140
- const response = await this.axiosInstance.get('/organizationalEntityAcls', {
141
- params: {
142
- q: 'roleAssignee',
143
- role: 'ADMINISTRATOR',
144
- projection: '(elements*(*,organizationalTarget~(localizedName)))'
145
- }
146
- });
147
-
148
- const organizations: LinkedInOrganization[] = [];
149
- if (response.data.elements) {
150
- for (const element of response.data.elements) {
151
- if (element.organizationalTarget) {
152
- organizations.push({
153
- urn: element.organizationalTarget,
154
- name: element['organizationalTarget~']?.localizedName || 'Unknown',
155
- id: element.organizationalTarget.split(':').pop() || ''
156
- });
157
- }
158
- }
159
- }
160
-
161
- return organizations;
162
- } catch (error) {
163
- LogError(`Failed to get admin organizations: ${error instanceof Error ? error.message : 'Unknown error'}`);
164
- throw error;
165
- }
117
+ }
118
+
119
+ /**
120
+ * Get the authenticated user's profile URN
121
+ */
122
+ protected async getCurrentUserUrn(): Promise<string> {
123
+ try {
124
+ const response = await this.axiosInstance.get('/me');
125
+ return `urn:li:person:${response.data.id}`;
126
+ } catch (error) {
127
+ LogError(`Failed to get current user URN: ${error instanceof Error ? error.message : 'Unknown error'}`);
128
+ throw error;
166
129
  }
167
-
168
- /**
169
- * Upload media to LinkedIn
170
- */
171
- protected async uploadSingleMedia(file: MediaFile): Promise<string> {
172
- try {
173
- // Step 1: Register upload
174
- const registerResponse = await this.axiosInstance.post('/assets?action=registerUpload', {
175
- registerUploadRequest: {
176
- recipes: ['urn:li:digitalmediaRecipe:feedshare-image'],
177
- owner: await this.getCurrentUserUrn(),
178
- serviceRelationships: [{
179
- relationshipType: 'OWNER',
180
- identifier: 'urn:li:userGeneratedContent'
181
- }]
182
- }
183
- });
184
-
185
- const uploadUrl = registerResponse.data.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl;
186
- const asset = registerResponse.data.value.asset;
187
-
188
- // Step 2: Upload the file
189
- const fileData = typeof file.data === 'string'
190
- ? Buffer.from(file.data, 'base64')
191
- : file.data;
192
-
193
- await axios.put(uploadUrl, fileData, {
194
- headers: {
195
- 'Authorization': `Bearer ${this.getAccessToken()}`,
196
- 'Content-Type': file.mimeType
197
- }
130
+ }
131
+
132
+ /**
133
+ * Get organizations the user has admin access to
134
+ */
135
+ protected async getAdminOrganizations(): Promise<LinkedInOrganization[]> {
136
+ try {
137
+ const response = await this.axiosInstance.get('/organizationalEntityAcls', {
138
+ params: {
139
+ q: 'roleAssignee',
140
+ role: 'ADMINISTRATOR',
141
+ projection: '(elements*(*,organizationalTarget~(localizedName)))',
142
+ },
143
+ });
144
+
145
+ const organizations: LinkedInOrganization[] = [];
146
+ if (response.data.elements) {
147
+ for (const element of response.data.elements) {
148
+ if (element.organizationalTarget) {
149
+ organizations.push({
150
+ urn: element.organizationalTarget,
151
+ name: element['organizationalTarget~']?.localizedName || 'Unknown',
152
+ id: element.organizationalTarget.split(':').pop() || '',
198
153
  });
199
-
200
- // Return the asset URN
201
- return asset;
202
- } catch (error) {
203
- LogError(`Failed to upload media to LinkedIn: ${error instanceof Error ? error.message : 'Unknown error'}`);
204
- throw error;
205
- }
206
- }
207
-
208
- /**
209
- * Validate media file meets LinkedIn requirements
210
- */
211
- protected validateMediaFile(file: MediaFile): void {
212
- const supportedTypes = [
213
- 'image/jpeg',
214
- 'image/png',
215
- 'image/gif',
216
- 'image/webp'
217
- ];
218
-
219
- if (!supportedTypes.includes(file.mimeType)) {
220
- throw new Error(`Unsupported media type: ${file.mimeType}. Supported types: ${supportedTypes.join(', ')}`);
154
+ }
221
155
  }
156
+ }
222
157
 
223
- // LinkedIn image size limits
224
- const maxSize = 10 * 1024 * 1024; // 10MB
225
- if (file.size > maxSize) {
226
- throw new Error(`File size exceeds limit. Max: ${maxSize / 1024 / 1024}MB, Got: ${file.size / 1024 / 1024}MB`);
227
- }
158
+ return organizations;
159
+ } catch (error) {
160
+ LogError(`Failed to get admin organizations: ${error instanceof Error ? error.message : 'Unknown error'}`);
161
+ throw error;
228
162
  }
229
-
230
- /**
231
- * Create a share (post) on LinkedIn
232
- */
233
- protected async createShare(shareData: LinkedInShareData): Promise<string> {
234
- try {
235
- const response = await this.axiosInstance.post('/ugcPosts', shareData);
236
- return response.data.id;
237
- } catch (error) {
238
- this.handleLinkedInError(error as AxiosError);
239
- }
163
+ }
164
+
165
+ /**
166
+ * Upload media to LinkedIn
167
+ */
168
+ protected async uploadSingleMedia(file: MediaFile): Promise<string> {
169
+ try {
170
+ // Step 1: Register upload
171
+ const registerResponse = await this.axiosInstance.post('/assets?action=registerUpload', {
172
+ registerUploadRequest: {
173
+ recipes: ['urn:li:digitalmediaRecipe:feedshare-image'],
174
+ owner: await this.getCurrentUserUrn(),
175
+ serviceRelationships: [
176
+ {
177
+ relationshipType: 'OWNER',
178
+ identifier: 'urn:li:userGeneratedContent',
179
+ },
180
+ ],
181
+ },
182
+ });
183
+
184
+ const uploadUrl = registerResponse.data.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl;
185
+ const asset = registerResponse.data.value.asset;
186
+
187
+ // Step 2: Upload the file
188
+ const fileData = typeof file.data === 'string' ? Buffer.from(file.data, 'base64') : file.data;
189
+
190
+ await axios.put(uploadUrl, fileData, {
191
+ headers: {
192
+ Authorization: `Bearer ${this.getAccessToken()}`,
193
+ 'Content-Type': file.mimeType,
194
+ },
195
+ });
196
+
197
+ // Return the asset URN
198
+ return asset;
199
+ } catch (error) {
200
+ LogError(`Failed to upload media to LinkedIn: ${error instanceof Error ? error.message : 'Unknown error'}`);
201
+ throw error;
240
202
  }
203
+ }
241
204
 
242
- /**
243
- * Get shares for a specific author (person or organization)
244
- */
245
- protected async getShares(authorUrn: string, count: number = 50, start: number = 0): Promise<LinkedInShare[]> {
246
- try {
247
- const response = await this.axiosInstance.get('/ugcPosts', {
248
- params: {
249
- q: 'authors',
250
- authors: `List(${authorUrn})`,
251
- count: count,
252
- start: start
253
- }
254
- });
205
+ /**
206
+ * Validate media file meets LinkedIn requirements
207
+ */
208
+ protected validateMediaFile(file: MediaFile): void {
209
+ const supportedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
255
210
 
256
- return response.data.elements || [];
257
- } catch (error) {
258
- LogError(`Failed to get shares: ${error instanceof Error ? error.message : 'Unknown error'}`);
259
- throw error;
260
- }
211
+ if (!supportedTypes.includes(file.mimeType)) {
212
+ throw new Error(`Unsupported media type: ${file.mimeType}. Supported types: ${supportedTypes.join(', ')}`);
261
213
  }
262
214
 
263
- /**
264
- * Convert LinkedIn share to common format
265
- */
266
- protected normalizePost(linkedInShare: LinkedInShare): SocialPost {
267
- const publishedAt = new Date(linkedInShare.firstPublishedAt || linkedInShare.created.time);
268
-
269
- // Extract media URLs
270
- const mediaUrls: string[] = [];
271
- if (linkedInShare.specificContent?.['com.linkedin.ugc.ShareContent']?.media) {
272
- for (const media of linkedInShare.specificContent['com.linkedin.ugc.ShareContent'].media) {
273
- if (media.media) {
274
- mediaUrls.push(media.media);
275
- }
276
- }
277
- }
278
-
279
- return {
280
- id: linkedInShare.id,
281
- platform: 'LinkedIn',
282
- profileId: linkedInShare.author,
283
- content: linkedInShare.specificContent?.['com.linkedin.ugc.ShareContent']?.shareCommentary?.text || '',
284
- mediaUrls: mediaUrls,
285
- publishedAt: publishedAt,
286
- platformSpecificData: {
287
- lifecycleState: linkedInShare.lifecycleState,
288
- visibility: linkedInShare.visibility,
289
- distribution: linkedInShare.distribution
290
- }
291
- };
215
+ // LinkedIn image size limits
216
+ const maxSize = 10 * 1024 * 1024; // 10MB
217
+ if (file.size > maxSize) {
218
+ throw new Error(`File size exceeds limit. Max: ${maxSize / 1024 / 1024}MB, Got: ${file.size / 1024 / 1024}MB`);
292
219
  }
293
-
294
- /**
295
- * Normalize LinkedIn analytics to common format
296
- */
297
- protected normalizeAnalytics(linkedInAnalytics: LinkedInAnalytics): SocialAnalytics {
298
- return {
299
- impressions: linkedInAnalytics.totalShareStatistics?.impressionCount || 0,
300
- engagements: linkedInAnalytics.totalShareStatistics?.engagement || 0,
301
- clicks: linkedInAnalytics.totalShareStatistics?.clickCount || 0,
302
- shares: linkedInAnalytics.totalShareStatistics?.shareCount || 0,
303
- comments: linkedInAnalytics.totalShareStatistics?.commentCount || 0,
304
- likes: linkedInAnalytics.totalShareStatistics?.likeCount || 0,
305
- reach: linkedInAnalytics.totalShareStatistics?.uniqueImpressionsCount || 0,
306
- platformMetrics: linkedInAnalytics
307
- };
220
+ }
221
+
222
+ /**
223
+ * Create a share (post) on LinkedIn
224
+ */
225
+ protected async createShare(shareData: LinkedInShareData): Promise<string> {
226
+ try {
227
+ const response = await this.axiosInstance.post('/ugcPosts', shareData);
228
+ return response.data.id;
229
+ } catch (error) {
230
+ this.handleLinkedInError(error as AxiosError);
308
231
  }
309
-
310
- /**
311
- * Search for posts - implemented in search action
312
- */
313
- protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
314
- // This is implemented in the search-posts.action.ts
315
- throw new Error('Search posts is implemented in LinkedInSearchPostsAction');
232
+ }
233
+
234
+ /**
235
+ * Get shares for a specific author (person or organization)
236
+ */
237
+ protected async getShares(authorUrn: string, count: number = 50, start: number = 0): Promise<LinkedInShare[]> {
238
+ try {
239
+ const response = await this.axiosInstance.get('/ugcPosts', {
240
+ params: {
241
+ q: 'authors',
242
+ authors: `List(${authorUrn})`,
243
+ count: count,
244
+ start: start,
245
+ },
246
+ });
247
+
248
+ return response.data.elements || [];
249
+ } catch (error) {
250
+ LogError(`Failed to get shares: ${error instanceof Error ? error.message : 'Unknown error'}`);
251
+ throw error;
316
252
  }
317
-
318
- /**
319
- * Handle LinkedIn-specific errors
320
- */
321
- protected handleLinkedInError(error: AxiosError): never {
322
- if (error.response) {
323
- const { status, data } = error.response;
324
- const errorData = data as any;
325
-
326
- switch (status) {
327
- case 400:
328
- throw new Error(`Bad Request: ${errorData.message || 'Invalid request parameters'}`);
329
- case 401:
330
- throw new Error('Unauthorized: Invalid or expired access token');
331
- case 403:
332
- throw new Error('Forbidden: Insufficient permissions. Ensure the app has required LinkedIn scopes.');
333
- case 404:
334
- throw new Error('Not Found: Resource does not exist');
335
- case 422:
336
- throw new Error(`Unprocessable Entity: ${errorData.message || 'Invalid data provided'}`);
337
- case 429:
338
- throw new Error('Rate Limit Exceeded: Too many requests');
339
- case 500:
340
- throw new Error('Internal Server Error: LinkedIn service error');
341
- default:
342
- throw new Error(`LinkedIn API Error (${status}): ${errorData.message || 'Unknown error'}`);
343
- }
344
- } else if (error.request) {
345
- throw new Error('Network Error: No response from LinkedIn');
346
- } else {
347
- throw new Error(`Request Error: ${error.message}`);
253
+ }
254
+
255
+ /**
256
+ * Convert LinkedIn share to common format
257
+ */
258
+ protected normalizePost(linkedInShare: LinkedInShare): SocialPost {
259
+ const publishedAt = new Date(linkedInShare.firstPublishedAt || linkedInShare.created.time);
260
+
261
+ // Extract media URLs
262
+ const mediaUrls: string[] = [];
263
+ if (linkedInShare.specificContent?.['com.linkedin.ugc.ShareContent']?.media) {
264
+ for (const media of linkedInShare.specificContent['com.linkedin.ugc.ShareContent'].media) {
265
+ if (media.media) {
266
+ mediaUrls.push(media.media);
348
267
  }
268
+ }
349
269
  }
350
270
 
351
- /**
352
- * Parse LinkedIn-specific rate limit headers
353
- */
354
- protected parseRateLimitHeaders(headers: any): { remaining: number; reset: Date; limit: number; } | null {
355
- // LinkedIn uses different header names
356
- const appRemaining = headers['x-app-rate-limit-remaining'];
357
- const appLimit = headers['x-app-rate-limit-limit'];
358
- const memberRemaining = headers['x-member-rate-limit-remaining'];
359
- const memberLimit = headers['x-member-rate-limit-limit'];
360
-
361
- // Use the more restrictive limit
362
- const remaining = Math.min(
363
- appRemaining ? parseInt(appRemaining) : Infinity,
364
- memberRemaining ? parseInt(memberRemaining) : Infinity
365
- );
366
-
367
- const limit = Math.min(
368
- appLimit ? parseInt(appLimit) : Infinity,
369
- memberLimit ? parseInt(memberLimit) : Infinity
370
- );
371
-
372
- if (remaining !== Infinity && limit !== Infinity) {
373
- // LinkedIn resets rate limits at the top of each hour
374
- const now = new Date();
375
- const reset = new Date(now);
376
- reset.setHours(reset.getHours() + 1, 0, 0, 0);
377
-
378
- return { remaining, reset, limit };
379
- }
380
-
381
- return null;
271
+ return {
272
+ id: linkedInShare.id,
273
+ platform: 'LinkedIn',
274
+ profileId: linkedInShare.author,
275
+ content: linkedInShare.specificContent?.['com.linkedin.ugc.ShareContent']?.shareCommentary?.text || '',
276
+ mediaUrls: mediaUrls,
277
+ publishedAt: publishedAt,
278
+ platformSpecificData: {
279
+ lifecycleState: linkedInShare.lifecycleState,
280
+ visibility: linkedInShare.visibility,
281
+ distribution: linkedInShare.distribution,
282
+ },
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Normalize LinkedIn analytics to common format
288
+ */
289
+ protected normalizeAnalytics(linkedInAnalytics: LinkedInAnalytics): SocialAnalytics {
290
+ return {
291
+ impressions: linkedInAnalytics.totalShareStatistics?.impressionCount || 0,
292
+ engagements: linkedInAnalytics.totalShareStatistics?.engagement || 0,
293
+ clicks: linkedInAnalytics.totalShareStatistics?.clickCount || 0,
294
+ shares: linkedInAnalytics.totalShareStatistics?.shareCount || 0,
295
+ comments: linkedInAnalytics.totalShareStatistics?.commentCount || 0,
296
+ likes: linkedInAnalytics.totalShareStatistics?.likeCount || 0,
297
+ reach: linkedInAnalytics.totalShareStatistics?.uniqueImpressionsCount || 0,
298
+ platformMetrics: linkedInAnalytics,
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Search for posts - implemented in search action
304
+ */
305
+ protected async searchPosts(params: SearchParams): Promise<SocialPost[]> {
306
+ // This is implemented in the search-posts.action.ts
307
+ throw new Error('Search posts is implemented in LinkedInSearchPostsAction');
308
+ }
309
+
310
+ /**
311
+ * Handle LinkedIn-specific errors
312
+ */
313
+ protected handleLinkedInError(error: AxiosError): never {
314
+ if (error.response) {
315
+ const { status, data } = error.response;
316
+ const errorData = data as any;
317
+
318
+ switch (status) {
319
+ case 400:
320
+ throw new Error(`Bad Request: ${errorData.message || 'Invalid request parameters'}`);
321
+ case 401:
322
+ throw new Error('Unauthorized: Invalid or expired access token');
323
+ case 403:
324
+ throw new Error('Forbidden: Insufficient permissions. Ensure the app has required LinkedIn scopes.');
325
+ case 404:
326
+ throw new Error('Not Found: Resource does not exist');
327
+ case 422:
328
+ throw new Error(`Unprocessable Entity: ${errorData.message || 'Invalid data provided'}`);
329
+ case 429:
330
+ throw new Error('Rate Limit Exceeded: Too many requests');
331
+ case 500:
332
+ throw new Error('Internal Server Error: LinkedIn service error');
333
+ default:
334
+ throw new Error(`LinkedIn API Error (${status}): ${errorData.message || 'Unknown error'}`);
335
+ }
336
+ } else if (error.request) {
337
+ throw new Error('Network Error: No response from LinkedIn');
338
+ } else {
339
+ throw new Error(`Request Error: ${error.message}`);
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Parse LinkedIn-specific rate limit headers
345
+ */
346
+ protected parseRateLimitHeaders(headers: any): { remaining: number; reset: Date; limit: number } | null {
347
+ // LinkedIn uses different header names
348
+ const appRemaining = headers['x-app-rate-limit-remaining'];
349
+ const appLimit = headers['x-app-rate-limit-limit'];
350
+ const memberRemaining = headers['x-member-rate-limit-remaining'];
351
+ const memberLimit = headers['x-member-rate-limit-limit'];
352
+
353
+ // Use the more restrictive limit
354
+ const remaining = Math.min(appRemaining ? parseInt(appRemaining) : Infinity, memberRemaining ? parseInt(memberRemaining) : Infinity);
355
+
356
+ const limit = Math.min(appLimit ? parseInt(appLimit) : Infinity, memberLimit ? parseInt(memberLimit) : Infinity);
357
+
358
+ if (remaining !== Infinity && limit !== Infinity) {
359
+ // LinkedIn resets rate limits at the top of each hour
360
+ const now = new Date();
361
+ const reset = new Date(now);
362
+ reset.setHours(reset.getHours() + 1, 0, 0, 0);
363
+
364
+ return { remaining, reset, limit };
382
365
  }
366
+
367
+ return null;
368
+ }
383
369
  }
384
370
 
385
371
  /**
386
372
  * LinkedIn-specific interfaces
387
373
  */
388
374
  export interface LinkedInOrganization {
389
- urn: string;
390
- name: string;
391
- id: string;
375
+ urn: string;
376
+ name: string;
377
+ id: string;
392
378
  }
393
379
 
394
380
  export interface LinkedInShareData {
395
- author: string; // URN of the author (person or organization)
396
- lifecycleState: 'PUBLISHED' | 'DRAFT';
397
- specificContent: {
398
- 'com.linkedin.ugc.ShareContent': {
399
- shareCommentary: {
400
- text: string;
401
- };
402
- shareMediaCategory: 'NONE' | 'ARTICLE' | 'IMAGE' | 'VIDEO' | 'RICH';
403
- media?: Array<{
404
- status: 'READY';
405
- media: string; // Asset URN
406
- title?: {
407
- text: string;
408
- };
409
- description?: {
410
- text: string;
411
- };
412
- }>;
381
+ author: string; // URN of the author (person or organization)
382
+ lifecycleState: 'PUBLISHED' | 'DRAFT';
383
+ specificContent: {
384
+ 'com.linkedin.ugc.ShareContent': {
385
+ shareCommentary: {
386
+ text: string;
387
+ };
388
+ shareMediaCategory: 'NONE' | 'ARTICLE' | 'IMAGE' | 'VIDEO' | 'RICH';
389
+ media?: Array<{
390
+ status: 'READY';
391
+ media: string; // Asset URN
392
+ title?: {
393
+ text: string;
413
394
  };
414
- };
415
- visibility: {
416
- 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN' | 'CONTAINER';
417
- };
418
- distribution?: {
419
- linkedInDistributionTarget?: {
420
- visibleToGuest?: boolean;
395
+ description?: {
396
+ text: string;
421
397
  };
398
+ }>;
399
+ };
400
+ };
401
+ visibility: {
402
+ 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN' | 'CONTAINER';
403
+ };
404
+ distribution?: {
405
+ linkedInDistributionTarget?: {
406
+ visibleToGuest?: boolean;
422
407
  };
408
+ };
423
409
  }
424
410
 
425
411
  export interface LinkedInShare {
426
- id: string;
427
- author: string;
428
- created: {
429
- actor: string;
430
- time: number;
431
- };
432
- firstPublishedAt?: number;
433
- lastModified?: {
434
- actor: string;
435
- time: number;
436
- };
437
- lifecycleState: string;
438
- specificContent: {
439
- 'com.linkedin.ugc.ShareContent': {
440
- shareCommentary: {
441
- text: string;
442
- };
443
- shareMediaCategory: string;
444
- media?: Array<{
445
- media: string;
446
- title?: {
447
- text: string;
448
- };
449
- }>;
412
+ id: string;
413
+ author: string;
414
+ created: {
415
+ actor: string;
416
+ time: number;
417
+ };
418
+ firstPublishedAt?: number;
419
+ lastModified?: {
420
+ actor: string;
421
+ time: number;
422
+ };
423
+ lifecycleState: string;
424
+ specificContent: {
425
+ 'com.linkedin.ugc.ShareContent': {
426
+ shareCommentary: {
427
+ text: string;
428
+ };
429
+ shareMediaCategory: string;
430
+ media?: Array<{
431
+ media: string;
432
+ title?: {
433
+ text: string;
450
434
  };
435
+ }>;
451
436
  };
452
- visibility: {
453
- 'com.linkedin.ugc.MemberNetworkVisibility': string;
454
- };
455
- distribution?: any;
437
+ };
438
+ visibility: {
439
+ 'com.linkedin.ugc.MemberNetworkVisibility': string;
440
+ };
441
+ distribution?: any;
456
442
  }
457
443
 
458
444
  export interface LinkedInAnalytics {
459
- totalShareStatistics?: {
460
- impressionCount: number;
461
- clickCount: number;
462
- engagement: number;
463
- likeCount: number;
464
- commentCount: number;
465
- shareCount: number;
466
- uniqueImpressionsCount: number;
467
- };
468
- timeRange?: {
469
- start: number;
470
- end: number;
471
- };
445
+ totalShareStatistics?: {
446
+ impressionCount: number;
447
+ clickCount: number;
448
+ engagement: number;
449
+ likeCount: number;
450
+ commentCount: number;
451
+ shareCount: number;
452
+ uniqueImpressionsCount: number;
453
+ };
454
+ timeRange?: {
455
+ start: number;
456
+ end: number;
457
+ };
472
458
  }
473
459
 
474
460
  export interface LinkedInArticle {
475
- author: string;
476
- publishedAt: number;
477
- coverImage?: string;
478
- title: string;
479
- description?: string;
480
- content: string;
481
- visibility: string;
482
- }
461
+ author: string;
462
+ publishedAt: number;
463
+ coverImage?: string;
464
+ title: string;
465
+ description?: string;
466
+ content: string;
467
+ visibility: string;
468
+ }