@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, SocialPost, SocialAnalytics, MediaFile } from '../../base/base-social.action';
|
|
3
|
-
import { UserInfo, LogError, LogStatus } from '@memberjunction/
|
|
3
|
+
import { UserInfo, LogError, LogStatus } from '@memberjunction/core';
|
|
4
4
|
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
5
5
|
import FormData from 'form-data';
|
|
6
6
|
import { BaseAction } from '@memberjunction/actions';
|
|
@@ -11,475 +11,481 @@ import { BaseAction } from '@memberjunction/actions';
|
|
|
11
11
|
*/
|
|
12
12
|
@RegisterClass(BaseAction, 'BufferBaseAction')
|
|
13
13
|
export abstract class BufferBaseAction extends BaseSocialMediaAction {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
protected get apiBaseUrl(): string {
|
|
19
|
-
return 'https://api.bufferapp.com/1';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private axiosInstance: AxiosInstance | null = null;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get axios instance with authentication
|
|
26
|
-
*/
|
|
27
|
-
protected getAxiosInstance(): AxiosInstance {
|
|
28
|
-
if (!this.axiosInstance) {
|
|
29
|
-
this.axiosInstance = axios.create({
|
|
30
|
-
baseURL: this.apiBaseUrl,
|
|
31
|
-
timeout: 30000,
|
|
32
|
-
headers: {
|
|
33
|
-
Accept: 'application/json',
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Add request interceptor for auth
|
|
39
|
-
this.axiosInstance.interceptors.request.use(
|
|
40
|
-
(config) => {
|
|
41
|
-
const token = this.getAccessToken();
|
|
42
|
-
if (token) {
|
|
43
|
-
config.headers.Authorization = `Bearer ${token}`;
|
|
44
|
-
}
|
|
45
|
-
return config;
|
|
46
|
-
},
|
|
47
|
-
(error) => {
|
|
48
|
-
return Promise.reject(error);
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Add response interceptor for rate limiting
|
|
53
|
-
this.axiosInstance.interceptors.response.use(
|
|
54
|
-
(response) => {
|
|
55
|
-
// Log rate limit headers if present
|
|
56
|
-
const headers = response.headers;
|
|
57
|
-
const rateLimitInfo = this.parseRateLimitHeaders(headers);
|
|
58
|
-
if (rateLimitInfo) {
|
|
59
|
-
LogStatus(`Buffer API - Remaining requests: ${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 hit
|
|
66
|
-
const retryAfter = error.response.headers['retry-after'];
|
|
67
|
-
await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : 60);
|
|
68
|
-
// Retry the request
|
|
69
|
-
return this.axiosInstance!.request(error.config!);
|
|
70
|
-
}
|
|
71
|
-
return Promise.reject(error);
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return this.axiosInstance;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Refresh access token using refresh token
|
|
80
|
-
*/
|
|
81
|
-
protected async refreshAccessToken(): Promise<void> {
|
|
82
|
-
const refreshToken = this.getRefreshToken();
|
|
83
|
-
if (!refreshToken) {
|
|
84
|
-
throw new Error('No refresh token available for Buffer');
|
|
14
|
+
protected get platformName(): string {
|
|
15
|
+
return 'Buffer';
|
|
85
16
|
}
|
|
86
17
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
client_id: this.getCustomAttribute(1), // Store Buffer client ID in CustomAttribute1
|
|
90
|
-
client_secret: this.getCustomAttribute(2), // Store Buffer client secret in CustomAttribute2
|
|
91
|
-
refresh_token: refreshToken,
|
|
92
|
-
grant_type: 'refresh_token',
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const { access_token, expires_in } = response.data;
|
|
96
|
-
|
|
97
|
-
await this.updateStoredTokens(
|
|
98
|
-
access_token,
|
|
99
|
-
refreshToken, // Buffer doesn't rotate refresh tokens
|
|
100
|
-
expires_in
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
LogStatus('Buffer access token refreshed successfully');
|
|
104
|
-
} catch (error) {
|
|
105
|
-
LogError('Failed to refresh Buffer access token:', error);
|
|
106
|
-
throw new Error('Failed to refresh Buffer access token');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get Buffer profiles for the authenticated user
|
|
112
|
-
*/
|
|
113
|
-
protected async getProfiles(): Promise<any[]> {
|
|
114
|
-
try {
|
|
115
|
-
const response = await this.getAxiosInstance().get('/profiles.json');
|
|
116
|
-
return response.data || [];
|
|
117
|
-
} catch (error) {
|
|
118
|
-
LogError('Failed to get Buffer profiles:', error);
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Upload media to Buffer
|
|
125
|
-
*/
|
|
126
|
-
protected async uploadSingleMedia(file: MediaFile): Promise<string> {
|
|
127
|
-
try {
|
|
128
|
-
const formData = new FormData();
|
|
129
|
-
|
|
130
|
-
// Convert base64 to buffer if needed
|
|
131
|
-
const buffer = typeof file.data === 'string' ? Buffer.from(file.data, 'base64') : file.data;
|
|
132
|
-
|
|
133
|
-
formData.append('media', buffer, {
|
|
134
|
-
filename: file.filename,
|
|
135
|
-
contentType: file.mimeType,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const response = await this.getAxiosInstance().post('/updates/media/upload.json', formData, {
|
|
139
|
-
headers: {
|
|
140
|
-
...formData.getHeaders(),
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (response.data && response.data.media && response.data.media[0]) {
|
|
145
|
-
return response.data.media[0].picture; // URL of uploaded media
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
throw new Error('Failed to upload media to Buffer');
|
|
149
|
-
} catch (error) {
|
|
150
|
-
LogError('Buffer media upload failed:', error);
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Create a Buffer update (post)
|
|
157
|
-
*/
|
|
158
|
-
protected async createUpdate(
|
|
159
|
-
profileIds: string[],
|
|
160
|
-
text: string,
|
|
161
|
-
media?: { link?: string; description?: string; picture?: string }[],
|
|
162
|
-
scheduledAt?: Date,
|
|
163
|
-
options?: {
|
|
164
|
-
shorten?: boolean;
|
|
165
|
-
now?: boolean;
|
|
166
|
-
top?: boolean;
|
|
167
|
-
attachment?: boolean;
|
|
168
|
-
}
|
|
169
|
-
): Promise<any> {
|
|
170
|
-
const data: any = {
|
|
171
|
-
profile_ids: profileIds,
|
|
172
|
-
text: text,
|
|
173
|
-
shorten: options?.shorten !== false, // Default to true
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (media && media.length > 0) {
|
|
177
|
-
data.media = media;
|
|
18
|
+
protected get apiBaseUrl(): string {
|
|
19
|
+
return 'https://api.bufferapp.com/1';
|
|
178
20
|
}
|
|
179
21
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
22
|
+
private axiosInstance: AxiosInstance | null = null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get axios instance with authentication
|
|
26
|
+
*/
|
|
27
|
+
protected getAxiosInstance(): AxiosInstance {
|
|
28
|
+
if (!this.axiosInstance) {
|
|
29
|
+
this.axiosInstance = axios.create({
|
|
30
|
+
baseURL: this.apiBaseUrl,
|
|
31
|
+
timeout: 30000,
|
|
32
|
+
headers: {
|
|
33
|
+
'Accept': 'application/json',
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Add request interceptor for auth
|
|
39
|
+
this.axiosInstance.interceptors.request.use(
|
|
40
|
+
(config) => {
|
|
41
|
+
const token = this.getAccessToken();
|
|
42
|
+
if (token) {
|
|
43
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
},
|
|
47
|
+
(error) => {
|
|
48
|
+
return Promise.reject(error);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Add response interceptor for rate limiting
|
|
53
|
+
this.axiosInstance.interceptors.response.use(
|
|
54
|
+
(response) => {
|
|
55
|
+
// Log rate limit headers if present
|
|
56
|
+
const headers = response.headers;
|
|
57
|
+
const rateLimitInfo = this.parseRateLimitHeaders(headers);
|
|
58
|
+
if (rateLimitInfo) {
|
|
59
|
+
LogStatus(`Buffer API - Remaining requests: ${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 hit
|
|
66
|
+
const retryAfter = error.response.headers['retry-after'];
|
|
67
|
+
await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : 60);
|
|
68
|
+
// Retry the request
|
|
69
|
+
return this.axiosInstance!.request(error.config!);
|
|
70
|
+
}
|
|
71
|
+
return Promise.reject(error);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return this.axiosInstance;
|
|
184
76
|
}
|
|
185
77
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Refresh access token using refresh token
|
|
80
|
+
*/
|
|
81
|
+
protected async refreshAccessToken(): Promise<void> {
|
|
82
|
+
const refreshToken = this.getRefreshToken();
|
|
83
|
+
if (!refreshToken) {
|
|
84
|
+
throw new Error('No refresh token available for Buffer');
|
|
85
|
+
}
|
|
189
86
|
|
|
190
|
-
|
|
191
|
-
|
|
87
|
+
try {
|
|
88
|
+
const response = await axios.post(`${this.apiBaseUrl}/oauth2/token`, {
|
|
89
|
+
client_id: this.getCustomAttribute(1), // Store Buffer client ID in CustomAttribute1
|
|
90
|
+
client_secret: this.getCustomAttribute(2), // Store Buffer client secret in CustomAttribute2
|
|
91
|
+
refresh_token: refreshToken,
|
|
92
|
+
grant_type: 'refresh_token'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const { access_token, expires_in } = response.data;
|
|
96
|
+
|
|
97
|
+
await this.updateStoredTokens(
|
|
98
|
+
access_token,
|
|
99
|
+
refreshToken, // Buffer doesn't rotate refresh tokens
|
|
100
|
+
expires_in
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
LogStatus('Buffer access token refreshed successfully');
|
|
104
|
+
} catch (error) {
|
|
105
|
+
LogError('Failed to refresh Buffer access token:', error);
|
|
106
|
+
throw new Error('Failed to refresh Buffer access token');
|
|
107
|
+
}
|
|
192
108
|
}
|
|
193
109
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
*/
|
|
206
|
-
protected async getUpdates(
|
|
207
|
-
profileId: string,
|
|
208
|
-
status: 'pending' | 'sent',
|
|
209
|
-
options?: {
|
|
210
|
-
page?: number;
|
|
211
|
-
count?: number;
|
|
212
|
-
since?: Date;
|
|
213
|
-
utc?: boolean;
|
|
214
|
-
}
|
|
215
|
-
): Promise<any> {
|
|
216
|
-
const params: any = {
|
|
217
|
-
page: options?.page || 1,
|
|
218
|
-
count: options?.count || 10,
|
|
219
|
-
utc: options?.utc !== false, // Default to true
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
if (options?.since) {
|
|
223
|
-
params.since = Math.floor(options.since.getTime() / 1000);
|
|
110
|
+
/**
|
|
111
|
+
* Get Buffer profiles for the authenticated user
|
|
112
|
+
*/
|
|
113
|
+
protected async getProfiles(): Promise<any[]> {
|
|
114
|
+
try {
|
|
115
|
+
const response = await this.getAxiosInstance().get('/profiles.json');
|
|
116
|
+
return response.data || [];
|
|
117
|
+
} catch (error) {
|
|
118
|
+
LogError('Failed to get Buffer profiles:', error);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
224
121
|
}
|
|
225
122
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Upload media to Buffer
|
|
125
|
+
*/
|
|
126
|
+
protected async uploadSingleMedia(file: MediaFile): Promise<string> {
|
|
127
|
+
try {
|
|
128
|
+
const formData = new FormData();
|
|
129
|
+
|
|
130
|
+
// Convert base64 to buffer if needed
|
|
131
|
+
const buffer = typeof file.data === 'string'
|
|
132
|
+
? Buffer.from(file.data, 'base64')
|
|
133
|
+
: file.data;
|
|
134
|
+
|
|
135
|
+
formData.append('media', buffer, {
|
|
136
|
+
filename: file.filename,
|
|
137
|
+
contentType: file.mimeType
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const response = await this.getAxiosInstance().post('/updates/media/upload.json', formData, {
|
|
141
|
+
headers: {
|
|
142
|
+
...formData.getHeaders()
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (response.data && response.data.media && response.data.media[0]) {
|
|
147
|
+
return response.data.media[0].picture; // URL of uploaded media
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error('Failed to upload media to Buffer');
|
|
151
|
+
} catch (error) {
|
|
152
|
+
LogError('Buffer media upload failed:', error);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
232
155
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create a Buffer update (post)
|
|
159
|
+
*/
|
|
160
|
+
protected async createUpdate(
|
|
161
|
+
profileIds: string[],
|
|
162
|
+
text: string,
|
|
163
|
+
media?: { link?: string; description?: string; picture?: string }[],
|
|
164
|
+
scheduledAt?: Date,
|
|
165
|
+
options?: {
|
|
166
|
+
shorten?: boolean;
|
|
167
|
+
now?: boolean;
|
|
168
|
+
top?: boolean;
|
|
169
|
+
attachment?: boolean;
|
|
170
|
+
}
|
|
171
|
+
): Promise<any> {
|
|
172
|
+
const data: any = {
|
|
173
|
+
profile_ids: profileIds,
|
|
174
|
+
text: text,
|
|
175
|
+
shorten: options?.shorten !== false // Default to true
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (media && media.length > 0) {
|
|
179
|
+
data.media = media;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (scheduledAt) {
|
|
183
|
+
data.scheduled_at = Math.floor(scheduledAt.getTime() / 1000); // Unix timestamp
|
|
184
|
+
} else if (options?.now) {
|
|
185
|
+
data.now = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (options?.top) {
|
|
189
|
+
data.top = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options?.attachment !== undefined) {
|
|
193
|
+
data.attachment = options.attachment;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const response = await this.getAxiosInstance().post('/updates/create.json', data);
|
|
198
|
+
return response.data;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
LogError('Failed to create Buffer update:', error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
245
203
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get updates (posts) from Buffer
|
|
207
|
+
*/
|
|
208
|
+
protected async getUpdates(
|
|
209
|
+
profileId: string,
|
|
210
|
+
status: 'pending' | 'sent',
|
|
211
|
+
options?: {
|
|
212
|
+
page?: number;
|
|
213
|
+
count?: number;
|
|
214
|
+
since?: Date;
|
|
215
|
+
utc?: boolean;
|
|
216
|
+
}
|
|
217
|
+
): Promise<any> {
|
|
218
|
+
const params: any = {
|
|
219
|
+
page: options?.page || 1,
|
|
220
|
+
count: options?.count || 10,
|
|
221
|
+
utc: options?.utc !== false // Default to true
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (options?.since) {
|
|
225
|
+
params.since = Math.floor(options.since.getTime() / 1000);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const response = await this.getAxiosInstance().get(
|
|
230
|
+
`/profiles/${profileId}/updates/${status}.json`,
|
|
231
|
+
{ params }
|
|
232
|
+
);
|
|
233
|
+
return response.data;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
LogError(`Failed to get ${status} updates from Buffer:`, error);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
258
238
|
}
|
|
259
239
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Delete a Buffer update
|
|
242
|
+
*/
|
|
243
|
+
protected async deleteUpdate(updateId: string): Promise<boolean> {
|
|
244
|
+
try {
|
|
245
|
+
const response = await this.getAxiosInstance().post(`/updates/${updateId}/destroy.json`);
|
|
246
|
+
return response.data.success === true;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
LogError('Failed to delete Buffer update:', error);
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
266
251
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Reorder updates in the queue
|
|
255
|
+
*/
|
|
256
|
+
protected async reorderUpdates(profileId: string, updateIds: string[], offset?: number): Promise<any> {
|
|
257
|
+
const data: any = {
|
|
258
|
+
order: updateIds
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (offset !== undefined) {
|
|
262
|
+
data.offset = offset;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const response = await this.getAxiosInstance().post(
|
|
267
|
+
`/profiles/${profileId}/updates/reorder.json`,
|
|
268
|
+
data
|
|
269
|
+
);
|
|
270
|
+
return response.data;
|
|
271
|
+
} catch (error) {
|
|
272
|
+
LogError('Failed to reorder Buffer updates:', error);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
279
275
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
offset?: number;
|
|
293
|
-
profileIds?: string[];
|
|
294
|
-
}): Promise<SocialPost[]> {
|
|
295
|
-
const posts: SocialPost[] = [];
|
|
296
|
-
const profileIds = params.profileIds || [];
|
|
297
|
-
|
|
298
|
-
// If no profile IDs provided, get all profiles
|
|
299
|
-
if (profileIds.length === 0) {
|
|
300
|
-
const profiles = await this.getProfiles();
|
|
301
|
-
profileIds.push(...profiles.map((p) => p.id));
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get analytics for sent posts
|
|
279
|
+
*/
|
|
280
|
+
protected async getAnalytics(updateId: string): Promise<any> {
|
|
281
|
+
try {
|
|
282
|
+
const response = await this.getAxiosInstance().get(`/updates/${updateId}/interactions.json`);
|
|
283
|
+
return response.data;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
LogError('Failed to get Buffer analytics:', error);
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
302
288
|
}
|
|
303
289
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Search posts implementation for Buffer
|
|
292
|
+
* Buffer doesn't have a native search API, so we'll fetch posts and filter client-side
|
|
293
|
+
*/
|
|
294
|
+
protected async searchPosts(params: {
|
|
295
|
+
query?: string;
|
|
296
|
+
hashtags?: string[];
|
|
297
|
+
startDate?: Date;
|
|
298
|
+
endDate?: Date;
|
|
299
|
+
limit?: number;
|
|
300
|
+
offset?: number;
|
|
301
|
+
profileIds?: string[];
|
|
302
|
+
}): Promise<SocialPost[]> {
|
|
303
|
+
const posts: SocialPost[] = [];
|
|
304
|
+
const profileIds = params.profileIds || [];
|
|
305
|
+
|
|
306
|
+
// If no profile IDs provided, get all profiles
|
|
307
|
+
if (profileIds.length === 0) {
|
|
308
|
+
const profiles = await this.getProfiles();
|
|
309
|
+
profileIds.push(...profiles.map(p => p.id));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Fetch sent posts from each profile
|
|
313
|
+
for (const profileId of profileIds) {
|
|
314
|
+
try {
|
|
315
|
+
let page = 1;
|
|
316
|
+
let hasMore = true;
|
|
317
|
+
|
|
318
|
+
while (hasMore && posts.length < (params.limit || 100)) {
|
|
319
|
+
const result = await this.getUpdates(profileId, 'sent', {
|
|
320
|
+
page: page,
|
|
321
|
+
count: 100,
|
|
322
|
+
since: params.startDate
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (result.updates && result.updates.length > 0) {
|
|
326
|
+
for (const update of result.updates) {
|
|
327
|
+
const post = this.normalizePost(update);
|
|
328
|
+
|
|
329
|
+
// Apply filters
|
|
330
|
+
if (this.matchesSearchCriteria(post, params)) {
|
|
331
|
+
posts.push(post);
|
|
332
|
+
|
|
333
|
+
if (posts.length >= (params.limit || 100)) {
|
|
334
|
+
hasMore = false;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
page++;
|
|
341
|
+
hasMore = result.updates.length === 100;
|
|
342
|
+
} else {
|
|
343
|
+
hasMore = false;
|
|
344
|
+
}
|
|
328
345
|
}
|
|
329
|
-
|
|
346
|
+
} catch (error) {
|
|
347
|
+
LogError(`Failed to search posts for profile ${profileId}:`, error);
|
|
330
348
|
}
|
|
331
|
-
|
|
332
|
-
page++;
|
|
333
|
-
hasMore = result.updates.length === 100;
|
|
334
|
-
} else {
|
|
335
|
-
hasMore = false;
|
|
336
|
-
}
|
|
337
349
|
}
|
|
338
|
-
} catch (error) {
|
|
339
|
-
LogError(`Failed to search posts for profile ${profileId}:`, error);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
350
|
|
|
343
|
-
|
|
344
|
-
|
|
351
|
+
// Sort by published date descending
|
|
352
|
+
posts.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
|
|
353
|
+
|
|
354
|
+
// Apply offset if specified
|
|
355
|
+
if (params.offset) {
|
|
356
|
+
return posts.slice(params.offset, params.offset + (params.limit || 100));
|
|
357
|
+
}
|
|
345
358
|
|
|
346
|
-
|
|
347
|
-
if (params.offset) {
|
|
348
|
-
return posts.slice(params.offset, params.offset + (params.limit || 100));
|
|
359
|
+
return posts.slice(0, params.limit || 100);
|
|
349
360
|
}
|
|
350
361
|
|
|
351
|
-
|
|
352
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Check if a post matches search criteria
|
|
364
|
+
*/
|
|
365
|
+
private matchesSearchCriteria(post: SocialPost, params: any): boolean {
|
|
366
|
+
// Check date range
|
|
367
|
+
if (params.endDate && post.publishedAt > params.endDate) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
353
370
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
// Check query text
|
|
372
|
+
if (params.query) {
|
|
373
|
+
const query = params.query.toLowerCase();
|
|
374
|
+
const content = post.content.toLowerCase();
|
|
375
|
+
if (!content.includes(query)) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
362
379
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
380
|
+
// Check hashtags
|
|
381
|
+
if (params.hashtags && params.hashtags.length > 0) {
|
|
382
|
+
const postHashtags = this.extractHashtags(post.content);
|
|
383
|
+
const hasMatchingHashtag = params.hashtags.some(tag =>
|
|
384
|
+
postHashtags.includes(tag.toLowerCase().replace('#', ''))
|
|
385
|
+
);
|
|
386
|
+
if (!hasMatchingHashtag) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
371
390
|
|
|
372
|
-
|
|
373
|
-
if (params.hashtags && params.hashtags.length > 0) {
|
|
374
|
-
const postHashtags = this.extractHashtags(post.content);
|
|
375
|
-
const hasMatchingHashtag = params.hashtags.some((tag) => postHashtags.includes(tag.toLowerCase().replace('#', '')));
|
|
376
|
-
if (!hasMatchingHashtag) {
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
391
|
+
return true;
|
|
379
392
|
}
|
|
380
393
|
|
|
381
|
-
|
|
382
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Extract hashtags from post content
|
|
396
|
+
*/
|
|
397
|
+
protected extractHashtags(content: string): string[] {
|
|
398
|
+
const regex = /#(\w+)/g;
|
|
399
|
+
const hashtags: string[] = [];
|
|
400
|
+
let match;
|
|
401
|
+
|
|
402
|
+
while ((match = regex.exec(content)) !== null) {
|
|
403
|
+
hashtags.push(match[1].toLowerCase());
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return hashtags;
|
|
407
|
+
}
|
|
383
408
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Normalize Buffer post to common format
|
|
411
|
+
*/
|
|
412
|
+
protected normalizePost(bufferPost: any): SocialPost {
|
|
413
|
+
const media: string[] = [];
|
|
414
|
+
|
|
415
|
+
if (bufferPost.media) {
|
|
416
|
+
if (bufferPost.media.picture) {
|
|
417
|
+
media.push(bufferPost.media.picture);
|
|
418
|
+
}
|
|
419
|
+
if (bufferPost.media.link) {
|
|
420
|
+
media.push(bufferPost.media.link);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
391
423
|
|
|
392
|
-
|
|
393
|
-
|
|
424
|
+
return {
|
|
425
|
+
id: bufferPost.id,
|
|
426
|
+
platform: 'Buffer',
|
|
427
|
+
profileId: bufferPost.profile_id,
|
|
428
|
+
content: bufferPost.text || '',
|
|
429
|
+
mediaUrls: media,
|
|
430
|
+
publishedAt: new Date(bufferPost.sent_at * 1000), // Convert Unix timestamp
|
|
431
|
+
scheduledFor: bufferPost.due_at ? new Date(bufferPost.due_at * 1000) : undefined,
|
|
432
|
+
analytics: bufferPost.statistics ? this.normalizeAnalytics(bufferPost.statistics) : undefined,
|
|
433
|
+
platformSpecificData: {
|
|
434
|
+
profileService: bufferPost.profile_service,
|
|
435
|
+
status: bufferPost.status,
|
|
436
|
+
userId: bufferPost.user_id,
|
|
437
|
+
viaName: bufferPost.via,
|
|
438
|
+
sourceUrl: bufferPost.source_url,
|
|
439
|
+
day: bufferPost.day,
|
|
440
|
+
dueTime: bufferPost.due_time,
|
|
441
|
+
mediaDescription: bufferPost.media?.description,
|
|
442
|
+
mediaTitle: bufferPost.media?.title,
|
|
443
|
+
mediaLink: bufferPost.media?.link
|
|
444
|
+
}
|
|
445
|
+
};
|
|
394
446
|
}
|
|
395
447
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
448
|
+
/**
|
|
449
|
+
* Normalize Buffer analytics to common format
|
|
450
|
+
*/
|
|
451
|
+
protected normalizeAnalytics(bufferStats: any): SocialAnalytics {
|
|
452
|
+
return {
|
|
453
|
+
impressions: bufferStats.reach || 0,
|
|
454
|
+
engagements: (bufferStats.clicks || 0) + (bufferStats.favorites || 0) +
|
|
455
|
+
(bufferStats.mentions || 0) + (bufferStats.retweets || 0) +
|
|
456
|
+
(bufferStats.shares || 0) + (bufferStats.comments || 0),
|
|
457
|
+
clicks: bufferStats.clicks || 0,
|
|
458
|
+
shares: bufferStats.shares || bufferStats.retweets || 0,
|
|
459
|
+
comments: bufferStats.comments || bufferStats.mentions || 0,
|
|
460
|
+
likes: bufferStats.favorites || bufferStats.likes || 0,
|
|
461
|
+
reach: bufferStats.reach || 0,
|
|
462
|
+
saves: undefined,
|
|
463
|
+
videoViews: undefined,
|
|
464
|
+
platformMetrics: bufferStats
|
|
465
|
+
};
|
|
412
466
|
}
|
|
413
467
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Normalize Buffer analytics to common format
|
|
440
|
-
*/
|
|
441
|
-
protected normalizeAnalytics(bufferStats: any): SocialAnalytics {
|
|
442
|
-
return {
|
|
443
|
-
impressions: bufferStats.reach || 0,
|
|
444
|
-
engagements:
|
|
445
|
-
(bufferStats.clicks || 0) +
|
|
446
|
-
(bufferStats.favorites || 0) +
|
|
447
|
-
(bufferStats.mentions || 0) +
|
|
448
|
-
(bufferStats.retweets || 0) +
|
|
449
|
-
(bufferStats.shares || 0) +
|
|
450
|
-
(bufferStats.comments || 0),
|
|
451
|
-
clicks: bufferStats.clicks || 0,
|
|
452
|
-
shares: bufferStats.shares || bufferStats.retweets || 0,
|
|
453
|
-
comments: bufferStats.comments || bufferStats.mentions || 0,
|
|
454
|
-
likes: bufferStats.favorites || bufferStats.likes || 0,
|
|
455
|
-
reach: bufferStats.reach || 0,
|
|
456
|
-
saves: undefined,
|
|
457
|
-
videoViews: undefined,
|
|
458
|
-
platformMetrics: bufferStats,
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Map Buffer error to our error codes
|
|
464
|
-
*/
|
|
465
|
-
protected mapBufferError(error: any): string {
|
|
466
|
-
if (error.response) {
|
|
467
|
-
const status = error.response.status;
|
|
468
|
-
const data = error.response.data;
|
|
469
|
-
|
|
470
|
-
if (status === 401) {
|
|
471
|
-
return 'INVALID_TOKEN';
|
|
472
|
-
} else if (status === 429) {
|
|
473
|
-
return 'RATE_LIMIT';
|
|
474
|
-
} else if (status === 403) {
|
|
475
|
-
return 'INSUFFICIENT_PERMISSIONS';
|
|
476
|
-
} else if (status === 404) {
|
|
477
|
-
return 'POST_NOT_FOUND';
|
|
478
|
-
} else if (data?.error?.includes('media')) {
|
|
479
|
-
return 'INVALID_MEDIA';
|
|
480
|
-
}
|
|
481
|
-
}
|
|
468
|
+
/**
|
|
469
|
+
* Map Buffer error to our error codes
|
|
470
|
+
*/
|
|
471
|
+
protected mapBufferError(error: any): string {
|
|
472
|
+
if (error.response) {
|
|
473
|
+
const status = error.response.status;
|
|
474
|
+
const data = error.response.data;
|
|
475
|
+
|
|
476
|
+
if (status === 401) {
|
|
477
|
+
return 'INVALID_TOKEN';
|
|
478
|
+
} else if (status === 429) {
|
|
479
|
+
return 'RATE_LIMIT';
|
|
480
|
+
} else if (status === 403) {
|
|
481
|
+
return 'INSUFFICIENT_PERMISSIONS';
|
|
482
|
+
} else if (status === 404) {
|
|
483
|
+
return 'POST_NOT_FOUND';
|
|
484
|
+
} else if (data?.error?.includes('media')) {
|
|
485
|
+
return 'INVALID_MEDIA';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
482
488
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
489
|
+
return 'PLATFORM_ERROR';
|
|
490
|
+
}
|
|
491
|
+
}
|