@olastudio/social-media-sdk 0.1.0 → 0.1.2
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/README.md +190 -55
- package/dist/adapters/expo.js +23 -2
- package/dist/adapters/expo.js.map +1 -1
- package/dist/adapters/expo.mjs +22 -1
- package/dist/adapters/expo.mjs.map +1 -1
- package/dist/adapters/index.js +0 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/core/index.js +318 -87
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +301 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.js +2225 -170
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2186 -7
- package/dist/index.mjs.map +1 -1
- package/dist/providers/facebook/index.js +1071 -26
- package/dist/providers/facebook/index.js.map +1 -1
- package/dist/providers/facebook/index.mjs +1067 -3
- package/dist/providers/facebook/index.mjs.map +1 -1
- package/dist/providers/instagram/index.js +866 -22
- package/dist/providers/instagram/index.js.map +1 -1
- package/dist/providers/instagram/index.mjs +863 -3
- package/dist/providers/instagram/index.mjs.map +1 -1
- package/dist/providers/tiktok/index.js +395 -41
- package/dist/providers/tiktok/index.js.map +1 -1
- package/dist/providers/tiktok/index.mjs +389 -1
- package/dist/providers/tiktok/index.mjs.map +1 -1
- package/package.json +3 -4
- package/dist/chunk-36RADUUO.mjs +0 -31
- package/dist/chunk-36RADUUO.mjs.map +0 -1
- package/dist/chunk-7QAMNVQU.js +0 -666
- package/dist/chunk-7QAMNVQU.js.map +0 -1
- package/dist/chunk-B6NUTR54.js +0 -4
- package/dist/chunk-B6NUTR54.js.map +0 -1
- package/dist/chunk-BX3RO5PW.js +0 -4
- package/dist/chunk-BX3RO5PW.js.map +0 -1
- package/dist/chunk-CGNGZNVG.mjs +0 -391
- package/dist/chunk-CGNGZNVG.mjs.map +0 -1
- package/dist/chunk-ER5A6TIL.js +0 -296
- package/dist/chunk-ER5A6TIL.js.map +0 -1
- package/dist/chunk-GF3OEIKI.mjs +0 -3
- package/dist/chunk-GF3OEIKI.mjs.map +0 -1
- package/dist/chunk-H5GAC4UG.mjs +0 -277
- package/dist/chunk-H5GAC4UG.mjs.map +0 -1
- package/dist/chunk-HPLIHYLQ.js +0 -35
- package/dist/chunk-HPLIHYLQ.js.map +0 -1
- package/dist/chunk-MV6HJQQO.mjs +0 -3
- package/dist/chunk-MV6HJQQO.mjs.map +0 -1
- package/dist/chunk-ONR2OJOB.mjs +0 -848
- package/dist/chunk-ONR2OJOB.mjs.map +0 -1
- package/dist/chunk-PJ4KYVHH.js +0 -854
- package/dist/chunk-PJ4KYVHH.js.map +0 -1
- package/dist/chunk-QRGJXASL.js +0 -402
- package/dist/chunk-QRGJXASL.js.map +0 -1
- package/dist/chunk-QZHJXRRW.mjs +0 -661
- package/dist/chunk-QZHJXRRW.mjs.map +0 -1
|
@@ -1,3 +1,391 @@
|
|
|
1
|
-
|
|
1
|
+
// providers/tiktok/constants.ts
|
|
2
|
+
var TIKTOK_API_VERSION = "v2";
|
|
3
|
+
var TIKTOK_API_BASE_URL = `https://open.tiktokapis.com/${TIKTOK_API_VERSION}`;
|
|
4
|
+
var TIKTOK_OAUTH_AUTHORIZATION_URL = "https://www.tiktok.com/v2/auth/authorize/";
|
|
5
|
+
var TIKTOK_OAUTH_TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/";
|
|
6
|
+
var TIKTOK_OAUTH_REVOKE_URL = "https://open.tiktokapis.com/v2/oauth/revoke/";
|
|
7
|
+
var TikTokScopes = {
|
|
8
|
+
// User info scopes
|
|
9
|
+
USER_INFO_BASIC: "user.info.basic",
|
|
10
|
+
USER_INFO_PROFILE: "user.info.profile",
|
|
11
|
+
USER_INFO_STATS: "user.info.stats",
|
|
12
|
+
// Video scopes
|
|
13
|
+
VIDEO_LIST: "video.list",
|
|
14
|
+
VIDEO_PUBLISH: "video.publish",
|
|
15
|
+
VIDEO_UPLOAD: "video.upload"
|
|
16
|
+
};
|
|
17
|
+
var TIKTOK_DEFAULT_SCOPES = [
|
|
18
|
+
TikTokScopes.USER_INFO_BASIC,
|
|
19
|
+
TikTokScopes.USER_INFO_PROFILE,
|
|
20
|
+
TikTokScopes.USER_INFO_STATS,
|
|
21
|
+
TikTokScopes.VIDEO_LIST,
|
|
22
|
+
TikTokScopes.VIDEO_PUBLISH,
|
|
23
|
+
TikTokScopes.VIDEO_UPLOAD
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// providers/tiktok/api/index.ts
|
|
27
|
+
var TikTokAPI = class {
|
|
28
|
+
constructor(config, accessToken) {
|
|
29
|
+
this._config = config;
|
|
30
|
+
this.accessToken = accessToken;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Set access token
|
|
34
|
+
*/
|
|
35
|
+
setAccessToken(accessToken) {
|
|
36
|
+
this.accessToken = accessToken;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get current access token
|
|
40
|
+
*/
|
|
41
|
+
getAccessToken() {
|
|
42
|
+
return this.accessToken;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Make authenticated API request
|
|
46
|
+
*/
|
|
47
|
+
async request(endpoint, options = {}) {
|
|
48
|
+
if (!this.accessToken) {
|
|
49
|
+
throw new Error("Access token is required");
|
|
50
|
+
}
|
|
51
|
+
const url = `${TIKTOK_API_BASE_URL}${endpoint}`;
|
|
52
|
+
const response = await fetch(url, {
|
|
53
|
+
...options,
|
|
54
|
+
headers: {
|
|
55
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
...options.headers
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
if (data.error && data.error.code !== "ok") {
|
|
62
|
+
throw new Error(data.error.message || "TikTok API error");
|
|
63
|
+
}
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get user info
|
|
68
|
+
* @see https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info
|
|
69
|
+
*/
|
|
70
|
+
async getUserInfo(fields) {
|
|
71
|
+
const defaultFields = [
|
|
72
|
+
"open_id",
|
|
73
|
+
"union_id",
|
|
74
|
+
"avatar_url",
|
|
75
|
+
"avatar_url_100",
|
|
76
|
+
"avatar_large_url",
|
|
77
|
+
"display_name",
|
|
78
|
+
"bio_description",
|
|
79
|
+
"profile_deep_link",
|
|
80
|
+
"is_verified",
|
|
81
|
+
"follower_count",
|
|
82
|
+
"following_count",
|
|
83
|
+
"likes_count",
|
|
84
|
+
"video_count"
|
|
85
|
+
];
|
|
86
|
+
const params = new URLSearchParams({
|
|
87
|
+
fields: (fields || defaultFields).join(",")
|
|
88
|
+
});
|
|
89
|
+
const response = await this.request(
|
|
90
|
+
`/user/info/?${params.toString()}`
|
|
91
|
+
);
|
|
92
|
+
return response.data.user;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get user's videos
|
|
96
|
+
* @see https://developers.tiktok.com/doc/tiktok-api-v2-video-list
|
|
97
|
+
*/
|
|
98
|
+
async getVideoList(options) {
|
|
99
|
+
const defaultFields = [
|
|
100
|
+
"id",
|
|
101
|
+
"create_time",
|
|
102
|
+
"cover_image_url",
|
|
103
|
+
"share_url",
|
|
104
|
+
"video_description",
|
|
105
|
+
"duration",
|
|
106
|
+
"height",
|
|
107
|
+
"width",
|
|
108
|
+
"title",
|
|
109
|
+
"embed_html",
|
|
110
|
+
"embed_link",
|
|
111
|
+
"like_count",
|
|
112
|
+
"comment_count",
|
|
113
|
+
"share_count",
|
|
114
|
+
"view_count"
|
|
115
|
+
];
|
|
116
|
+
const body = {
|
|
117
|
+
max_count: options?.maxCount || 20,
|
|
118
|
+
cursor: options?.cursor,
|
|
119
|
+
fields: options?.fields || defaultFields
|
|
120
|
+
};
|
|
121
|
+
const response = await this.request("/video/list/", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: JSON.stringify(body)
|
|
124
|
+
});
|
|
125
|
+
return response.data;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Initialize direct video upload
|
|
129
|
+
* @see https://developers.tiktok.com/doc/tiktok-api-v2-post-publish-video-init
|
|
130
|
+
*/
|
|
131
|
+
async initVideoUpload(options) {
|
|
132
|
+
const response = await this.request(
|
|
133
|
+
"/post/publish/video/init/",
|
|
134
|
+
{
|
|
135
|
+
method: "POST",
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
source_info: options.sourceInfo,
|
|
138
|
+
post_info: options.postInfo
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
return response.data;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get publish status
|
|
146
|
+
* @see https://developers.tiktok.com/doc/tiktok-api-v2-post-publish-status-fetch
|
|
147
|
+
*/
|
|
148
|
+
async getPublishStatus(publishId) {
|
|
149
|
+
const response = await this.request(
|
|
150
|
+
"/post/publish/status/fetch/",
|
|
151
|
+
{
|
|
152
|
+
method: "POST",
|
|
153
|
+
body: JSON.stringify({ publish_id: publishId })
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
return response.data;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get account insights (aggregated from user info and recent videos)
|
|
160
|
+
* TikTok doesn't have a dedicated insights API like Instagram/Facebook,
|
|
161
|
+
* so we compute metrics from user info and video stats
|
|
162
|
+
*/
|
|
163
|
+
async getAccountInsights() {
|
|
164
|
+
const userInfo = await this.getUserInfo();
|
|
165
|
+
const videoList = await this.getVideoList({ maxCount: 50 });
|
|
166
|
+
let totalViews = 0;
|
|
167
|
+
let totalLikes = 0;
|
|
168
|
+
let totalComments = 0;
|
|
169
|
+
let totalShares = 0;
|
|
170
|
+
for (const video of videoList.videos) {
|
|
171
|
+
totalViews += video.view_count || 0;
|
|
172
|
+
totalLikes += video.like_count || 0;
|
|
173
|
+
totalComments += video.comment_count || 0;
|
|
174
|
+
totalShares += video.share_count || 0;
|
|
175
|
+
}
|
|
176
|
+
const totalEngagements = totalLikes + totalComments + totalShares;
|
|
177
|
+
const averageEngagementRate = totalViews > 0 ? totalEngagements / totalViews * 100 : 0;
|
|
178
|
+
return {
|
|
179
|
+
followerCount: userInfo.follower_count || 0,
|
|
180
|
+
followingCount: userInfo.following_count || 0,
|
|
181
|
+
likesCount: userInfo.likes_count || 0,
|
|
182
|
+
videoCount: userInfo.video_count || 0,
|
|
183
|
+
totalViews,
|
|
184
|
+
totalComments,
|
|
185
|
+
totalShares,
|
|
186
|
+
averageEngagementRate: Math.round(averageEngagementRate * 100) / 100
|
|
187
|
+
// Round to 2 decimals
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get insights for a specific video
|
|
192
|
+
*/
|
|
193
|
+
async getVideoInsights(videoId) {
|
|
194
|
+
const videoList = await this.getVideoList({ maxCount: 50 });
|
|
195
|
+
const video = videoList.videos.find((v) => v.id === videoId);
|
|
196
|
+
if (!video) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const viewCount = video.view_count || 0;
|
|
200
|
+
const likeCount = video.like_count || 0;
|
|
201
|
+
const commentCount = video.comment_count || 0;
|
|
202
|
+
const shareCount = video.share_count || 0;
|
|
203
|
+
const engagementRate = viewCount > 0 ? (likeCount + commentCount + shareCount) / viewCount * 100 : 0;
|
|
204
|
+
return {
|
|
205
|
+
videoId: video.id,
|
|
206
|
+
viewCount,
|
|
207
|
+
likeCount,
|
|
208
|
+
commentCount,
|
|
209
|
+
shareCount,
|
|
210
|
+
engagementRate: Math.round(engagementRate * 100) / 100,
|
|
211
|
+
createTime: video.create_time
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get insights for multiple videos
|
|
216
|
+
*/
|
|
217
|
+
async getVideosInsights(maxCount = 20) {
|
|
218
|
+
const videoList = await this.getVideoList({ maxCount });
|
|
219
|
+
return videoList.videos.map((video) => {
|
|
220
|
+
const viewCount = video.view_count || 0;
|
|
221
|
+
const likeCount = video.like_count || 0;
|
|
222
|
+
const commentCount = video.comment_count || 0;
|
|
223
|
+
const shareCount = video.share_count || 0;
|
|
224
|
+
const engagementRate = viewCount > 0 ? (likeCount + commentCount + shareCount) / viewCount * 100 : 0;
|
|
225
|
+
return {
|
|
226
|
+
videoId: video.id,
|
|
227
|
+
viewCount,
|
|
228
|
+
likeCount,
|
|
229
|
+
commentCount,
|
|
230
|
+
shareCount,
|
|
231
|
+
engagementRate: Math.round(engagementRate * 100) / 100,
|
|
232
|
+
createTime: video.create_time
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get formatted insights (unified format similar to Instagram/Facebook)
|
|
238
|
+
* This allows for easier aggregation across platforms
|
|
239
|
+
*/
|
|
240
|
+
async getInsights(params) {
|
|
241
|
+
const accountInsights = await this.getAccountInsights();
|
|
242
|
+
const now = /* @__PURE__ */ new Date();
|
|
243
|
+
const endTime = now.toISOString();
|
|
244
|
+
const metricsMap = {
|
|
245
|
+
followers: accountInsights.followerCount,
|
|
246
|
+
following: accountInsights.followingCount,
|
|
247
|
+
likes: accountInsights.likesCount,
|
|
248
|
+
video_count: accountInsights.videoCount,
|
|
249
|
+
views: accountInsights.totalViews,
|
|
250
|
+
comments: accountInsights.totalComments,
|
|
251
|
+
shares: accountInsights.totalShares,
|
|
252
|
+
engagement_rate: accountInsights.averageEngagementRate
|
|
253
|
+
};
|
|
254
|
+
const data = params.metric.map((metricName) => ({
|
|
255
|
+
name: metricName,
|
|
256
|
+
period: "lifetime",
|
|
257
|
+
values: [{
|
|
258
|
+
value: metricsMap[metricName] || 0,
|
|
259
|
+
end_time: endTime
|
|
260
|
+
}],
|
|
261
|
+
title: metricName.charAt(0).toUpperCase() + metricName.slice(1).replace(/_/g, " ")
|
|
262
|
+
}));
|
|
263
|
+
return { data };
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// providers/tiktok/auth/index.ts
|
|
268
|
+
var TikTokAuth = class {
|
|
269
|
+
constructor(config) {
|
|
270
|
+
this.config = config;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Build authorization URL for OAuth flow
|
|
274
|
+
*/
|
|
275
|
+
getAuthorizationUrl(options) {
|
|
276
|
+
const params = new URLSearchParams({
|
|
277
|
+
client_key: this.config.clientKey,
|
|
278
|
+
redirect_uri: options?.redirectUri || this.config.redirectUri || "",
|
|
279
|
+
response_type: "code",
|
|
280
|
+
scope: (options?.scopes || this.config.scopes || TIKTOK_DEFAULT_SCOPES).join(",")
|
|
281
|
+
});
|
|
282
|
+
if (options?.state) {
|
|
283
|
+
params.set("state", options.state);
|
|
284
|
+
}
|
|
285
|
+
return `${TIKTOK_OAUTH_AUTHORIZATION_URL}?${params.toString()}`;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Exchange authorization code for access token
|
|
289
|
+
* NOTE: This should be done server-side to protect client_secret
|
|
290
|
+
*/
|
|
291
|
+
async exchangeCodeForToken(code, redirectUri) {
|
|
292
|
+
if (!this.config.clientSecret) {
|
|
293
|
+
throw new Error("Client secret is required for token exchange");
|
|
294
|
+
}
|
|
295
|
+
const response = await fetch(TIKTOK_OAUTH_TOKEN_URL, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
299
|
+
},
|
|
300
|
+
body: new URLSearchParams({
|
|
301
|
+
client_key: this.config.clientKey,
|
|
302
|
+
client_secret: this.config.clientSecret,
|
|
303
|
+
code,
|
|
304
|
+
grant_type: "authorization_code",
|
|
305
|
+
redirect_uri: redirectUri || this.config.redirectUri || ""
|
|
306
|
+
}).toString()
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
const error = await response.json();
|
|
310
|
+
throw new Error(error.error?.message || "Failed to exchange code for token");
|
|
311
|
+
}
|
|
312
|
+
return response.json();
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Refresh access token using refresh token
|
|
316
|
+
* NOTE: This should be done server-side to protect client_secret
|
|
317
|
+
*/
|
|
318
|
+
async refreshToken(refreshToken) {
|
|
319
|
+
if (!this.config.clientSecret) {
|
|
320
|
+
throw new Error("Client secret is required for token refresh");
|
|
321
|
+
}
|
|
322
|
+
const response = await fetch(TIKTOK_OAUTH_TOKEN_URL, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: {
|
|
325
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
326
|
+
},
|
|
327
|
+
body: new URLSearchParams({
|
|
328
|
+
client_key: this.config.clientKey,
|
|
329
|
+
client_secret: this.config.clientSecret,
|
|
330
|
+
grant_type: "refresh_token",
|
|
331
|
+
refresh_token: refreshToken
|
|
332
|
+
}).toString()
|
|
333
|
+
});
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
const error = await response.json();
|
|
336
|
+
throw new Error(error.error?.message || "Failed to refresh token");
|
|
337
|
+
}
|
|
338
|
+
return response.json();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Revoke access token
|
|
342
|
+
*/
|
|
343
|
+
async revokeToken(accessToken) {
|
|
344
|
+
const response = await fetch(TIKTOK_OAUTH_REVOKE_URL, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
headers: {
|
|
347
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
348
|
+
},
|
|
349
|
+
body: new URLSearchParams({
|
|
350
|
+
client_key: this.config.clientKey,
|
|
351
|
+
token: accessToken
|
|
352
|
+
}).toString()
|
|
353
|
+
});
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
const error = await response.json();
|
|
356
|
+
throw new Error(error.error?.message || "Failed to revoke token");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// providers/tiktok/TikTokProvider.ts
|
|
362
|
+
var TikTokProvider = class {
|
|
363
|
+
constructor(config) {
|
|
364
|
+
this.name = "tiktok";
|
|
365
|
+
this.config = config;
|
|
366
|
+
this.auth = new TikTokAuth(config);
|
|
367
|
+
this.api = new TikTokAPI(config);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Create new API instance with access token
|
|
371
|
+
*/
|
|
372
|
+
createAPIClient(accessToken) {
|
|
373
|
+
return new TikTokAPI(this.config, accessToken);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Update access token of existing API client
|
|
377
|
+
*/
|
|
378
|
+
setAccessToken(accessToken) {
|
|
379
|
+
this.api.setAccessToken(accessToken);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get current access token
|
|
383
|
+
*/
|
|
384
|
+
getAccessToken() {
|
|
385
|
+
return this.api.getAccessToken();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export { TIKTOK_API_BASE_URL, TIKTOK_API_VERSION, TIKTOK_DEFAULT_SCOPES, TIKTOK_OAUTH_AUTHORIZATION_URL, TIKTOK_OAUTH_REVOKE_URL, TIKTOK_OAUTH_TOKEN_URL, TikTokAPI, TikTokAuth, TikTokProvider, TikTokScopes };
|
|
2
390
|
//# sourceMappingURL=index.mjs.map
|
|
3
391
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
|
1
|
+
{"version":3,"sources":["../../../providers/tiktok/constants.ts","../../../providers/tiktok/api/index.ts","../../../providers/tiktok/auth/index.ts","../../../providers/tiktok/TikTokProvider.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * TikTok API Constants\n */\n\n/**\n * TikTok API Version\n * @see https://developers.tiktok.com/doc/about-tiktok-api-versions\n */\nexport const TIKTOK_API_VERSION = 'v2' as const;\n\n/**\n * TikTok API Base URL\n */\nexport const TIKTOK_API_BASE_URL = `https://open.tiktokapis.com/${TIKTOK_API_VERSION}` as const;\n\n/**\n * TikTok OAuth URLs\n */\nexport const TIKTOK_OAUTH_AUTHORIZATION_URL = 'https://www.tiktok.com/v2/auth/authorize/' as const;\nexport const TIKTOK_OAUTH_TOKEN_URL = 'https://open.tiktokapis.com/v2/oauth/token/' as const;\nexport const TIKTOK_OAUTH_REVOKE_URL = 'https://open.tiktokapis.com/v2/oauth/revoke/' as const;\n\n/**\n * TikTok OAuth Scopes\n * @see https://developers.tiktok.com/doc/tiktok-api-scopes\n */\nexport const TikTokScopes = {\n // User info scopes\n USER_INFO_BASIC: 'user.info.basic',\n USER_INFO_PROFILE: 'user.info.profile',\n USER_INFO_STATS: 'user.info.stats',\n \n // Video scopes\n VIDEO_LIST: 'video.list',\n VIDEO_PUBLISH: 'video.publish',\n VIDEO_UPLOAD: 'video.upload',\n} as const;\n\nexport type TikTokScope = typeof TikTokScopes[keyof typeof TikTokScopes];\n\n/**\n * Default scopes for TikTok connection\n */\nexport const TIKTOK_DEFAULT_SCOPES: TikTokScope[] = [\n TikTokScopes.USER_INFO_BASIC,\n TikTokScopes.USER_INFO_PROFILE,\n TikTokScopes.USER_INFO_STATS,\n TikTokScopes.VIDEO_LIST,\n TikTokScopes.VIDEO_PUBLISH,\n TikTokScopes.VIDEO_UPLOAD,\n];\n","/**\n * TikTok API - API client for TikTok endpoints\n */\n\nimport { TIKTOK_API_BASE_URL } from '../constants';\nimport {\n TikTokAccountInsights,\n TikTokAPIResponse,\n TikTokConfig,\n TikTokInsights,\n TikTokInsightsParams,\n TikTokPublishStatusResponse,\n TikTokUploadInitResponse,\n TikTokUserInfo,\n TikTokVideoInsights,\n TikTokVideoListResponse,\n} from '../types';\n\n/**\n * TikTok API client\n */\nexport class TikTokAPI {\n private _config: TikTokConfig;\n private accessToken?: string;\n\n constructor(config: TikTokConfig, accessToken?: string) {\n this._config = config;\n this.accessToken = accessToken;\n }\n\n /**\n * Set access token\n */\n setAccessToken(accessToken: string) {\n this.accessToken = accessToken;\n }\n\n /**\n * Get current access token\n */\n getAccessToken(): string | undefined {\n return this.accessToken;\n }\n\n /**\n * Make authenticated API request\n */\n private async request<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<TikTokAPIResponse<T>> {\n if (!this.accessToken) {\n throw new Error('Access token is required');\n }\n\n const url = `${TIKTOK_API_BASE_URL}${endpoint}`;\n\n const response = await fetch(url, {\n ...options,\n headers: {\n 'Authorization': `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n });\n\n const data = await response.json();\n\n if (data.error && data.error.code !== 'ok') {\n throw new Error(data.error.message || 'TikTok API error');\n }\n\n return data;\n }\n\n /**\n * Get user info\n * @see https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info\n */\n async getUserInfo(fields?: string[]): Promise<TikTokUserInfo> {\n const defaultFields = [\n 'open_id',\n 'union_id',\n 'avatar_url',\n 'avatar_url_100',\n 'avatar_large_url',\n 'display_name',\n 'bio_description',\n 'profile_deep_link',\n 'is_verified',\n 'follower_count',\n 'following_count',\n 'likes_count',\n 'video_count',\n ];\n\n const params = new URLSearchParams({\n fields: (fields || defaultFields).join(','),\n });\n\n const response = await this.request<{ user: TikTokUserInfo }>(\n `/user/info/?${params.toString()}`\n );\n\n return response.data.user;\n }\n\n /**\n * Get user's videos\n * @see https://developers.tiktok.com/doc/tiktok-api-v2-video-list\n */\n async getVideoList(options?: {\n cursor?: number;\n maxCount?: number;\n fields?: string[];\n }): Promise<TikTokVideoListResponse> {\n const defaultFields = [\n 'id',\n 'create_time',\n 'cover_image_url',\n 'share_url',\n 'video_description',\n 'duration',\n 'height',\n 'width',\n 'title',\n 'embed_html',\n 'embed_link',\n 'like_count',\n 'comment_count',\n 'share_count',\n 'view_count',\n ];\n\n const body = {\n max_count: options?.maxCount || 20,\n cursor: options?.cursor,\n fields: options?.fields || defaultFields,\n };\n\n const response = await this.request<TikTokVideoListResponse>('/video/list/', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n\n return response.data;\n }\n\n /**\n * Initialize direct video upload\n * @see https://developers.tiktok.com/doc/tiktok-api-v2-post-publish-video-init\n */\n async initVideoUpload(options: {\n sourceInfo: {\n source: 'FILE_UPLOAD' | 'PULL_FROM_URL';\n videoSize?: number;\n chunkSize?: number;\n totalChunkCount?: number;\n videoUrl?: string;\n };\n postInfo?: {\n title?: string;\n privacyLevel?: 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'SELF_ONLY';\n disableDuet?: boolean;\n disableStitch?: boolean;\n disableComment?: boolean;\n videoCoverTimestampMs?: number;\n };\n }): Promise<TikTokUploadInitResponse> {\n const response = await this.request<TikTokUploadInitResponse>(\n '/post/publish/video/init/',\n {\n method: 'POST',\n body: JSON.stringify({\n source_info: options.sourceInfo,\n post_info: options.postInfo,\n }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Get publish status\n * @see https://developers.tiktok.com/doc/tiktok-api-v2-post-publish-status-fetch\n */\n async getPublishStatus(publishId: string): Promise<TikTokPublishStatusResponse> {\n const response = await this.request<TikTokPublishStatusResponse>(\n '/post/publish/status/fetch/',\n {\n method: 'POST',\n body: JSON.stringify({ publish_id: publishId }),\n }\n );\n\n return response.data;\n }\n\n /**\n * Get account insights (aggregated from user info and recent videos)\n * TikTok doesn't have a dedicated insights API like Instagram/Facebook,\n * so we compute metrics from user info and video stats\n */\n async getAccountInsights(): Promise<TikTokAccountInsights> {\n // Get user info for follower/following counts\n const userInfo = await this.getUserInfo();\n\n // Get recent videos for engagement metrics\n const videoList = await this.getVideoList({ maxCount: 50 });\n\n // Calculate totals from videos\n let totalViews = 0;\n let totalLikes = 0;\n let totalComments = 0;\n let totalShares = 0;\n\n for (const video of videoList.videos) {\n totalViews += video.view_count || 0;\n totalLikes += video.like_count || 0;\n totalComments += video.comment_count || 0;\n totalShares += video.share_count || 0;\n }\n\n // Calculate average engagement rate\n // Engagement Rate = (likes + comments + shares) / views * 100\n const totalEngagements = totalLikes + totalComments + totalShares;\n const averageEngagementRate = totalViews > 0\n ? (totalEngagements / totalViews) * 100\n : 0;\n\n return {\n followerCount: userInfo.follower_count || 0,\n followingCount: userInfo.following_count || 0,\n likesCount: userInfo.likes_count || 0,\n videoCount: userInfo.video_count || 0,\n totalViews,\n totalComments,\n totalShares,\n averageEngagementRate: Math.round(averageEngagementRate * 100) / 100, // Round to 2 decimals\n };\n }\n\n /**\n * Get insights for a specific video\n */\n async getVideoInsights(videoId: string): Promise<TikTokVideoInsights | null> {\n const videoList = await this.getVideoList({ maxCount: 50 });\n\n const video = videoList.videos.find(v => v.id === videoId);\n if (!video) {\n return null;\n }\n\n const viewCount = video.view_count || 0;\n const likeCount = video.like_count || 0;\n const commentCount = video.comment_count || 0;\n const shareCount = video.share_count || 0;\n\n // Calculate engagement rate for this video\n const engagementRate = viewCount > 0\n ? ((likeCount + commentCount + shareCount) / viewCount) * 100\n : 0;\n\n return {\n videoId: video.id,\n viewCount,\n likeCount,\n commentCount,\n shareCount,\n engagementRate: Math.round(engagementRate * 100) / 100,\n createTime: video.create_time,\n };\n }\n\n /**\n * Get insights for multiple videos\n */\n async getVideosInsights(maxCount: number = 20): Promise<TikTokVideoInsights[]> {\n const videoList = await this.getVideoList({ maxCount });\n\n return videoList.videos.map(video => {\n const viewCount = video.view_count || 0;\n const likeCount = video.like_count || 0;\n const commentCount = video.comment_count || 0;\n const shareCount = video.share_count || 0;\n\n const engagementRate = viewCount > 0\n ? ((likeCount + commentCount + shareCount) / viewCount) * 100\n : 0;\n\n return {\n videoId: video.id,\n viewCount,\n likeCount,\n commentCount,\n shareCount,\n engagementRate: Math.round(engagementRate * 100) / 100,\n createTime: video.create_time,\n };\n });\n }\n\n /**\n * Get formatted insights (unified format similar to Instagram/Facebook)\n * This allows for easier aggregation across platforms\n */\n async getInsights(params: TikTokInsightsParams): Promise<TikTokInsights> {\n const accountInsights = await this.getAccountInsights();\n const now = new Date();\n const endTime = now.toISOString();\n\n const metricsMap: Record<string, number> = {\n followers: accountInsights.followerCount,\n following: accountInsights.followingCount,\n likes: accountInsights.likesCount,\n video_count: accountInsights.videoCount,\n views: accountInsights.totalViews,\n comments: accountInsights.totalComments,\n shares: accountInsights.totalShares,\n engagement_rate: accountInsights.averageEngagementRate,\n };\n\n const data = params.metric.map(metricName => ({\n name: metricName,\n period: 'lifetime',\n values: [{\n value: metricsMap[metricName] || 0,\n end_time: endTime,\n }],\n title: metricName.charAt(0).toUpperCase() + metricName.slice(1).replace(/_/g, ' '),\n }));\n\n return { data };\n }\n}\n\n","/**\n * TikTok Auth - Authentication utilities\n */\n\nimport {\n TIKTOK_DEFAULT_SCOPES,\n TIKTOK_OAUTH_AUTHORIZATION_URL,\n TIKTOK_OAUTH_REVOKE_URL,\n TIKTOK_OAUTH_TOKEN_URL,\n TikTokScope,\n} from '../constants';\nimport { TikTokConfig, TikTokTokenResponse } from '../types';\n\n/**\n * TikTok Auth class for OAuth operations\n */\nexport class TikTokAuth {\n private config: TikTokConfig;\n\n constructor(config: TikTokConfig) {\n this.config = config;\n }\n\n /**\n * Build authorization URL for OAuth flow\n */\n getAuthorizationUrl(options?: {\n redirectUri?: string;\n scopes?: TikTokScope[];\n state?: string;\n }): string {\n const params = new URLSearchParams({\n client_key: this.config.clientKey,\n redirect_uri: options?.redirectUri || this.config.redirectUri || '',\n response_type: 'code',\n scope: (options?.scopes || this.config.scopes || TIKTOK_DEFAULT_SCOPES).join(','),\n });\n\n if (options?.state) {\n params.set('state', options.state);\n }\n\n return `${TIKTOK_OAUTH_AUTHORIZATION_URL}?${params.toString()}`;\n }\n\n /**\n * Exchange authorization code for access token\n * NOTE: This should be done server-side to protect client_secret\n */\n async exchangeCodeForToken(\n code: string,\n redirectUri?: string\n ): Promise<TikTokTokenResponse> {\n if (!this.config.clientSecret) {\n throw new Error('Client secret is required for token exchange');\n }\n\n const response = await fetch(TIKTOK_OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_key: this.config.clientKey,\n client_secret: this.config.clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri || this.config.redirectUri || '',\n }).toString(),\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error?.message || 'Failed to exchange code for token');\n }\n\n return response.json();\n }\n\n /**\n * Refresh access token using refresh token\n * NOTE: This should be done server-side to protect client_secret\n */\n async refreshToken(refreshToken: string): Promise<TikTokTokenResponse> {\n if (!this.config.clientSecret) {\n throw new Error('Client secret is required for token refresh');\n }\n\n const response = await fetch(TIKTOK_OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_key: this.config.clientKey,\n client_secret: this.config.clientSecret,\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }).toString(),\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error?.message || 'Failed to refresh token');\n }\n\n return response.json();\n }\n\n /**\n * Revoke access token\n */\n async revokeToken(accessToken: string): Promise<void> {\n const response = await fetch(TIKTOK_OAUTH_REVOKE_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_key: this.config.clientKey,\n token: accessToken,\n }).toString(),\n });\n\n if (!response.ok) {\n const error = await response.json();\n throw new Error(error.error?.message || 'Failed to revoke token');\n }\n }\n}\n","/**\n * TikTok Provider - Main TikTok provider\n */\n\nimport { TikTokAPI } from './api';\nimport { TikTokAuth } from './auth';\nimport { TikTokConfig } from './types';\n\n/**\n * TikTok provider that encapsulates Auth and API\n */\nexport class TikTokProvider {\n public readonly name = 'tiktok' as const;\n public auth: TikTokAuth;\n public api: TikTokAPI;\n public config: TikTokConfig;\n\n constructor(config: TikTokConfig) {\n this.config = config;\n this.auth = new TikTokAuth(config);\n this.api = new TikTokAPI(config);\n }\n\n /**\n * Create new API instance with access token\n */\n createAPIClient(accessToken: string): TikTokAPI {\n return new TikTokAPI(this.config, accessToken);\n }\n\n /**\n * Update access token of existing API client\n */\n setAccessToken(accessToken: string) {\n this.api.setAccessToken(accessToken);\n }\n\n /**\n * Get current access token\n */\n getAccessToken(): string | undefined {\n return this.api.getAccessToken();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olastudio/social-media-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Modular SDK for managing authentication and APIs of multiple social media platforms",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -62,14 +62,13 @@
|
|
|
62
62
|
"api",
|
|
63
63
|
"expo",
|
|
64
64
|
"react-native",
|
|
65
|
-
"
|
|
66
|
-
"supabase"
|
|
65
|
+
"typescript"
|
|
67
66
|
],
|
|
68
67
|
"author": "Oktopus Dev",
|
|
69
68
|
"license": "MIT",
|
|
70
69
|
"repository": {
|
|
71
70
|
"type": "git",
|
|
72
|
-
"url": "https://github.com/
|
|
71
|
+
"url": "https://github.com/OktopusSolutions/social-media-sdk"
|
|
73
72
|
},
|
|
74
73
|
"peerDependencies": {
|
|
75
74
|
"expo-auth-session": ">=5.0.0",
|
package/dist/chunk-36RADUUO.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// core/errors/BaseError.ts
|
|
2
|
-
var BaseError = class extends Error {
|
|
3
|
-
constructor(message, code, details) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.name = this.constructor.name;
|
|
6
|
-
this.code = code;
|
|
7
|
-
this.details = details;
|
|
8
|
-
Error.captureStackTrace(this, this.constructor);
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// core/errors/AuthError.ts
|
|
13
|
-
var AuthError = class extends BaseError {
|
|
14
|
-
constructor(message, code, details) {
|
|
15
|
-
super(message, code, details);
|
|
16
|
-
this.name = "AuthError";
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// core/errors/APIError.ts
|
|
21
|
-
var APIError = class extends BaseError {
|
|
22
|
-
constructor(message, statusCode, code, details) {
|
|
23
|
-
super(message, code, details);
|
|
24
|
-
this.name = "APIError";
|
|
25
|
-
this.statusCode = statusCode;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export { APIError, AuthError, BaseError };
|
|
30
|
-
//# sourceMappingURL=chunk-36RADUUO.mjs.map
|
|
31
|
-
//# sourceMappingURL=chunk-36RADUUO.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../core/errors/BaseError.ts","../core/errors/AuthError.ts","../core/errors/APIError.ts"],"names":[],"mappings":";AAIO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EAKnC,WAAA,CAAY,OAAA,EAAiB,IAAA,EAAwB,OAAA,EAAe;AAClE,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,WAAA,CAAY,IAAA;AAC7B,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAChD;AACF;;;ACVO,IAAM,SAAA,GAAN,cAAwB,SAAA,CAAU;AAAA,EACvC,WAAA,CAAY,OAAA,EAAiB,IAAA,EAAwB,OAAA,EAAe;AAClE,IAAA,KAAA,CAAM,OAAA,EAAS,MAAM,OAAO,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAAA,EACd;AACF;;;ACLO,IAAM,QAAA,GAAN,cAAuB,SAAA,CAAU;AAAA,EACtC,WAAA,CACE,OAAA,EACA,UAAA,EACA,IAAA,EACA,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAA,EAAS,MAAM,OAAO,CAAA;AAC5B,IAAA,IAAA,CAAK,IAAA,GAAO,UAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACpB;AACF","file":"chunk-36RADUUO.mjs","sourcesContent":["/**\n * Base Error - Error base para la librería\n */\n\nexport class BaseError extends Error {\n public code?: string | number;\n public statusCode?: number;\n public details?: any;\n\n constructor(message: string, code?: string | number, details?: any) {\n super(message);\n this.name = this.constructor.name;\n this.code = code;\n this.details = details;\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n\n","/**\n * Auth Error - Authentication errors\n */\n\nimport { BaseError } from './BaseError';\n\nexport class AuthError extends BaseError {\n constructor(message: string, code?: string | number, details?: any) {\n super(message, code, details);\n this.name = 'AuthError';\n }\n}\n\n\n","/**\n * API Error - Errores de API\n */\n\nimport { BaseError } from './BaseError';\n\nexport class APIError extends BaseError {\n constructor(\n message: string,\n statusCode?: number,\n code?: string | number,\n details?: any\n ) {\n super(message, code, details);\n this.name = 'APIError';\n this.statusCode = statusCode;\n }\n}\n\n\n"]}
|