@memberjunction/actions-bizapps-social 2.112.0 → 2.113.1

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 +13 -0
  3. package/dist/base/base-social.action.d.ts.map +1 -1
  4. package/dist/base/base-social.action.js +24 -18
  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 +34 -35
  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 +36 -34
  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 +27 -25
  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 +23 -19
  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 +32 -28
  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 +44 -42
  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 +39 -37
  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 +59 -44
  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 +31 -33
  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 +32 -28
  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 +26 -24
  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 +34 -32
  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 +52 -43
  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 +28 -30
  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 +20 -18
  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 +26 -27
  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 +59 -38
  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 +25 -23
  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 +60 -56
  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 +27 -25
  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 +45 -55
  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 +29 -31
  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 +23 -25
  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 +30 -32
  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 +30 -28
  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 +38 -33
  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 +26 -25
  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 +29 -25
  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 +47 -40
  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 +31 -30
  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 +58 -56
  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 +68 -58
  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 +25 -22
  153. package/dist/providers/youtube/youtube-base.action.js.map +1 -1
  154. package/package.json +6 -5
  155. package/src/base/base-social.action.ts +224 -217
  156. package/src/providers/buffer/buffer-base.action.ts +441 -435
  157. package/src/providers/facebook/actions/boost-post.action.ts +386 -350
  158. package/src/providers/facebook/actions/create-album.action.ts +307 -291
  159. package/src/providers/facebook/actions/create-post.action.ts +227 -224
  160. package/src/providers/facebook/actions/get-page-insights.action.ts +403 -383
  161. package/src/providers/facebook/actions/get-page-posts.action.ts +225 -214
  162. package/src/providers/facebook/actions/get-post-insights.action.ts +316 -300
  163. package/src/providers/facebook/actions/respond-to-comments.action.ts +336 -319
  164. package/src/providers/facebook/actions/schedule-post.action.ts +292 -289
  165. package/src/providers/facebook/actions/search-posts.action.ts +413 -399
  166. package/src/providers/facebook/facebook-base.action.ts +670 -653
  167. package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
  168. package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +189 -184
  169. package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +161 -160
  170. package/src/providers/hootsuite/actions/get-analytics.action.ts +254 -249
  171. package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +207 -206
  172. package/src/providers/hootsuite/actions/get-social-profiles.action.ts +205 -206
  173. package/src/providers/hootsuite/actions/search-posts.action.ts +369 -351
  174. package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +209 -211
  175. package/src/providers/hootsuite/hootsuite-base.action.ts +307 -301
  176. package/src/providers/instagram/actions/create-post.action.ts +296 -276
  177. package/src/providers/instagram/actions/create-story.action.ts +394 -378
  178. package/src/providers/instagram/actions/get-account-insights.action.ts +420 -384
  179. package/src/providers/instagram/actions/get-business-posts.action.ts +242 -233
  180. package/src/providers/instagram/actions/get-comments.action.ts +377 -365
  181. package/src/providers/instagram/actions/get-post-insights.action.ts +273 -265
  182. package/src/providers/instagram/actions/schedule-post.action.ts +235 -233
  183. package/src/providers/instagram/actions/search-posts.action.ts +538 -512
  184. package/src/providers/instagram/instagram-base.action.ts +393 -368
  185. package/src/providers/linkedin/actions/create-article.action.ts +266 -275
  186. package/src/providers/linkedin/actions/create-post.action.ts +177 -179
  187. package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
  188. package/src/providers/linkedin/actions/get-organization-posts.action.ts +147 -146
  189. package/src/providers/linkedin/actions/get-personal-posts.action.ts +139 -138
  190. package/src/providers/linkedin/actions/get-post-analytics.action.ts +189 -190
  191. package/src/providers/linkedin/actions/schedule-post.action.ts +189 -191
  192. package/src/providers/linkedin/actions/search-posts.action.ts +283 -275
  193. package/src/providers/linkedin/linkedin-base.action.ts +421 -407
  194. package/src/providers/tiktok/tiktok-base.action.ts +320 -305
  195. package/src/providers/twitter/actions/create-thread.action.ts +207 -203
  196. package/src/providers/twitter/actions/create-tweet.action.ts +188 -187
  197. package/src/providers/twitter/actions/delete-tweet.action.ts +129 -128
  198. package/src/providers/twitter/actions/get-analytics.action.ts +411 -402
  199. package/src/providers/twitter/actions/get-mentions.action.ts +219 -218
  200. package/src/providers/twitter/actions/get-timeline.action.ts +233 -232
  201. package/src/providers/twitter/actions/schedule-tweet.action.ts +222 -221
  202. package/src/providers/twitter/actions/search-tweets.action.ts +543 -540
  203. package/src/providers/twitter/twitter-base.action.ts +560 -541
  204. package/src/providers/youtube/youtube-base.action.ts +333 -320
