@memberjunction/actions-bizapps-social 2.112.0 → 2.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/base/base-social.action.d.ts.map +1 -1
- package/dist/base/base-social.action.js +24 -18
- package/dist/base/base-social.action.js.map +1 -1
- package/dist/providers/buffer/buffer-base.action.d.ts.map +1 -1
- package/dist/providers/buffer/buffer-base.action.js +34 -35
- package/dist/providers/buffer/buffer-base.action.js.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/boost-post.action.js +33 -33
- package/dist/providers/facebook/actions/boost-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-album.action.js +36 -34
- package/dist/providers/facebook/actions/create-album.action.js.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/create-post.action.js +20 -20
- package/dist/providers/facebook/actions/create-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-insights.action.js +27 -25
- package/dist/providers/facebook/actions/get-page-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-page-posts.action.js +23 -19
- package/dist/providers/facebook/actions/get-page-posts.action.js.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/get-post-insights.action.js +32 -28
- package/dist/providers/facebook/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/respond-to-comments.action.js +44 -42
- package/dist/providers/facebook/actions/respond-to-comments.action.js.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/schedule-post.action.js +29 -29
- package/dist/providers/facebook/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/facebook/actions/search-posts.action.js +39 -37
- package/dist/providers/facebook/actions/search-posts.action.js.map +1 -1
- package/dist/providers/facebook/facebook-base.action.d.ts.map +1 -1
- package/dist/providers/facebook/facebook-base.action.js +59 -44
- package/dist/providers/facebook/facebook-base.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js +31 -33
- package/dist/providers/hootsuite/actions/bulk-schedule-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js +32 -28
- package/dist/providers/hootsuite/actions/create-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js +19 -19
- package/dist/providers/hootsuite/actions/delete-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-analytics.action.js +26 -24
- package/dist/providers/hootsuite/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js +22 -22
- package/dist/providers/hootsuite/actions/get-scheduled-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js +34 -32
- package/dist/providers/hootsuite/actions/get-social-profiles.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/search-posts.action.js +52 -43
- package/dist/providers/hootsuite/actions/search-posts.action.js.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js +28 -30
- package/dist/providers/hootsuite/actions/update-scheduled-post.action.js.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.d.ts.map +1 -1
- package/dist/providers/hootsuite/hootsuite-base.action.js +20 -18
- package/dist/providers/hootsuite/hootsuite-base.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-post.action.js +26 -27
- package/dist/providers/instagram/actions/create-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/create-story.action.js +35 -35
- package/dist/providers/instagram/actions/create-story.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-account-insights.action.js +59 -38
- package/dist/providers/instagram/actions/get-account-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-business-posts.action.js +29 -29
- package/dist/providers/instagram/actions/get-business-posts.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-comments.action.js +36 -36
- package/dist/providers/instagram/actions/get-comments.action.js.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/get-post-insights.action.js +25 -23
- package/dist/providers/instagram/actions/get-post-insights.action.js.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/schedule-post.action.js +25 -25
- package/dist/providers/instagram/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/instagram/actions/search-posts.action.js +60 -56
- package/dist/providers/instagram/actions/search-posts.action.js.map +1 -1
- package/dist/providers/instagram/instagram-base.action.d.ts.map +1 -1
- package/dist/providers/instagram/instagram-base.action.js +27 -25
- package/dist/providers/instagram/instagram-base.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-article.action.js +45 -55
- package/dist/providers/linkedin/actions/create-article.action.js.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/create-post.action.js +29 -31
- package/dist/providers/linkedin/actions/create-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-followers.action.js +28 -28
- package/dist/providers/linkedin/actions/get-followers.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-organization-posts.action.js +20 -20
- package/dist/providers/linkedin/actions/get-organization-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-personal-posts.action.js +19 -19
- package/dist/providers/linkedin/actions/get-personal-posts.action.js.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/get-post-analytics.action.js +23 -25
- package/dist/providers/linkedin/actions/get-post-analytics.action.js.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/schedule-post.action.js +30 -32
- package/dist/providers/linkedin/actions/schedule-post.action.js.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.d.ts.map +1 -1
- package/dist/providers/linkedin/actions/search-posts.action.js +30 -28
- package/dist/providers/linkedin/actions/search-posts.action.js.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.d.ts.map +1 -1
- package/dist/providers/linkedin/linkedin-base.action.js +38 -33
- package/dist/providers/linkedin/linkedin-base.action.js.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.d.ts.map +1 -1
- package/dist/providers/tiktok/tiktok-base.action.js +26 -25
- package/dist/providers/tiktok/tiktok-base.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-thread.action.js +29 -25
- package/dist/providers/twitter/actions/create-thread.action.js.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/create-tweet.action.js +23 -23
- package/dist/providers/twitter/actions/create-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/delete-tweet.action.js +19 -19
- package/dist/providers/twitter/actions/delete-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-analytics.action.js +47 -40
- package/dist/providers/twitter/actions/get-analytics.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-mentions.action.js +31 -30
- package/dist/providers/twitter/actions/get-mentions.action.js.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/get-timeline.action.js +29 -29
- package/dist/providers/twitter/actions/get-timeline.action.js.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/schedule-tweet.action.js +26 -26
- package/dist/providers/twitter/actions/schedule-tweet.action.js.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.d.ts.map +1 -1
- package/dist/providers/twitter/actions/search-tweets.action.js +58 -56
- package/dist/providers/twitter/actions/search-tweets.action.js.map +1 -1
- package/dist/providers/twitter/twitter-base.action.d.ts.map +1 -1
- package/dist/providers/twitter/twitter-base.action.js +68 -58
- package/dist/providers/twitter/twitter-base.action.js.map +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts +1 -1
- package/dist/providers/youtube/youtube-base.action.d.ts.map +1 -1
- package/dist/providers/youtube/youtube-base.action.js +25 -22
- package/dist/providers/youtube/youtube-base.action.js.map +1 -1
- package/package.json +6 -5
- package/src/base/base-social.action.ts +224 -217
- package/src/providers/buffer/buffer-base.action.ts +441 -435
- package/src/providers/facebook/actions/boost-post.action.ts +386 -350
- package/src/providers/facebook/actions/create-album.action.ts +307 -291
- package/src/providers/facebook/actions/create-post.action.ts +227 -224
- package/src/providers/facebook/actions/get-page-insights.action.ts +403 -383
- package/src/providers/facebook/actions/get-page-posts.action.ts +225 -214
- package/src/providers/facebook/actions/get-post-insights.action.ts +316 -300
- package/src/providers/facebook/actions/respond-to-comments.action.ts +336 -319
- package/src/providers/facebook/actions/schedule-post.action.ts +292 -289
- package/src/providers/facebook/actions/search-posts.action.ts +413 -399
- package/src/providers/facebook/facebook-base.action.ts +670 -653
- package/src/providers/hootsuite/actions/bulk-schedule-posts.action.ts +257 -257
- package/src/providers/hootsuite/actions/create-scheduled-post.action.ts +189 -184
- package/src/providers/hootsuite/actions/delete-scheduled-post.action.ts +161 -160
- package/src/providers/hootsuite/actions/get-analytics.action.ts +254 -249
- package/src/providers/hootsuite/actions/get-scheduled-posts.action.ts +207 -206
- package/src/providers/hootsuite/actions/get-social-profiles.action.ts +205 -206
- package/src/providers/hootsuite/actions/search-posts.action.ts +369 -351
- package/src/providers/hootsuite/actions/update-scheduled-post.action.ts +209 -211
- package/src/providers/hootsuite/hootsuite-base.action.ts +307 -301
- package/src/providers/instagram/actions/create-post.action.ts +296 -276
- package/src/providers/instagram/actions/create-story.action.ts +394 -378
- package/src/providers/instagram/actions/get-account-insights.action.ts +420 -384
- package/src/providers/instagram/actions/get-business-posts.action.ts +242 -233
- package/src/providers/instagram/actions/get-comments.action.ts +377 -365
- package/src/providers/instagram/actions/get-post-insights.action.ts +273 -265
- package/src/providers/instagram/actions/schedule-post.action.ts +235 -233
- package/src/providers/instagram/actions/search-posts.action.ts +538 -512
- package/src/providers/instagram/instagram-base.action.ts +393 -368
- package/src/providers/linkedin/actions/create-article.action.ts +266 -275
- package/src/providers/linkedin/actions/create-post.action.ts +177 -179
- package/src/providers/linkedin/actions/get-followers.action.ts +211 -211
- package/src/providers/linkedin/actions/get-organization-posts.action.ts +147 -146
- package/src/providers/linkedin/actions/get-personal-posts.action.ts +139 -138
- package/src/providers/linkedin/actions/get-post-analytics.action.ts +189 -190
- package/src/providers/linkedin/actions/schedule-post.action.ts +189 -191
- package/src/providers/linkedin/actions/search-posts.action.ts +283 -275
- package/src/providers/linkedin/linkedin-base.action.ts +421 -407
- package/src/providers/tiktok/tiktok-base.action.ts +320 -305
- package/src/providers/twitter/actions/create-thread.action.ts +207 -203
- package/src/providers/twitter/actions/create-tweet.action.ts +188 -187
- package/src/providers/twitter/actions/delete-tweet.action.ts +129 -128
- package/src/providers/twitter/actions/get-analytics.action.ts +411 -402
- package/src/providers/twitter/actions/get-mentions.action.ts +219 -218
- package/src/providers/twitter/actions/get-timeline.action.ts +233 -232
- package/src/providers/twitter/actions/schedule-tweet.action.ts +222 -221
- package/src/providers/twitter/actions/search-tweets.action.ts +543 -540
- package/src/providers/twitter/twitter-base.action.ts +560 -541
- package/src/providers/youtube/youtube-base.action.ts +333 -320
|
@@ -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/core';
|
|
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,300 +43,315 @@ export interface TikTokUser {
|
|
|
43
43
|
*/
|
|
44
44
|
@RegisterClass(BaseAction, 'TikTokBaseAction')
|
|
45
45
|
export abstract class TikTokBaseAction extends BaseSocialMediaAction {
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
);
|
|
46
|
+
|
|
47
|
+
protected get platformName(): string {
|
|
48
|
+
return 'TikTok';
|
|
102
49
|
}
|
|
103
|
-
|
|
104
|
-
|
|
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');
|
|
50
|
+
|
|
51
|
+
protected get apiBaseUrl(): string {
|
|
52
|
+
return 'https://open-api.tiktok.com';
|
|
119
53
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
54
|
+
|
|
55
|
+
private axiosInstance: AxiosInstance | null = null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize axios instance with interceptors
|
|
59
|
+
*/
|
|
60
|
+
protected getAxiosInstance(): AxiosInstance {
|
|
61
|
+
if (!this.axiosInstance) {
|
|
62
|
+
this.axiosInstance = axios.create({
|
|
63
|
+
baseURL: this.apiBaseUrl,
|
|
64
|
+
timeout: 30000,
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'Accept': 'application/json'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Request interceptor for logging
|
|
72
|
+
this.axiosInstance.interceptors.request.use(
|
|
73
|
+
(config) => {
|
|
74
|
+
this.logApiRequest(config.method?.toUpperCase() || 'GET', config.url || '', config.data);
|
|
75
|
+
return config;
|
|
76
|
+
},
|
|
77
|
+
(error) => {
|
|
78
|
+
LogError(`TikTok API Request Error: ${error.message}`);
|
|
79
|
+
return Promise.reject(error);
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Response interceptor for logging and error handling
|
|
84
|
+
this.axiosInstance.interceptors.response.use(
|
|
85
|
+
(response) => {
|
|
86
|
+
this.logApiResponse(response.data);
|
|
87
|
+
return response;
|
|
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
|
+
);
|
|
154
103
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
throw new Error(`TikTok API error: ${errorMessage}`);
|
|
158
|
-
}
|
|
159
|
-
throw error;
|
|
104
|
+
|
|
105
|
+
return this.axiosInstance;
|
|
160
106
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Make authenticated TikTok API request
|
|
110
|
+
*/
|
|
111
|
+
protected async makeTikTokRequest<T>(
|
|
112
|
+
endpoint: string,
|
|
113
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
|
114
|
+
data?: any,
|
|
115
|
+
params?: any
|
|
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
|
+
}
|
|
170
162
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Refresh TikTok access token
|
|
166
|
+
*/
|
|
167
|
+
protected async refreshAccessToken(): Promise<void> {
|
|
168
|
+
const refreshToken = this.getRefreshToken();
|
|
169
|
+
if (!refreshToken) {
|
|
170
|
+
throw new Error('No refresh token available for TikTok');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const response = await axios.post(`${this.apiBaseUrl}/oauth/refresh_token/`, {
|
|
175
|
+
client_key: this.getCustomAttribute(2), // Store client key in CustomAttribute2
|
|
176
|
+
grant_type: 'refresh_token',
|
|
177
|
+
refresh_token: refreshToken
|
|
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
|
+
}
|
|
188
190
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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));
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Upload media to TikTok (requires special approval)
|
|
194
|
+
*/
|
|
195
|
+
protected async uploadSingleMedia(file: MediaFile): Promise<string> {
|
|
196
|
+
// TikTok video upload requires special approval
|
|
197
|
+
// This is a placeholder implementation
|
|
198
|
+
throw new Error('TikTok video upload requires special API approval. Please use TikTok Creator Studio for video uploads.');
|
|
214
199
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Search posts (videos) - TikTok only allows searching user's own videos
|
|
203
|
+
*/
|
|
204
|
+
protected async searchPosts(params: any): Promise<SocialPost[]> {
|
|
205
|
+
// TikTok doesn't provide a general search API for security/privacy reasons
|
|
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));
|
|
221
249
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get user's videos
|
|
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 || [];
|
|
226
265
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Convert TikTok video to common post format
|
|
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
|
+
};
|
|
231
297
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Normalize TikTok analytics to common format
|
|
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
|
+
};
|
|
236
314
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extract hashtags from video description
|
|
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());
|
|
240
323
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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}`);
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get current user info
|
|
327
|
+
*/
|
|
328
|
+
protected async getCurrentUser(): Promise<TikTokUser> {
|
|
329
|
+
const response = await this.makeTikTokRequest<any>(
|
|
330
|
+
'/v2/user/info/',
|
|
331
|
+
'GET',
|
|
332
|
+
undefined,
|
|
333
|
+
{
|
|
334
|
+
fields: 'open_id,union_id,avatar_url,display_name,bio_description,profile_deep_link,is_verified,follower_count,following_count,likes_count'
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
return response.data?.user;
|
|
334
339
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Validate TikTok-specific media requirements
|
|
343
|
+
*/
|
|
344
|
+
protected validateMediaFile(file: MediaFile): void {
|
|
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
|
+
}
|
|
340
356
|
}
|
|
341
|
-
|
|
342
|
-
}
|
|
357
|
+
}
|