@memberjunction/actions-bizapps-social 2.111.1 → 2.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -6
- package/dist/base/base-social.action.d.ts.map +1 -1
- package/dist/base/base-social.action.js +18 -24
- 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 +35 -34
- 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 +34 -36
- 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 +25 -27
- 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 +19 -23
- 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 +28 -32
- 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 +42 -44
- 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 +37 -39
- 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 +44 -59
- 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 +33 -31
- 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 +28 -32
- 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 +24 -26
- 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 +32 -34
- 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 +43 -52
- 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 +30 -28
- 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 +18 -20
- 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 +27 -26
- 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 +38 -59
- 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 +23 -25
- 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 +56 -60
- 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 +25 -27
- 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 +55 -45
- 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 +31 -29
- 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 +25 -23
- 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 +32 -30
- 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 +28 -30
- 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 +33 -38
- 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 +25 -26
- 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 +25 -29
- 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 +40 -47
- 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 +30 -31
- 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 +56 -58
- 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 +58 -68
- 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 +22 -25
- package/dist/providers/youtube/youtube-base.action.js.map +1 -1
- package/package.json +5 -6
- package/src/base/base-social.action.ts +217 -224
- package/src/providers/buffer/buffer-base.action.ts +435 -441
- package/src/providers/facebook/actions/boost-post.action.ts +350 -386
- package/src/providers/facebook/actions/create-album.action.ts +291 -307
- package/src/providers/facebook/actions/create-post.action.ts +224 -227
- package/src/providers/facebook/actions/get-page-insights.action.ts +383 -403
- package/src/providers/facebook/actions/get-page-posts.action.ts +214 -225
- package/src/providers/facebook/actions/get-post-insights.action.ts +300 -316
- package/src/providers/facebook/actions/respond-to-comments.action.ts +319 -336
- package/src/providers/facebook/actions/schedule-post.action.ts +289 -292
- package/src/providers/facebook/actions/search-posts.action.ts +399 -413
- package/src/providers/facebook/facebook-base.action.ts +653 -670
- package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
- package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +184 -189
- package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +160 -161
- package/src/providers/hootsuite/actions/get-analytics.action.ts +249 -254
- package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +206 -207
- package/src/providers/hootsuite/actions/get-social-profiles.action.ts +206 -205
- package/src/providers/hootsuite/actions/search-posts.action.ts +351 -369
- package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +211 -209
- package/src/providers/hootsuite/hootsuite-base.action.ts +301 -307
- package/src/providers/instagram/actions/create-post.action.ts +276 -296
- package/src/providers/instagram/actions/create-story.action.ts +378 -394
- package/src/providers/instagram/actions/get-account-insights.action.ts +384 -420
- package/src/providers/instagram/actions/get-business-posts.action.ts +233 -242
- package/src/providers/instagram/actions/get-comments.action.ts +365 -377
- package/src/providers/instagram/actions/get-post-insights.action.ts +265 -273
- package/src/providers/instagram/actions/schedule-post.action.ts +233 -235
- package/src/providers/instagram/actions/search-posts.action.ts +512 -538
- package/src/providers/instagram/instagram-base.action.ts +368 -393
- package/src/providers/linkedin/actions/create-article.action.ts +275 -266
- package/src/providers/linkedin/actions/create-post.action.ts +179 -177
- package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
- package/src/providers/linkedin/actions/get-organization-posts.action.ts +146 -147
- package/src/providers/linkedin/actions/get-personal-posts.action.ts +138 -139
- package/src/providers/linkedin/actions/get-post-analytics.action.ts +190 -189
- package/src/providers/linkedin/actions/schedule-post.action.ts +191 -189
- package/src/providers/linkedin/actions/search-posts.action.ts +275 -283
- package/src/providers/linkedin/linkedin-base.action.ts +407 -421
- package/src/providers/tiktok/tiktok-base.action.ts +305 -320
- package/src/providers/twitter/actions/create-thread.action.ts +203 -207
- package/src/providers/twitter/actions/create-tweet.action.ts +187 -188
- package/src/providers/twitter/actions/delete-tweet.action.ts +128 -129
- package/src/providers/twitter/actions/get-analytics.action.ts +402 -411
- package/src/providers/twitter/actions/get-mentions.action.ts +218 -219
- package/src/providers/twitter/actions/get-timeline.action.ts +232 -233
- package/src/providers/twitter/actions/schedule-tweet.action.ts +221 -222
- package/src/providers/twitter/actions/search-tweets.action.ts +540 -543
- package/src/providers/twitter/twitter-base.action.ts +541 -560
- package/src/providers/youtube/youtube-base.action.ts +320 -333
|
@@ -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 { LogError, LogStatus } from '@memberjunction/
|
|
3
|
+
import { LogError, LogStatus } from '@memberjunction/global';
|
|
4
4
|
import axios, { AxiosInstance, AxiosError } from 'axios';
|
|
5
5
|
import { BaseAction } from '@memberjunction/actions';
|
|
6
6
|
|
|
@@ -8,33 +8,33 @@ import { BaseAction } from '@memberjunction/actions';
|
|
|
8
8
|
* TikTok video information
|
|
9
9
|
*/
|
|
10
10
|
export interface TikTokVideo {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
id: string;
|
|
12
|
+
share_url: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
duration: number;
|
|
16
|
+
cover_image_url: string;
|
|
17
|
+
share_count: number;
|
|
18
|
+
view_count: number;
|
|
19
|
+
like_count: number;
|
|
20
|
+
comment_count: number;
|
|
21
|
+
create_time: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* TikTok user information
|
|
26
26
|
*/
|
|
27
27
|
export interface TikTokUser {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
open_id: string;
|
|
29
|
+
union_id: string;
|
|
30
|
+
avatar_url: string;
|
|
31
|
+
display_name: string;
|
|
32
|
+
bio_description: string;
|
|
33
|
+
profile_deep_link: string;
|
|
34
|
+
is_verified: boolean;
|
|
35
|
+
follower_count: number;
|
|
36
|
+
following_count: number;
|
|
37
|
+
likes_count: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -43,315 +43,300 @@ export interface TikTokUser {
|
|
|
43
43
|
*/
|
|
44
44
|
@RegisterClass(BaseAction, 'TikTokBaseAction')
|
|
45
45
|
export abstract class TikTokBaseAction extends BaseSocialMediaAction {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
protected get platformName(): string {
|
|
47
|
+
return 'TikTok';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected get apiBaseUrl(): string {
|
|
51
|
+
return 'https://open-api.tiktok.com';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private axiosInstance: AxiosInstance | null = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initialize axios instance with interceptors
|
|
58
|
+
*/
|
|
59
|
+
protected getAxiosInstance(): AxiosInstance {
|
|
60
|
+
if (!this.axiosInstance) {
|
|
61
|
+
this.axiosInstance = axios.create({
|
|
62
|
+
baseURL: this.apiBaseUrl,
|
|
63
|
+
timeout: 30000,
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
Accept: 'application/json',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Request interceptor for logging
|
|
71
|
+
this.axiosInstance.interceptors.request.use(
|
|
72
|
+
(config) => {
|
|
73
|
+
this.logApiRequest(config.method?.toUpperCase() || 'GET', config.url || '', config.data);
|
|
74
|
+
return config;
|
|
75
|
+
},
|
|
76
|
+
(error) => {
|
|
77
|
+
LogError(`TikTok API Request Error: ${error.message}`);
|
|
78
|
+
return Promise.reject(error);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Response interceptor for logging and error handling
|
|
83
|
+
this.axiosInstance.interceptors.response.use(
|
|
84
|
+
(response) => {
|
|
85
|
+
this.logApiResponse(response.data);
|
|
86
|
+
return response;
|
|
87
|
+
},
|
|
88
|
+
async (error: AxiosError) => {
|
|
89
|
+
if (error.response?.status === 429) {
|
|
90
|
+
// Rate limit hit
|
|
91
|
+
const retryAfter = error.response.headers['retry-after'];
|
|
92
|
+
await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : undefined);
|
|
93
|
+
|
|
94
|
+
// Retry the request
|
|
95
|
+
return this.axiosInstance?.request(error.config!);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
LogError(`TikTok API Response Error: ${error.message}`);
|
|
99
|
+
return Promise.reject(error);
|
|
100
|
+
}
|
|
101
|
+
);
|
|
49
102
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
103
|
+
|
|
104
|
+
return this.axiosInstance;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Make authenticated TikTok API request
|
|
109
|
+
*/
|
|
110
|
+
protected async makeTikTokRequest<T>(
|
|
111
|
+
endpoint: string,
|
|
112
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
|
113
|
+
data?: any,
|
|
114
|
+
params?: any
|
|
115
|
+
): Promise<T> {
|
|
116
|
+
const token = this.getAccessToken();
|
|
117
|
+
if (!token) {
|
|
118
|
+
throw new Error('No access token available for TikTok');
|
|
53
119
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
async (error: AxiosError) => {
|
|
90
|
-
if (error.response?.status === 429) {
|
|
91
|
-
// Rate limit hit
|
|
92
|
-
const retryAfter = error.response.headers['retry-after'];
|
|
93
|
-
await this.handleRateLimit(retryAfter ? parseInt(retryAfter) : undefined);
|
|
94
|
-
|
|
95
|
-
// Retry the request
|
|
96
|
-
return this.axiosInstance?.request(error.config!);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
LogError(`TikTok API Response Error: ${error.message}`);
|
|
100
|
-
return Promise.reject(error);
|
|
101
|
-
}
|
|
102
|
-
);
|
|
120
|
+
|
|
121
|
+
const axios = this.getAxiosInstance();
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const response = await axios.request<T>({
|
|
125
|
+
method,
|
|
126
|
+
url: endpoint,
|
|
127
|
+
data,
|
|
128
|
+
params,
|
|
129
|
+
headers: {
|
|
130
|
+
Authorization: `Bearer ${token}`,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return response.data;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof AxiosError) {
|
|
137
|
+
if (error.response?.status === 401) {
|
|
138
|
+
// Token might be expired, try to refresh
|
|
139
|
+
await this.refreshAccessToken();
|
|
140
|
+
|
|
141
|
+
// Retry with new token
|
|
142
|
+
const newToken = this.getAccessToken();
|
|
143
|
+
const retryResponse = await axios.request<T>({
|
|
144
|
+
method,
|
|
145
|
+
url: endpoint,
|
|
146
|
+
data,
|
|
147
|
+
params,
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: `Bearer ${newToken}`,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return retryResponse.data;
|
|
103
154
|
}
|
|
104
|
-
|
|
105
|
-
|
|
155
|
+
|
|
156
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
157
|
+
throw new Error(`TikTok API error: ${errorMessage}`);
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
106
160
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
): Promise<T> {
|
|
117
|
-
const token = this.getAccessToken();
|
|
118
|
-
if (!token) {
|
|
119
|
-
throw new Error('No access token available for TikTok');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const axios = this.getAxiosInstance();
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const response = await axios.request<T>({
|
|
126
|
-
method,
|
|
127
|
-
url: endpoint,
|
|
128
|
-
data,
|
|
129
|
-
params,
|
|
130
|
-
headers: {
|
|
131
|
-
'Authorization': `Bearer ${token}`
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return response.data;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if (error instanceof AxiosError) {
|
|
138
|
-
if (error.response?.status === 401) {
|
|
139
|
-
// Token might be expired, try to refresh
|
|
140
|
-
await this.refreshAccessToken();
|
|
141
|
-
|
|
142
|
-
// Retry with new token
|
|
143
|
-
const newToken = this.getAccessToken();
|
|
144
|
-
const retryResponse = await axios.request<T>({
|
|
145
|
-
method,
|
|
146
|
-
url: endpoint,
|
|
147
|
-
data,
|
|
148
|
-
params,
|
|
149
|
-
headers: {
|
|
150
|
-
'Authorization': `Bearer ${newToken}`
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return retryResponse.data;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
158
|
-
throw new Error(`TikTok API error: ${errorMessage}`);
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Refresh TikTok access token
|
|
165
|
+
*/
|
|
166
|
+
protected async refreshAccessToken(): Promise<void> {
|
|
167
|
+
const refreshToken = this.getRefreshToken();
|
|
168
|
+
if (!refreshToken) {
|
|
169
|
+
throw new Error('No refresh token available for TikTok');
|
|
162
170
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const { access_token, refresh_token: newRefreshToken, expires_in } = response.data.data;
|
|
181
|
-
|
|
182
|
-
// Update stored tokens
|
|
183
|
-
await this.updateStoredTokens(access_token, newRefreshToken, expires_in);
|
|
184
|
-
|
|
185
|
-
LogStatus('TikTok access token refreshed successfully');
|
|
186
|
-
} catch (error) {
|
|
187
|
-
LogError(`Failed to refresh TikTok access token: ${error}`);
|
|
188
|
-
throw new Error('Failed to refresh TikTok access token');
|
|
189
|
-
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const response = await axios.post(`${this.apiBaseUrl}/oauth/refresh_token/`, {
|
|
174
|
+
client_key: this.getCustomAttribute(2), // Store client key in CustomAttribute2
|
|
175
|
+
grant_type: 'refresh_token',
|
|
176
|
+
refresh_token: refreshToken,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const { access_token, refresh_token: newRefreshToken, expires_in } = response.data.data;
|
|
180
|
+
|
|
181
|
+
// Update stored tokens
|
|
182
|
+
await this.updateStoredTokens(access_token, newRefreshToken, expires_in);
|
|
183
|
+
|
|
184
|
+
LogStatus('TikTok access token refreshed successfully');
|
|
185
|
+
} catch (error) {
|
|
186
|
+
LogError(`Failed to refresh TikTok access token: ${error}`);
|
|
187
|
+
throw new Error('Failed to refresh TikTok access token');
|
|
190
188
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Upload media to TikTok (requires special approval)
|
|
193
|
+
*/
|
|
194
|
+
protected async uploadSingleMedia(file: MediaFile): Promise<string> {
|
|
195
|
+
// TikTok video upload requires special approval
|
|
196
|
+
// This is a placeholder implementation
|
|
197
|
+
throw new Error('TikTok video upload requires special API approval. Please use TikTok Creator Studio for video uploads.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Search posts (videos) - TikTok only allows searching user's own videos
|
|
202
|
+
*/
|
|
203
|
+
protected async searchPosts(params: any): Promise<SocialPost[]> {
|
|
204
|
+
// TikTok doesn't provide a general search API for security/privacy reasons
|
|
205
|
+
// We can only search within a user's own videos
|
|
206
|
+
const videos = await this.getUserVideos();
|
|
207
|
+
|
|
208
|
+
let filtered = videos;
|
|
209
|
+
|
|
210
|
+
// Apply filters if provided
|
|
211
|
+
if (params.query) {
|
|
212
|
+
const query = params.query.toLowerCase();
|
|
213
|
+
filtered = filtered.filter((video) => video.title.toLowerCase().includes(query) || video.description.toLowerCase().includes(query));
|
|
199
214
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// We can only search within a user's own videos
|
|
207
|
-
const videos = await this.getUserVideos();
|
|
208
|
-
|
|
209
|
-
let filtered = videos;
|
|
210
|
-
|
|
211
|
-
// Apply filters if provided
|
|
212
|
-
if (params.query) {
|
|
213
|
-
const query = params.query.toLowerCase();
|
|
214
|
-
filtered = filtered.filter(video =>
|
|
215
|
-
video.title.toLowerCase().includes(query) ||
|
|
216
|
-
video.description.toLowerCase().includes(query)
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (params.hashtags && params.hashtags.length > 0) {
|
|
221
|
-
filtered = filtered.filter(video => {
|
|
222
|
-
const videoHashtags = this.extractHashtags(video.description);
|
|
223
|
-
return params.hashtags.some((tag: string) =>
|
|
224
|
-
videoHashtags.includes(tag.toLowerCase())
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (params.startDate) {
|
|
230
|
-
const startTime = new Date(params.startDate).getTime() / 1000;
|
|
231
|
-
filtered = filtered.filter(video => video.create_time >= startTime);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (params.endDate) {
|
|
235
|
-
const endTime = new Date(params.endDate).getTime() / 1000;
|
|
236
|
-
filtered = filtered.filter(video => video.create_time <= endTime);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Apply limit and offset
|
|
240
|
-
if (params.offset) {
|
|
241
|
-
filtered = filtered.slice(params.offset);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (params.limit) {
|
|
245
|
-
filtered = filtered.slice(0, params.limit);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return filtered.map(video => this.normalizePost(video));
|
|
215
|
+
|
|
216
|
+
if (params.hashtags && params.hashtags.length > 0) {
|
|
217
|
+
filtered = filtered.filter((video) => {
|
|
218
|
+
const videoHashtags = this.extractHashtags(video.description);
|
|
219
|
+
return params.hashtags.some((tag: string) => videoHashtags.includes(tag.toLowerCase()));
|
|
220
|
+
});
|
|
249
221
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
protected async getUserVideos(): Promise<TikTokVideo[]> {
|
|
255
|
-
const response = await this.makeTikTokRequest<any>(
|
|
256
|
-
'/v2/video/list/',
|
|
257
|
-
'GET',
|
|
258
|
-
undefined,
|
|
259
|
-
{
|
|
260
|
-
fields: 'id,share_url,title,description,duration,cover_image_url,share_count,view_count,like_count,comment_count,create_time'
|
|
261
|
-
}
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
return response.data?.videos || [];
|
|
222
|
+
|
|
223
|
+
if (params.startDate) {
|
|
224
|
+
const startTime = new Date(params.startDate).getTime() / 1000;
|
|
225
|
+
filtered = filtered.filter((video) => video.create_time >= startTime);
|
|
265
226
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
protected normalizePost(video: TikTokVideo): SocialPost {
|
|
271
|
-
return {
|
|
272
|
-
id: video.id,
|
|
273
|
-
platform: this.platformName,
|
|
274
|
-
profileId: this.getCustomAttribute(1) || '', // Store user ID in CustomAttribute1
|
|
275
|
-
content: video.description || video.title,
|
|
276
|
-
mediaUrls: [video.cover_image_url],
|
|
277
|
-
publishedAt: new Date(video.create_time * 1000),
|
|
278
|
-
analytics: {
|
|
279
|
-
impressions: video.view_count,
|
|
280
|
-
engagements: video.like_count + video.comment_count + video.share_count,
|
|
281
|
-
clicks: 0, // Not available in TikTok API
|
|
282
|
-
shares: video.share_count,
|
|
283
|
-
comments: video.comment_count,
|
|
284
|
-
likes: video.like_count,
|
|
285
|
-
reach: video.view_count,
|
|
286
|
-
videoViews: video.view_count,
|
|
287
|
-
platformMetrics: {
|
|
288
|
-
duration: video.duration,
|
|
289
|
-
shareUrl: video.share_url
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
platformSpecificData: {
|
|
293
|
-
...video,
|
|
294
|
-
videoUrl: video.share_url
|
|
295
|
-
}
|
|
296
|
-
};
|
|
227
|
+
|
|
228
|
+
if (params.endDate) {
|
|
229
|
+
const endTime = new Date(params.endDate).getTime() / 1000;
|
|
230
|
+
filtered = filtered.filter((video) => video.create_time <= endTime);
|
|
297
231
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
protected normalizeAnalytics(platformData: any): SocialAnalytics {
|
|
303
|
-
return {
|
|
304
|
-
impressions: platformData.view_count || 0,
|
|
305
|
-
engagements: (platformData.like_count || 0) + (platformData.comment_count || 0) + (platformData.share_count || 0),
|
|
306
|
-
clicks: 0, // Not available in TikTok API
|
|
307
|
-
shares: platformData.share_count || 0,
|
|
308
|
-
comments: platformData.comment_count || 0,
|
|
309
|
-
likes: platformData.like_count || 0,
|
|
310
|
-
reach: platformData.view_count || 0,
|
|
311
|
-
videoViews: platformData.view_count || 0,
|
|
312
|
-
platformMetrics: platformData
|
|
313
|
-
};
|
|
232
|
+
|
|
233
|
+
// Apply limit and offset
|
|
234
|
+
if (params.offset) {
|
|
235
|
+
filtered = filtered.slice(params.offset);
|
|
314
236
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
*/
|
|
319
|
-
protected extractHashtags(description: string): string[] {
|
|
320
|
-
const hashtagRegex = /#(\w+)/g;
|
|
321
|
-
const matches = description.match(hashtagRegex) || [];
|
|
322
|
-
return matches.map(tag => tag.substring(1).toLowerCase());
|
|
237
|
+
|
|
238
|
+
if (params.limit) {
|
|
239
|
+
filtered = filtered.slice(0, params.limit);
|
|
323
240
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
241
|
+
|
|
242
|
+
return filtered.map((video) => this.normalizePost(video));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get user's videos
|
|
247
|
+
*/
|
|
248
|
+
protected async getUserVideos(): Promise<TikTokVideo[]> {
|
|
249
|
+
const response = await this.makeTikTokRequest<any>('/v2/video/list/', 'GET', undefined, {
|
|
250
|
+
fields: 'id,share_url,title,description,duration,cover_image_url,share_count,view_count,like_count,comment_count,create_time',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return response.data?.videos || [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Convert TikTok video to common post format
|
|
258
|
+
*/
|
|
259
|
+
protected normalizePost(video: TikTokVideo): SocialPost {
|
|
260
|
+
return {
|
|
261
|
+
id: video.id,
|
|
262
|
+
platform: this.platformName,
|
|
263
|
+
profileId: this.getCustomAttribute(1) || '', // Store user ID in CustomAttribute1
|
|
264
|
+
content: video.description || video.title,
|
|
265
|
+
mediaUrls: [video.cover_image_url],
|
|
266
|
+
publishedAt: new Date(video.create_time * 1000),
|
|
267
|
+
analytics: {
|
|
268
|
+
impressions: video.view_count,
|
|
269
|
+
engagements: video.like_count + video.comment_count + video.share_count,
|
|
270
|
+
clicks: 0, // Not available in TikTok API
|
|
271
|
+
shares: video.share_count,
|
|
272
|
+
comments: video.comment_count,
|
|
273
|
+
likes: video.like_count,
|
|
274
|
+
reach: video.view_count,
|
|
275
|
+
videoViews: video.view_count,
|
|
276
|
+
platformMetrics: {
|
|
277
|
+
duration: video.duration,
|
|
278
|
+
shareUrl: video.share_url,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
platformSpecificData: {
|
|
282
|
+
...video,
|
|
283
|
+
videoUrl: video.share_url,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Normalize TikTok analytics to common format
|
|
290
|
+
*/
|
|
291
|
+
protected normalizeAnalytics(platformData: any): SocialAnalytics {
|
|
292
|
+
return {
|
|
293
|
+
impressions: platformData.view_count || 0,
|
|
294
|
+
engagements: (platformData.like_count || 0) + (platformData.comment_count || 0) + (platformData.share_count || 0),
|
|
295
|
+
clicks: 0, // Not available in TikTok API
|
|
296
|
+
shares: platformData.share_count || 0,
|
|
297
|
+
comments: platformData.comment_count || 0,
|
|
298
|
+
likes: platformData.like_count || 0,
|
|
299
|
+
reach: platformData.view_count || 0,
|
|
300
|
+
videoViews: platformData.view_count || 0,
|
|
301
|
+
platformMetrics: platformData,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Extract hashtags from video description
|
|
307
|
+
*/
|
|
308
|
+
protected extractHashtags(description: string): string[] {
|
|
309
|
+
const hashtagRegex = /#(\w+)/g;
|
|
310
|
+
const matches = description.match(hashtagRegex) || [];
|
|
311
|
+
return matches.map((tag) => tag.substring(1).toLowerCase());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get current user info
|
|
316
|
+
*/
|
|
317
|
+
protected async getCurrentUser(): Promise<TikTokUser> {
|
|
318
|
+
const response = await this.makeTikTokRequest<any>('/v2/user/info/', 'GET', undefined, {
|
|
319
|
+
fields:
|
|
320
|
+
'open_id,union_id,avatar_url,display_name,bio_description,profile_deep_link,is_verified,follower_count,following_count,likes_count',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return response.data?.user;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Validate TikTok-specific media requirements
|
|
328
|
+
*/
|
|
329
|
+
protected validateMediaFile(file: MediaFile): void {
|
|
330
|
+
const allowedTypes = ['video/mp4', 'video/quicktime', 'video/webm'];
|
|
331
|
+
|
|
332
|
+
if (!allowedTypes.includes(file.mimeType)) {
|
|
333
|
+
throw new Error(`TikTok only supports video files. Got: ${file.mimeType}`);
|
|
339
334
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const allowedTypes = ['video/mp4', 'video/quicktime', 'video/webm'];
|
|
346
|
-
|
|
347
|
-
if (!allowedTypes.includes(file.mimeType)) {
|
|
348
|
-
throw new Error(`TikTok only supports video files. Got: ${file.mimeType}`);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Max file size: 287.6 MB
|
|
352
|
-
const maxSize = 287.6 * 1024 * 1024;
|
|
353
|
-
if (file.size > maxSize) {
|
|
354
|
-
throw new Error(`File size exceeds TikTok limit of 287.6 MB. Got: ${(file.size / 1024 / 1024).toFixed(2)} MB`);
|
|
355
|
-
}
|
|
335
|
+
|
|
336
|
+
// Max file size: 287.6 MB
|
|
337
|
+
const maxSize = 287.6 * 1024 * 1024;
|
|
338
|
+
if (file.size > maxSize) {
|
|
339
|
+
throw new Error(`File size exceeds TikTok limit of 287.6 MB. Got: ${(file.size / 1024 / 1024).toFixed(2)} MB`);
|
|
356
340
|
}
|
|
357
|
-
}
|
|
341
|
+
}
|
|
342
|
+
}
|