@@ -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/global';
5
+ import { LogStatus, LogError } from '@memberjunction/core';
6
6
  import { BaseAction } from '@memberjunction/actions';
7
7
 
8
8
  /**
@@ -12,457 +12,471 @@ 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';
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
- );
15
+ protected get platformName(): string {
16
+ return 'LinkedIn';
78
17
  }
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');
18
+
19
+ protected get apiBaseUrl(): string {
20
+ return 'https://api.linkedin.com/v2';
89
21
  }
90
22
 
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
- },
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
+ );
104
78
  }
105
- );
79
+ return this._axiosInstance;
80
+ }
106
81
 
107
- const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
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
+ }
108
90
 
109
- // Update stored tokens
110
- await this.updateStoredTokens(access_token, newRefreshToken || refreshToken, expires_in);
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
+ }
111
121
 
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;
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
+ }
116
133
  }
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;
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
+ }
129
166
  }
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() || '',
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
+ }
153
198
  });
154
- }
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(', ')}`);
155
221
  }
156
- }
157
222
 
158
- return organizations;
159
- } catch (error) {
160
- LogError(`Failed to get admin organizations: ${error instanceof Error ? error.message : 'Unknown error'}`);
161
- throw error;
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
+ }
162
228
  }
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;
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
+ }
202
240
  }
203
- }
204
241
 
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'];
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
+ });
210
255
 
211
- if (!supportedTypes.includes(file.mimeType)) {
212
- throw new Error(`Unsupported media type: ${file.mimeType}. Supported types: ${supportedTypes.join(', ')}`);
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
+ }
213
261
  }
214
262
 
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`);
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
+ };
219
292
  }
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);
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
+ };
231
308
  }
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;
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');
252
316
  }
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);
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}`);
267
348
  }
268
- }
269
349
  }
270
350
 
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 };
365
- }
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
+ }
366
380
 
367
- return null;
368
- }
381
+ return null;
382
+ }
369
383
  }
370
384
 
371
385
  /**
372
386
  * LinkedIn-specific interfaces
373
387
  */
374
388
  export interface LinkedInOrganization {
375
- urn: string;
376
- name: string;
377
- id: string;
389
+ urn: string;
390
+ name: string;
391
+ id: string;
378
392
  }
379
393
 
380
394
  export interface LinkedInShareData {
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;
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
+ }>;
394
413
  };
395
- description?: {
396
- text: string;
397
- };
398
- }>;
399
414
  };
400
- };
401
- visibility: {
402
- 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN' | 'CONTAINER';
403
- };
404
- distribution?: {
405
- linkedInDistributionTarget?: {
406
- visibleToGuest?: boolean;
415
+ visibility: {
416
+ 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS' | 'LOGGED_IN' | 'CONTAINER';
417
+ };
418
+ distribution?: {
419
+ linkedInDistributionTarget?: {
420
+ visibleToGuest?: boolean;
421
+ };
407
422
  };
408
- };
409
423
  }
410
424
 
411
425
  export interface LinkedInShare {
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;
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
+ }>;
434
450
  };
435
- }>;
436
451
  };
437
- };
438
- visibility: {
439
- 'com.linkedin.ugc.MemberNetworkVisibility': string;
440
- };
441
- distribution?: any;
452
+ visibility: {
453
+ 'com.linkedin.ugc.MemberNetworkVisibility': string;
454
+ };
455
+ distribution?: any;
442
456
  }
443
457
 
444
458
  export interface LinkedInAnalytics {
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
- };
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
+ };
458
472
  }
459
473
 
460
474
  export interface LinkedInArticle {
461
- author: string;
462
- publishedAt: number;
463
- coverImage?: string;
464
- title: string;
465
- description?: string;
466
- content: string;
467
- visibility: string;
468
- }
475
+ author: string;
476
+ publishedAt: number;
477
+ coverImage?: string;
478
+ title: string;
479
+ description?: string;
480
+ content: string;
481
+ visibility: string;
482
+ }