@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.
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RegisterClass } from '@memberjunction/global';
|
|
2
2
|
import { BaseSocialMediaAction, MediaFile } from '../../base/base-social.action';
|
|
3
|
-
import { UserInfo, LogStatus, LogError } from '@memberjunction/
|
|
3
|
+
import { UserInfo, LogStatus, LogError } from '@memberjunction/core';
|
|
4
4
|
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
5
5
|
import { BaseAction } from '@memberjunction/actions';
|
|
6
6
|
|
|
@@ -11,396 +11,421 @@ import { BaseAction } from '@memberjunction/actions';
|
|
|
11
11
|
*/
|
|
12
12
|
@RegisterClass(BaseAction, 'InstagramBaseAction')
|
|
13
13
|
export abstract class InstagramBaseAction extends BaseSocialMediaAction {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
protected get apiBaseUrl(): string {
|
|
19
|
-
return 'https://graph.facebook.com/v18.0';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Instagram Business Account ID (stored in CustomAttribute1)
|
|
24
|
-
*/
|
|
25
|
-
protected get instagramBusinessAccountId(): string {
|
|
26
|
-
return this.getCustomAttribute(1) || '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Facebook Page ID (stored in CustomAttribute2) - required for Instagram Business API
|
|
31
|
-
*/
|
|
32
|
-
protected get facebookPageId(): string {
|
|
33
|
-
return this.getCustomAttribute(2) || '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Axios instance for API calls
|
|
38
|
-
*/
|
|
39
|
-
private _axiosInstance: AxiosInstance | null = null;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get or create axios instance with authentication
|
|
43
|
-
*/
|
|
44
|
-
protected get axios(): AxiosInstance {
|
|
45
|
-
if (!this._axiosInstance) {
|
|
46
|
-
this._axiosInstance = axios.create({
|
|
47
|
-
baseURL: this.apiBaseUrl,
|
|
48
|
-
headers: this.buildHeaders(),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Add response interceptor for error handling
|
|
52
|
-
this._axiosInstance.interceptors.response.use(
|
|
53
|
-
(response) => response,
|
|
54
|
-
async (error) => {
|
|
55
|
-
if (error.response?.status === 401 && this.isAuthError(error)) {
|
|
56
|
-
LogStatus('Instagram token appears invalid, attempting refresh...');
|
|
57
|
-
await this.refreshAccessToken();
|
|
58
|
-
|
|
59
|
-
// Retry the request with new token
|
|
60
|
-
error.config.headers.Authorization = `Bearer ${this.getAccessToken()}`;
|
|
61
|
-
return axios.request(error.config);
|
|
62
|
-
}
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
);
|
|
14
|
+
protected get platformName(): string {
|
|
15
|
+
return 'Instagram';
|
|
66
16
|
}
|
|
67
|
-
return this._axiosInstance;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Refresh the Instagram/Facebook access token
|
|
72
|
-
*/
|
|
73
|
-
protected async refreshAccessToken(): Promise<void> {
|
|
74
|
-
try {
|
|
75
|
-
// Instagram uses Facebook's OAuth system
|
|
76
|
-
// Long-lived tokens need to be exchanged periodically
|
|
77
|
-
const currentToken = this.getAccessToken();
|
|
78
|
-
if (!currentToken) {
|
|
79
|
-
throw new Error('No access token available to refresh');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Exchange for a new long-lived token
|
|
83
|
-
const response = await axios.get(`${this.apiBaseUrl}/oauth/access_token`, {
|
|
84
|
-
params: {
|
|
85
|
-
grant_type: 'fb_exchange_token',
|
|
86
|
-
client_id: this.getCustomAttribute(3), // App ID stored in CustomAttribute3
|
|
87
|
-
client_secret: this.getCustomAttribute(4), // App Secret stored in CustomAttribute4
|
|
88
|
-
fb_exchange_token: currentToken,
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (response.data.access_token) {
|
|
93
|
-
await this.updateStoredTokens(
|
|
94
|
-
response.data.access_token,
|
|
95
|
-
undefined, // Instagram doesn't use refresh tokens
|
|
96
|
-
response.data.expires_in || 5184000 // Default to 60 days
|
|
97
|
-
);
|
|
98
17
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
} catch (error) {
|
|
103
|
-
LogError('Failed to refresh Instagram access token', error);
|
|
104
|
-
throw new Error('Failed to refresh Instagram access token');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Make an Instagram Graph API request
|
|
110
|
-
*/
|
|
111
|
-
protected async makeInstagramRequest<T = any>(
|
|
112
|
-
endpoint: string,
|
|
113
|
-
method: 'GET' | 'POST' | 'DELETE' = 'GET',
|
|
114
|
-
data?: any,
|
|
115
|
-
params?: any
|
|
116
|
-
): Promise<T> {
|
|
117
|
-
try {
|
|
118
|
-
this.logApiRequest(method, `${this.apiBaseUrl}/${endpoint}`, data || params);
|
|
119
|
-
|
|
120
|
-
const response = await this.axios.request<T>({
|
|
121
|
-
url: endpoint,
|
|
122
|
-
method,
|
|
123
|
-
data,
|
|
124
|
-
params,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
this.logApiResponse(response.data);
|
|
128
|
-
return response.data;
|
|
129
|
-
} catch (error) {
|
|
130
|
-
if (axios.isAxiosError(error)) {
|
|
131
|
-
this.handleInstagramError(error);
|
|
132
|
-
}
|
|
133
|
-
throw error;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Handle Instagram-specific errors
|
|
139
|
-
*/
|
|
140
|
-
protected handleInstagramError(error: AxiosError): void {
|
|
141
|
-
const response = error.response;
|
|
142
|
-
if (!response) {
|
|
143
|
-
throw new Error('Network error occurred while calling Instagram API');
|
|
18
|
+
protected get apiBaseUrl(): string {
|
|
19
|
+
return 'https://graph.facebook.com/v18.0';
|
|
144
20
|
}
|
|
145
21
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Instagram Business Account ID (stored in CustomAttribute1)
|
|
24
|
+
*/
|
|
25
|
+
protected get instagramBusinessAccountId(): string {
|
|
26
|
+
return this.getCustomAttribute(1) || '';
|
|
27
|
+
}
|
|
150
28
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Facebook Page ID (stored in CustomAttribute2) - required for Instagram Business API
|
|
31
|
+
*/
|
|
32
|
+
protected get facebookPageId(): string {
|
|
33
|
+
return this.getCustomAttribute(2) || '';
|
|
34
|
+
}
|
|
154
35
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Axios instance for API calls
|
|
38
|
+
*/
|
|
39
|
+
private _axiosInstance: AxiosInstance | null = null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get or create axios instance with authentication
|
|
43
|
+
*/
|
|
44
|
+
protected get axios(): AxiosInstance {
|
|
45
|
+
if (!this._axiosInstance) {
|
|
46
|
+
this._axiosInstance = axios.create({
|
|
47
|
+
baseURL: this.apiBaseUrl,
|
|
48
|
+
headers: this.buildHeaders()
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Add response interceptor for error handling
|
|
52
|
+
this._axiosInstance.interceptors.response.use(
|
|
53
|
+
response => response,
|
|
54
|
+
async error => {
|
|
55
|
+
if (error.response?.status === 401 && this.isAuthError(error)) {
|
|
56
|
+
LogStatus('Instagram token appears invalid, attempting refresh...');
|
|
57
|
+
await this.refreshAccessToken();
|
|
58
|
+
|
|
59
|
+
// Retry the request with new token
|
|
60
|
+
error.config.headers.Authorization = `Bearer ${this.getAccessToken()}`;
|
|
61
|
+
return axios.request(error.config);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return this._axiosInstance;
|
|
160
68
|
}
|
|
161
69
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Refresh the Instagram/Facebook access token
|
|
72
|
+
*/
|
|
73
|
+
protected async refreshAccessToken(): Promise<void> {
|
|
74
|
+
try {
|
|
75
|
+
// Instagram uses Facebook's OAuth system
|
|
76
|
+
// Long-lived tokens need to be exchanged periodically
|
|
77
|
+
const currentToken = this.getAccessToken();
|
|
78
|
+
if (!currentToken) {
|
|
79
|
+
throw new Error('No access token available to refresh');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Exchange for a new long-lived token
|
|
83
|
+
const response = await axios.get(`${this.apiBaseUrl}/oauth/access_token`, {
|
|
84
|
+
params: {
|
|
85
|
+
grant_type: 'fb_exchange_token',
|
|
86
|
+
client_id: this.getCustomAttribute(3), // App ID stored in CustomAttribute3
|
|
87
|
+
client_secret: this.getCustomAttribute(4), // App Secret stored in CustomAttribute4
|
|
88
|
+
fb_exchange_token: currentToken
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (response.data.access_token) {
|
|
93
|
+
await this.updateStoredTokens(
|
|
94
|
+
response.data.access_token,
|
|
95
|
+
undefined, // Instagram doesn't use refresh tokens
|
|
96
|
+
response.data.expires_in || 5184000 // Default to 60 days
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Reset axios instance to use new token
|
|
100
|
+
this._axiosInstance = null;
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
LogError('Failed to refresh Instagram access token', error);
|
|
104
|
+
throw new Error('Failed to refresh Instagram access token');
|
|
105
|
+
}
|
|
168
106
|
}
|
|
169
107
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Make an Instagram Graph API request
|
|
110
|
+
*/
|
|
111
|
+
protected async makeInstagramRequest<T = any>(
|
|
112
|
+
endpoint: string,
|
|
113
|
+
method: 'GET' | 'POST' | 'DELETE' = 'GET',
|
|
114
|
+
data?: any,
|
|
115
|
+
params?: any
|
|
116
|
+
): Promise<T> {
|
|
117
|
+
try {
|
|
118
|
+
this.logApiRequest(method, `${this.apiBaseUrl}/${endpoint}`, data || params);
|
|
119
|
+
|
|
120
|
+
const response = await this.axios.request<T>({
|
|
121
|
+
url: endpoint,
|
|
122
|
+
method,
|
|
123
|
+
data,
|
|
124
|
+
params
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.logApiResponse(response.data);
|
|
128
|
+
return response.data;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (axios.isAxiosError(error)) {
|
|
131
|
+
this.handleInstagramError(error);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
176
135
|
}
|
|
177
136
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Handle Instagram-specific errors
|
|
139
|
+
*/
|
|
140
|
+
protected handleInstagramError(error: AxiosError): void {
|
|
141
|
+
const response = error.response;
|
|
142
|
+
if (!response) {
|
|
143
|
+
throw new Error('Network error occurred while calling Instagram API');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const errorData = response.data as any;
|
|
147
|
+
const errorMessage = errorData?.error?.message || 'Unknown Instagram API error';
|
|
148
|
+
const errorCode = errorData?.error?.code;
|
|
149
|
+
const errorSubcode = errorData?.error?.error_subcode;
|
|
150
|
+
|
|
151
|
+
// Check for rate limiting
|
|
152
|
+
if (errorCode === 32 || errorCode === 4 || response.status === 429) {
|
|
153
|
+
const retryAfter = response.headers['x-app-usage']
|
|
154
|
+
? this.parseAppUsage(response.headers['x-app-usage'])
|
|
155
|
+
: 3600; // Default to 1 hour
|
|
156
|
+
|
|
157
|
+
throw {
|
|
158
|
+
code: 'RATE_LIMIT',
|
|
159
|
+
message: 'Instagram API rate limit exceeded',
|
|
160
|
+
retryAfter
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check for permission errors
|
|
165
|
+
if (errorCode === 10 || errorSubcode === 460) {
|
|
166
|
+
throw {
|
|
167
|
+
code: 'INSUFFICIENT_PERMISSIONS',
|
|
168
|
+
message: 'Insufficient permissions for this Instagram operation'
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Media errors
|
|
173
|
+
if (errorCode === 100 && errorMessage.toLowerCase().includes('media')) {
|
|
174
|
+
throw {
|
|
175
|
+
code: 'INVALID_MEDIA',
|
|
176
|
+
message: errorMessage
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Post not found
|
|
181
|
+
if (errorCode === 100 && errorSubcode === 33) {
|
|
182
|
+
throw {
|
|
183
|
+
code: 'POST_NOT_FOUND',
|
|
184
|
+
message: 'Instagram post not found'
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw {
|
|
189
|
+
code: 'PLATFORM_ERROR',
|
|
190
|
+
message: errorMessage,
|
|
191
|
+
details: errorData
|
|
192
|
+
};
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (callCount > 90 || totalTime > 90 || totalCputime > 90) {
|
|
205
|
-
return 3600; // Wait 1 hour
|
|
206
|
-
}
|
|
207
|
-
return 0;
|
|
208
|
-
} catch {
|
|
209
|
-
return 0;
|
|
195
|
+
/**
|
|
196
|
+
* Parse Facebook's app usage header to determine rate limit status
|
|
197
|
+
*/
|
|
198
|
+
private parseAppUsage(appUsage: string): number {
|
|
199
|
+
try {
|
|
200
|
+
const usage = JSON.parse(appUsage);
|
|
201
|
+
const callCount = usage.call_count || 0;
|
|
202
|
+
const totalTime = usage.total_time || 0;
|
|
203
|
+
const totalCputime = usage.total_cputime || 0;
|
|
204
|
+
|
|
205
|
+
// If any metric is above 90%, implement backoff
|
|
206
|
+
if (callCount > 90 || totalTime > 90 || totalCputime > 90) {
|
|
207
|
+
return 3600; // Wait 1 hour
|
|
208
|
+
}
|
|
209
|
+
return 0;
|
|
210
|
+
} catch {
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
210
213
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Upload media to Instagram (returns container ID)
|
|
217
|
+
*/
|
|
218
|
+
protected async uploadSingleMedia(file: MediaFile): Promise<string> {
|
|
219
|
+
try {
|
|
220
|
+
// For Instagram, media must be hosted at a public URL
|
|
221
|
+
// This is a simplified version - in production, you'd upload to a CDN first
|
|
222
|
+
const mediaUrl = await this.uploadMediaToCDN(file);
|
|
223
|
+
|
|
224
|
+
let containerParams: any = {
|
|
225
|
+
access_token: this.getAccessToken()
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Determine media type and set appropriate parameters
|
|
229
|
+
if (file.mimeType.startsWith('image/')) {
|
|
230
|
+
containerParams.image_url = mediaUrl;
|
|
231
|
+
|
|
232
|
+
// Check if it's a carousel
|
|
233
|
+
if (file.filename.includes('carousel')) {
|
|
234
|
+
containerParams.is_carousel_item = true;
|
|
235
|
+
}
|
|
236
|
+
} else if (file.mimeType.startsWith('video/')) {
|
|
237
|
+
containerParams.video_url = mediaUrl;
|
|
238
|
+
containerParams.media_type = 'REELS'; // or 'VIDEO' for feed videos
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Add caption if provided in metadata
|
|
242
|
+
const metadata = (file as any).metadata;
|
|
243
|
+
if (metadata?.caption) {
|
|
244
|
+
containerParams.caption = metadata.caption;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Create media container
|
|
248
|
+
const response = await this.makeInstagramRequest<{ id: string }>(
|
|
249
|
+
`${this.instagramBusinessAccountId}/media`,
|
|
250
|
+
'POST',
|
|
251
|
+
containerParams
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return response.id;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
LogError('Failed to upload media to Instagram', error);
|
|
257
|
+
throw error;
|
|
233
258
|
}
|
|
234
|
-
} else if (file.mimeType.startsWith('video/')) {
|
|
235
|
-
containerParams.video_url = mediaUrl;
|
|
236
|
-
containerParams.media_type = 'REELS'; // or 'VIDEO' for feed videos
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Add caption if provided in metadata
|
|
240
|
-
const metadata = (file as any).metadata;
|
|
241
|
-
if (metadata?.caption) {
|
|
242
|
-
containerParams.caption = metadata.caption;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Create media container
|
|
246
|
-
const response = await this.makeInstagramRequest<{ id: string }>(`${this.instagramBusinessAccountId}/media`, 'POST', containerParams);
|
|
247
|
-
|
|
248
|
-
return response.id;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
LogError('Failed to upload media to Instagram', error);
|
|
251
|
-
throw error;
|
|
252
259
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* Get insights for a media object or account
|
|
278
|
-
*/
|
|
279
|
-
protected async getInsights(objectId: string, metrics: string[], period?: 'lifetime' | 'day' | 'week' | 'days_28'): Promise<any> {
|
|
280
|
-
const params: any = {
|
|
281
|
-
metric: metrics.join(','),
|
|
282
|
-
access_token: this.getAccessToken(),
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
if (period) {
|
|
286
|
-
params.period = period;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Upload media to a CDN (placeholder - implement based on your CDN)
|
|
263
|
+
*/
|
|
264
|
+
private async uploadMediaToCDN(file: MediaFile): Promise<string> {
|
|
265
|
+
// In a real implementation, this would upload to S3, Cloudinary, etc.
|
|
266
|
+
// For now, throw an error indicating this needs implementation
|
|
267
|
+
throw new Error('Media CDN upload not implemented. Instagram requires media to be hosted at a public URL.');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Publish a media container
|
|
272
|
+
*/
|
|
273
|
+
protected async publishMediaContainer(containerId: string): Promise<string> {
|
|
274
|
+
const response = await this.makeInstagramRequest<{ id: string }>(
|
|
275
|
+
`${this.instagramBusinessAccountId}/media_publish`,
|
|
276
|
+
'POST',
|
|
277
|
+
{
|
|
278
|
+
creation_id: containerId,
|
|
279
|
+
access_token: this.getAccessToken()
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return response.id;
|
|
287
284
|
}
|
|
288
285
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Get insights for a media object or account
|
|
288
|
+
*/
|
|
289
|
+
protected async getInsights(
|
|
290
|
+
objectId: string,
|
|
291
|
+
metrics: string[],
|
|
292
|
+
period?: 'lifetime' | 'day' | 'week' | 'days_28'
|
|
293
|
+
): Promise<any> {
|
|
294
|
+
const params: any = {
|
|
295
|
+
metric: metrics.join(','),
|
|
296
|
+
access_token: this.getAccessToken()
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (period) {
|
|
300
|
+
params.period = period;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const response = await this.makeInstagramRequest<{ data: any[] }>(
|
|
304
|
+
`${objectId}/insights`,
|
|
305
|
+
'GET',
|
|
306
|
+
null,
|
|
307
|
+
params
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
return response.data;
|
|
312
311
|
}
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Search for posts (limited to business account's own posts)
|
|
315
|
+
*/
|
|
316
|
+
protected async searchPosts(params: any): Promise<any[]> {
|
|
317
|
+
// Instagram doesn't have a general search API
|
|
318
|
+
// We can only search within the business account's own posts
|
|
319
|
+
const fields = 'id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count';
|
|
320
|
+
|
|
321
|
+
let endpoint = `${this.instagramBusinessAccountId}/media`;
|
|
322
|
+
const queryParams: any = {
|
|
323
|
+
fields,
|
|
324
|
+
access_token: this.getAccessToken(),
|
|
325
|
+
limit: params.limit || 25
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Add date filtering if provided
|
|
329
|
+
if (params.startDate) {
|
|
330
|
+
queryParams.since = Math.floor(new Date(params.startDate).getTime() / 1000);
|
|
331
|
+
}
|
|
332
|
+
if (params.endDate) {
|
|
333
|
+
queryParams.until = Math.floor(new Date(params.endDate).getTime() / 1000);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const posts: any[] = [];
|
|
337
|
+
let hasNext = true;
|
|
338
|
+
|
|
339
|
+
while (hasNext && posts.length < (params.limit || 100)) {
|
|
340
|
+
const response = await this.makeInstagramRequest<{
|
|
341
|
+
data: any[];
|
|
342
|
+
paging?: { next: string };
|
|
343
|
+
}>(endpoint, 'GET', null, queryParams);
|
|
344
|
+
|
|
345
|
+
if (response.data) {
|
|
346
|
+
// Filter by caption if query is provided
|
|
347
|
+
const filtered = params.query
|
|
348
|
+
? response.data.filter(post =>
|
|
349
|
+
post.caption?.toLowerCase().includes(params.query.toLowerCase()))
|
|
350
|
+
: response.data;
|
|
351
|
+
|
|
352
|
+
posts.push(...filtered);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (response.paging?.next) {
|
|
356
|
+
// Parse next URL for pagination
|
|
357
|
+
const nextUrl = new URL(response.paging.next);
|
|
358
|
+
queryParams.after = nextUrl.searchParams.get('after');
|
|
359
|
+
} else {
|
|
360
|
+
hasNext = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return posts.slice(0, params.limit || 100);
|
|
315
365
|
}
|
|
316
366
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
367
|
+
/**
|
|
368
|
+
* Normalize Instagram post to common format
|
|
369
|
+
*/
|
|
370
|
+
protected normalizePost(instagramPost: any): any {
|
|
371
|
+
return {
|
|
372
|
+
id: instagramPost.id,
|
|
373
|
+
platform: 'Instagram',
|
|
374
|
+
profileId: this.instagramBusinessAccountId,
|
|
375
|
+
content: instagramPost.caption || '',
|
|
376
|
+
mediaUrls: instagramPost.media_url ? [instagramPost.media_url] : [],
|
|
377
|
+
publishedAt: new Date(instagramPost.timestamp),
|
|
378
|
+
analytics: {
|
|
379
|
+
impressions: instagramPost.impressions_count || 0,
|
|
380
|
+
engagements: (instagramPost.like_count || 0) + (instagramPost.comments_count || 0),
|
|
381
|
+
clicks: 0, // Not available in basic metrics
|
|
382
|
+
shares: 0, // Instagram doesn't track shares
|
|
383
|
+
comments: instagramPost.comments_count || 0,
|
|
384
|
+
likes: instagramPost.like_count || 0,
|
|
385
|
+
reach: instagramPost.reach || 0,
|
|
386
|
+
saves: instagramPost.saved || 0,
|
|
387
|
+
videoViews: instagramPost.video_views || 0,
|
|
388
|
+
platformMetrics: instagramPost
|
|
389
|
+
},
|
|
390
|
+
platformSpecificData: {
|
|
391
|
+
mediaType: instagramPost.media_type,
|
|
392
|
+
permalink: instagramPost.permalink,
|
|
393
|
+
isCarousel: instagramPost.media_type === 'CAROUSEL_ALBUM'
|
|
394
|
+
}
|
|
395
|
+
};
|
|
342
396
|
}
|
|
343
397
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
impressions: instagramPost.impressions_count || 0,
|
|
360
|
-
engagements: (instagramPost.like_count || 0) + (instagramPost.comments_count || 0),
|
|
361
|
-
clicks: 0, // Not available in basic metrics
|
|
362
|
-
shares: 0, // Instagram doesn't track shares
|
|
363
|
-
comments: instagramPost.comments_count || 0,
|
|
364
|
-
likes: instagramPost.like_count || 0,
|
|
365
|
-
reach: instagramPost.reach || 0,
|
|
366
|
-
saves: instagramPost.saved || 0,
|
|
367
|
-
videoViews: instagramPost.video_views || 0,
|
|
368
|
-
platformMetrics: instagramPost,
|
|
369
|
-
},
|
|
370
|
-
platformSpecificData: {
|
|
371
|
-
mediaType: instagramPost.media_type,
|
|
372
|
-
permalink: instagramPost.permalink,
|
|
373
|
-
isCarousel: instagramPost.media_type === 'CAROUSEL_ALBUM',
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Check if media container is ready for publishing
|
|
380
|
-
*/
|
|
381
|
-
protected async isMediaContainerReady(containerId: string): Promise<boolean> {
|
|
382
|
-
const response = await this.makeInstagramRequest<{ status_code: string }>(containerId, 'GET', null, {
|
|
383
|
-
fields: 'status_code',
|
|
384
|
-
access_token: this.getAccessToken(),
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return response.status_code === 'FINISHED';
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Wait for media container to be ready
|
|
392
|
-
*/
|
|
393
|
-
protected async waitForMediaContainer(containerId: string, maxWaitTime: number = 60000): Promise<void> {
|
|
394
|
-
const startTime = Date.now();
|
|
395
|
-
const pollInterval = 2000; // 2 seconds
|
|
396
|
-
|
|
397
|
-
while (Date.now() - startTime < maxWaitTime) {
|
|
398
|
-
if (await this.isMediaContainerReady(containerId)) {
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
398
|
+
/**
|
|
399
|
+
* Check if media container is ready for publishing
|
|
400
|
+
*/
|
|
401
|
+
protected async isMediaContainerReady(containerId: string): Promise<boolean> {
|
|
402
|
+
const response = await this.makeInstagramRequest<{ status_code: string }>(
|
|
403
|
+
containerId,
|
|
404
|
+
'GET',
|
|
405
|
+
null,
|
|
406
|
+
{
|
|
407
|
+
fields: 'status_code',
|
|
408
|
+
access_token: this.getAccessToken()
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return response.status_code === 'FINISHED';
|
|
402
413
|
}
|
|
403
414
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
415
|
+
/**
|
|
416
|
+
* Wait for media container to be ready
|
|
417
|
+
*/
|
|
418
|
+
protected async waitForMediaContainer(containerId: string, maxWaitTime: number = 60000): Promise<void> {
|
|
419
|
+
const startTime = Date.now();
|
|
420
|
+
const pollInterval = 2000; // 2 seconds
|
|
421
|
+
|
|
422
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
423
|
+
if (await this.isMediaContainerReady(containerId)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
throw new Error('Media container processing timeout');
|
|
430
|
+
}
|
|
431
|
+
}
|