@memberjunction/actions-bizapps-social 2.112.0 → 2.113.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/base/base-social.action.d.ts.map +1 -1
- package/dist/base/base-social.action.js +24 -18
- package/dist/base/base-social.action.js.map +1 -1
- package/dist/providers/buffer/buffer-base.action.d.ts.map +1 -1
- package/dist/providers/buffer/buffer-base.action.js +34 -35
- package/dist/providers/buffer/buffer-base.action.js.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.js +33 -33
- package/dist/providers/facebook/actions/boost-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.js +36 -34
- package/dist/providers/facebook/actions/create-album.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.js +20 -20
- package/dist/providers/facebook/actions/create-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.js +27 -25
- package/dist/providers/facebook/actions/get-page-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.js +23 -19
- package/dist/providers/facebook/actions/get-page-posts.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.js +32 -28
- package/dist/providers/facebook/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.js +44 -42
- package/dist/providers/facebook/actions/respond-to-comments.action.js.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.js +29 -29
- package/dist/providers/facebook/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.js +39 -37
- package/dist/providers/facebook/actions/search-posts.action.js.map +1 -1
- package/dist/providers/facebook/facebook-base.action.d.ts.map +1 -1
- package/dist/providers/facebook/facebook-base.action.js +59 -44
- package/dist/providers/facebook/facebook-base.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js +31 -33
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js +32 -28
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js +19 -19
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.js +26 -24
- package/dist/providers/hootsuite/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js +22 -22
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js +34 -32
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.js +52 -43
- package/dist/providers/hootsuite/actions/search-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js +28 -30
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.js +20 -18
- package/dist/providers/hootsuite/hootsuite-base.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.js +26 -27
- package/dist/providers/instagram/actions/create-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.js +35 -35
- package/dist/providers/instagram/actions/create-story.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.js +59 -38
- package/dist/providers/instagram/actions/get-account-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.js +29 -29
- package/dist/providers/instagram/actions/get-business-posts.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.js +36 -36
- package/dist/providers/instagram/actions/get-comments.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.js +25 -23
- package/dist/providers/instagram/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.js +25 -25
- package/dist/providers/instagram/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.js +60 -56
- package/dist/providers/instagram/actions/search-posts.action.js.map +1 -1
- package/dist/providers/instagram/instagram-base.action.d.ts.map +1 -1
- package/dist/providers/instagram/instagram-base.action.js +27 -25
- package/dist/providers/instagram/instagram-base.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.js +45 -55
- package/dist/providers/linkedin/actions/create-article.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.js +29 -31
- package/dist/providers/linkedin/actions/create-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.js +28 -28
- package/dist/providers/linkedin/actions/get-followers.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.js +20 -20
- package/dist/providers/linkedin/actions/get-organization-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.js +19 -19
- package/dist/providers/linkedin/actions/get-personal-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.js +23 -25
- package/dist/providers/linkedin/actions/get-post-analytics.action.js.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.js +30 -32
- package/dist/providers/linkedin/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.js +30 -28
- package/dist/providers/linkedin/actions/search-posts.action.js.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.d.ts.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.js +38 -33
- package/dist/providers/linkedin/linkedin-base.action.js.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.d.ts.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.js +26 -25
- package/dist/providers/tiktok/tiktok-base.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.js +29 -25
- package/dist/providers/twitter/actions/create-thread.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.js +23 -23
- package/dist/providers/twitter/actions/create-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.js +19 -19
- package/dist/providers/twitter/actions/delete-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.js +47 -40
- package/dist/providers/twitter/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.js +31 -30
- package/dist/providers/twitter/actions/get-mentions.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.js +29 -29
- package/dist/providers/twitter/actions/get-timeline.action.js.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.js +26 -26
- package/dist/providers/twitter/actions/schedule-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.js +58 -56
- package/dist/providers/twitter/actions/search-tweets.action.js.map +1 -1
- package/dist/providers/twitter/twitter-base.action.d.ts.map +1 -1
- package/dist/providers/twitter/twitter-base.action.js +68 -58
- package/dist/providers/twitter/twitter-base.action.js.map +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts.map +1 -1
- package/dist/providers/youtube/youtube-base.action.js +25 -22
- package/dist/providers/youtube/youtube-base.action.js.map +1 -1
- package/package.json +6 -5
- package/src/base/base-social.action.ts +224 -217
- package/src/providers/buffer/buffer-base.action.ts +441 -435
- package/src/providers/facebook/actions/boost-post.action.ts +386 -350
- package/src/providers/facebook/actions/create-album.action.ts +307 -291
- package/src/providers/facebook/actions/create-post.action.ts +227 -224
- package/src/providers/facebook/actions/get-page-insights.action.ts +403 -383
- package/src/providers/facebook/actions/get-page-posts.action.ts +225 -214
- package/src/providers/facebook/actions/get-post-insights.action.ts +316 -300
- package/src/providers/facebook/actions/respond-to-comments.action.ts +336 -319
- package/src/providers/facebook/actions/schedule-post.action.ts +292 -289
- package/src/providers/facebook/actions/search-posts.action.ts +413 -399
- package/src/providers/facebook/facebook-base.action.ts +670 -653
- package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
- package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +189 -184
- package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +161 -160
- package/src/providers/hootsuite/actions/get-analytics.action.ts +254 -249
- package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +207 -206
- package/src/providers/hootsuite/actions/get-social-profiles.action.ts +205 -206
- package/src/providers/hootsuite/actions/search-posts.action.ts +369 -351
- package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +209 -211
- package/src/providers/hootsuite/hootsuite-base.action.ts +307 -301
- package/src/providers/instagram/actions/create-post.action.ts +296 -276
- package/src/providers/instagram/actions/create-story.action.ts +394 -378
- package/src/providers/instagram/actions/get-account-insights.action.ts +420 -384
- package/src/providers/instagram/actions/get-business-posts.action.ts +242 -233
- package/src/providers/instagram/actions/get-comments.action.ts +377 -365
- package/src/providers/instagram/actions/get-post-insights.action.ts +273 -265
- package/src/providers/instagram/actions/schedule-post.action.ts +235 -233
- package/src/providers/instagram/actions/search-posts.action.ts +538 -512
- package/src/providers/instagram/instagram-base.action.ts +393 -368
- package/src/providers/linkedin/actions/create-article.action.ts +266 -275
- package/src/providers/linkedin/actions/create-post.action.ts +177 -179
- package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
- package/src/providers/linkedin/actions/get-organization-posts.action.ts +147 -146
- package/src/providers/linkedin/actions/get-personal-posts.action.ts +139 -138
- package/src/providers/linkedin/actions/get-post-analytics.action.ts +189 -190
- package/src/providers/linkedin/actions/schedule-post.action.ts +189 -191
- package/src/providers/linkedin/actions/search-posts.action.ts +283 -275
- package/src/providers/linkedin/linkedin-base.action.ts +421 -407
- package/src/providers/tiktok/tiktok-base.action.ts +320 -305
- package/src/providers/twitter/actions/create-thread.action.ts +207 -203
- package/src/providers/twitter/actions/create-tweet.action.ts +188 -187
- package/src/providers/twitter/actions/delete-tweet.action.ts +129 -128
- package/src/providers/twitter/actions/get-analytics.action.ts +411 -402
- package/src/providers/twitter/actions/get-mentions.action.ts +219 -218
- package/src/providers/twitter/actions/get-timeline.action.ts +233 -232
- package/src/providers/twitter/actions/schedule-tweet.action.ts +222 -221
- package/src/providers/twitter/actions/search-tweets.action.ts +543 -540
- package/src/providers/twitter/twitter-base.action.ts +560 -541
- 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 } 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/
|
|
5
|
+
import { LogStatus, LogError } from '@memberjunction/core';
|
|
6
6
|
import { BaseAction } from '@memberjunction/actions';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -11,331 +11,337 @@ import { BaseAction } from '@memberjunction/actions';
|
|
|
11
11
|
*/
|
|
12
12
|
@RegisterClass(BaseAction, 'HootSuiteBaseAction')
|
|
13
13
|
export abstract class HootSuiteBaseAction extends BaseSocialMediaAction {
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
);
|
|
14
|
+
protected get platformName(): string {
|
|
15
|
+
return 'HootSuite';
|
|
76
16
|
}
|
|
77
|
-
|
|
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');
|
|
17
|
+
|
|
18
|
+
protected get apiBaseUrl(): string {
|
|
19
|
+
return 'https://platform.hootsuite.com/v1';
|
|
87
20
|
}
|
|
88
21
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
);
|
|
102
76
|
}
|
|
103
|
-
|
|
77
|
+
return this._axiosInstance;
|
|
78
|
+
}
|
|
104
79
|
|
|
105
|
-
|
|
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
|
+
}
|
|
106
88
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
}
|
|
114
150
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
|
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
|
+
}
|
|
147
175
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (state === 'READY') {
|
|
160
|
-
return;
|
|
161
|
-
} else if (state === 'FAILED') {
|
|
162
|
-
throw new Error('Media processing failed');
|
|
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;
|
|
163
187
|
}
|
|
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;
|
|
164
215
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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);
|
|
170
232
|
}
|
|
171
|
-
|
|
233
|
+
return date.toISOString();
|
|
172
234
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Parse HootSuite date string
|
|
238
|
+
*/
|
|
239
|
+
protected parseHootSuiteDate(dateString: string): Date {
|
|
240
|
+
return new Date(dateString);
|
|
185
241
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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);
|
|
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
|
+
};
|
|
226
262
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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}`);
|
|
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');
|
|
270
|
+
}
|
|
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
|
+
}
|
|
294
301
|
}
|
|
295
|
-
}
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
/**
|
|
299
305
|
* HootSuite-specific interfaces
|
|
300
306
|
*/
|
|
301
307
|
export interface HootSuiteProfile {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
id: string;
|
|
309
|
+
socialNetworkId: string;
|
|
310
|
+
socialNetworkUserId: string;
|
|
311
|
+
avatarUrl: string;
|
|
312
|
+
displayName: string;
|
|
313
|
+
type: string;
|
|
314
|
+
ownerId: string;
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
export interface HootSuitePost {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
};
|
|
324
330
|
}
|
|
325
331
|
|
|
326
332
|
export interface HootSuiteAnalytics {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
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
|
+
}
|