untiktok-api 1.0.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/LICENSE +21 -0
- package/LICENSE.txt +21 -0
- package/README.md +80 -0
- package/dist/api/comment.d.ts +42 -0
- package/dist/api/comment.js +84 -0
- package/dist/api/hashtag.d.ts +52 -0
- package/dist/api/hashtag.js +118 -0
- package/dist/api/playlist.d.ts +53 -0
- package/dist/api/playlist.js +112 -0
- package/dist/api/search.d.ts +38 -0
- package/dist/api/search.js +98 -0
- package/dist/api/sound.d.ts +57 -0
- package/dist/api/sound.js +133 -0
- package/dist/api/trending.d.ts +21 -0
- package/dist/api/trending.js +52 -0
- package/dist/api/user.d.ts +187 -0
- package/dist/api/user.js +498 -0
- package/dist/api/video.d.ts +119 -0
- package/dist/api/video.js +399 -0
- package/dist/exceptions.d.ts +24 -0
- package/dist/exceptions.js +61 -0
- package/dist/helpers.d.ts +23 -0
- package/dist/helpers.js +66 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +35 -0
- package/dist/stealth/index.d.ts +70 -0
- package/dist/stealth/index.js +128 -0
- package/dist/stealth/js/chrome_app.d.ts +1 -0
- package/dist/stealth/js/chrome_app.js +27 -0
- package/dist/stealth/js/chrome_csi.d.ts +1 -0
- package/dist/stealth/js/chrome_csi.js +14 -0
- package/dist/stealth/js/chrome_hairline.d.ts +1 -0
- package/dist/stealth/js/chrome_hairline.js +16 -0
- package/dist/stealth/js/chrome_load_times.d.ts +1 -0
- package/dist/stealth/js/chrome_load_times.js +28 -0
- package/dist/stealth/js/chrome_runtime_script.d.ts +1 -0
- package/dist/stealth/js/chrome_runtime_script.js +84 -0
- package/dist/stealth/js/generate_magic_arrays.d.ts +1 -0
- package/dist/stealth/js/generate_magic_arrays.js +28 -0
- package/dist/stealth/js/iframe_contentWindow.d.ts +1 -0
- package/dist/stealth/js/iframe_contentWindow.js +22 -0
- package/dist/stealth/js/media_codecs.d.ts +1 -0
- package/dist/stealth/js/media_codecs.js +16 -0
- package/dist/stealth/js/navigator_hardwareConcurrency.d.ts +1 -0
- package/dist/stealth/js/navigator_hardwareConcurrency.js +6 -0
- package/dist/stealth/js/navigator_languages.d.ts +1 -0
- package/dist/stealth/js/navigator_languages.js +6 -0
- package/dist/stealth/js/navigator_permissions.d.ts +1 -0
- package/dist/stealth/js/navigator_permissions.js +11 -0
- package/dist/stealth/js/navigator_platform.d.ts +1 -0
- package/dist/stealth/js/navigator_platform.js +8 -0
- package/dist/stealth/js/navigator_plugins_script.d.ts +1 -0
- package/dist/stealth/js/navigator_plugins_script.js +37 -0
- package/dist/stealth/js/navigator_userAgent_script.d.ts +1 -0
- package/dist/stealth/js/navigator_userAgent_script.js +8 -0
- package/dist/stealth/js/navigator_vendor_script.d.ts +1 -0
- package/dist/stealth/js/navigator_vendor_script.js +6 -0
- package/dist/stealth/js/utils_script.d.ts +1 -0
- package/dist/stealth/js/utils_script.js +119 -0
- package/dist/stealth/js/webgl_vendor_script.d.ts +1 -0
- package/dist/stealth/js/webgl_vendor_script.js +16 -0
- package/dist/stealth/js/window_outerdimensions.d.ts +1 -0
- package/dist/stealth/js/window_outerdimensions.js +9 -0
- package/dist/tiktok.d.ts +96 -0
- package/dist/tiktok.js +758 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +6 -0
- package/package.json +41 -0
package/dist/api/user.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// api/user.ts
|
|
4
|
+
// Mirrors TikTokApi/api/user.py
|
|
5
|
+
// ============================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.User = void 0;
|
|
8
|
+
const exceptions_1 = require("../exceptions");
|
|
9
|
+
class User {
|
|
10
|
+
constructor(parent, { username, userId, secUid, data } = {}) {
|
|
11
|
+
this.parent = parent;
|
|
12
|
+
this._updateIdSecUidUsername(userId, secUid, username);
|
|
13
|
+
if (data) {
|
|
14
|
+
this.asDict = data;
|
|
15
|
+
this._extractFromData();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns whether the user is currently live on TikTok.
|
|
20
|
+
* Based on the presence of a non-zero roomId in the user info data.
|
|
21
|
+
*/
|
|
22
|
+
get isLive() {
|
|
23
|
+
return this.roomId !== null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the roomId of the user if they are currently live on TikTok.
|
|
27
|
+
* Returns null if they are not live.
|
|
28
|
+
*/
|
|
29
|
+
get roomId() {
|
|
30
|
+
const data = this.asDict ?? {};
|
|
31
|
+
// Check inside userInfo.user
|
|
32
|
+
if (data["userInfo"]) {
|
|
33
|
+
const user = data["userInfo"]["user"];
|
|
34
|
+
if (user) {
|
|
35
|
+
if (user["roomId"] && user["roomId"] !== "0" && user["roomId"] !== 0) {
|
|
36
|
+
return String(user["roomId"]);
|
|
37
|
+
}
|
|
38
|
+
if (user["room_id"] && user["room_id"] !== "0" && user["room_id"] !== 0) {
|
|
39
|
+
return String(user["room_id"]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Check root level
|
|
44
|
+
if (data["roomId"] && data["roomId"] !== "0" && data["roomId"] !== 0) {
|
|
45
|
+
return String(data["roomId"]);
|
|
46
|
+
}
|
|
47
|
+
if (data["room_id"] && data["room_id"] !== "0" && data["room_id"] !== 0) {
|
|
48
|
+
return String(data["room_id"]);
|
|
49
|
+
}
|
|
50
|
+
// Check if roomData exists (fallback)
|
|
51
|
+
if (data["roomData"] || data["room_data"]) {
|
|
52
|
+
return "unknown_room_id";
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/** Gets the user's display name */
|
|
57
|
+
get nickname() {
|
|
58
|
+
return this._extractUserInfoValue("nickname");
|
|
59
|
+
}
|
|
60
|
+
/** Gets the user's bio / signature */
|
|
61
|
+
get signature() {
|
|
62
|
+
return this._extractUserInfoValue("signature");
|
|
63
|
+
}
|
|
64
|
+
/** Gets whether the user is a verified account */
|
|
65
|
+
get verified() {
|
|
66
|
+
return Boolean(this._extractUserInfoValue("verified"));
|
|
67
|
+
}
|
|
68
|
+
/** Gets whether the user has a private account */
|
|
69
|
+
get isPrivate() {
|
|
70
|
+
return Boolean(this._extractUserInfoValue("privateAccount"));
|
|
71
|
+
}
|
|
72
|
+
/** Gets the user's follower count */
|
|
73
|
+
get followers() {
|
|
74
|
+
return this._extractUserStatsValue("followerCount") || 0;
|
|
75
|
+
}
|
|
76
|
+
/** Gets the user's following count */
|
|
77
|
+
get following() {
|
|
78
|
+
return this._extractUserStatsValue("followingCount") || 0;
|
|
79
|
+
}
|
|
80
|
+
/** Gets the user's total likes (hearts) */
|
|
81
|
+
get likes() {
|
|
82
|
+
return this._extractUserStatsValue("heartCount") || 0;
|
|
83
|
+
}
|
|
84
|
+
/** Gets the user's total video count */
|
|
85
|
+
get videoCount() {
|
|
86
|
+
return this._extractUserStatsValue("videoCount") || 0;
|
|
87
|
+
}
|
|
88
|
+
/** Gets the user's link in bio */
|
|
89
|
+
get bioLink() {
|
|
90
|
+
const bioLinkObj = this._extractUserInfoValue("bioLink");
|
|
91
|
+
return bioLinkObj?.link ?? null;
|
|
92
|
+
}
|
|
93
|
+
/** Gets the user's profile picture URL (largest available) */
|
|
94
|
+
get avatar() {
|
|
95
|
+
return this._extractUserInfoValue("avatarLarger") ||
|
|
96
|
+
this._extractUserInfoValue("avatarMedium") ||
|
|
97
|
+
this._extractUserInfoValue("avatarThumb") || null;
|
|
98
|
+
}
|
|
99
|
+
_extractUserInfoValue(key) {
|
|
100
|
+
const data = this.asDict ?? {};
|
|
101
|
+
if (data["userInfo"]) {
|
|
102
|
+
const user = data["userInfo"]["user"];
|
|
103
|
+
if (user && user[key] !== undefined)
|
|
104
|
+
return user[key];
|
|
105
|
+
}
|
|
106
|
+
return data[key] ?? null;
|
|
107
|
+
}
|
|
108
|
+
_extractUserStatsValue(key) {
|
|
109
|
+
const data = this.asDict ?? {};
|
|
110
|
+
if (data["userInfo"]) {
|
|
111
|
+
const stats = data["userInfo"]["stats"];
|
|
112
|
+
if (stats && stats[key] !== undefined)
|
|
113
|
+
return stats[key];
|
|
114
|
+
}
|
|
115
|
+
if (data["stats"]) {
|
|
116
|
+
const stats = data["stats"];
|
|
117
|
+
if (stats[key] !== undefined)
|
|
118
|
+
return stats[key];
|
|
119
|
+
}
|
|
120
|
+
return data[key] ?? null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns a dictionary of information associated with this User.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const userData = await api.user({ username: 'therock' }).info();
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
async info(kwargs = {}) {
|
|
131
|
+
const username = this.username;
|
|
132
|
+
if (!username) {
|
|
133
|
+
throw new TypeError("You must provide the username when creating this class to use this method.");
|
|
134
|
+
}
|
|
135
|
+
const urlParams = {
|
|
136
|
+
secUid: this.secUid ?? "",
|
|
137
|
+
uniqueId: username,
|
|
138
|
+
msToken: kwargs.msToken,
|
|
139
|
+
};
|
|
140
|
+
const resp = await this.parent.makeRequest({
|
|
141
|
+
url: "https://www.tiktok.com/api/user/detail/",
|
|
142
|
+
params: urlParams,
|
|
143
|
+
headers: kwargs.headers,
|
|
144
|
+
sessionIndex: kwargs.sessionIndex,
|
|
145
|
+
});
|
|
146
|
+
if (resp == null) {
|
|
147
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
148
|
+
}
|
|
149
|
+
this.asDict = resp;
|
|
150
|
+
this._extractFromData();
|
|
151
|
+
return resp;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns a user's playlists.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* for await (const playlist of api.user({ username: 'therock' }).playlists()) {
|
|
159
|
+
* console.log(playlist.name);
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
async *playlists(count = 20, cursor = 0, kwargs = {}) {
|
|
164
|
+
if (!this.secUid) {
|
|
165
|
+
await this.info(kwargs);
|
|
166
|
+
}
|
|
167
|
+
let found = 0;
|
|
168
|
+
while (found < count) {
|
|
169
|
+
const params = {
|
|
170
|
+
secUid: this.secUid,
|
|
171
|
+
count: Math.min(count, 30),
|
|
172
|
+
cursor,
|
|
173
|
+
};
|
|
174
|
+
const resp = await this.parent.makeRequest({
|
|
175
|
+
url: "https://www.tiktok.com/api/user/playlist",
|
|
176
|
+
params,
|
|
177
|
+
headers: kwargs.headers,
|
|
178
|
+
sessionIndex: kwargs.sessionIndex,
|
|
179
|
+
});
|
|
180
|
+
if (resp == null) {
|
|
181
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
182
|
+
}
|
|
183
|
+
const playList = resp["playList"] ?? [];
|
|
184
|
+
for (const pl of playList) {
|
|
185
|
+
yield this.parent.playlist({ data: pl });
|
|
186
|
+
found++;
|
|
187
|
+
}
|
|
188
|
+
if (!resp["hasMore"])
|
|
189
|
+
return;
|
|
190
|
+
cursor = resp["cursor"];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Returns a user's videos.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* for await (const video of api.user({ username: 'davidteathercodes' }).videos()) {
|
|
199
|
+
* console.log(video.id);
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
async *videos(count = 30, cursor = 0, kwargs = {}) {
|
|
204
|
+
if (!this.secUid) {
|
|
205
|
+
await this.info(kwargs);
|
|
206
|
+
}
|
|
207
|
+
let found = 0;
|
|
208
|
+
while (found < count) {
|
|
209
|
+
const params = {
|
|
210
|
+
secUid: this.secUid,
|
|
211
|
+
count: 30,
|
|
212
|
+
cursor,
|
|
213
|
+
};
|
|
214
|
+
const resp = await this.parent.makeRequest({
|
|
215
|
+
url: "https://www.tiktok.com/api/post/item_list/",
|
|
216
|
+
params,
|
|
217
|
+
headers: kwargs.headers,
|
|
218
|
+
sessionIndex: kwargs.sessionIndex,
|
|
219
|
+
});
|
|
220
|
+
if (resp == null) {
|
|
221
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
222
|
+
}
|
|
223
|
+
const itemList = resp["itemList"] ?? [];
|
|
224
|
+
for (const item of itemList) {
|
|
225
|
+
yield this.parent.video({ data: item });
|
|
226
|
+
found++;
|
|
227
|
+
}
|
|
228
|
+
if (!resp["hasMore"])
|
|
229
|
+
return;
|
|
230
|
+
cursor = resp["cursor"];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns a user's pinned videos.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* for await (const video of api.user({ username: 'davidteathercodes' }).pinned()) {
|
|
239
|
+
* console.log(video.id);
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
async *pinned(count = 3, kwargs = {}) {
|
|
244
|
+
// Pinned videos are always sent at the top of the videos feed
|
|
245
|
+
// We fetch a batch of videos and explicitly filter for pinned flags
|
|
246
|
+
let found = 0;
|
|
247
|
+
// We only need to check the first few videos since pinned are always at top
|
|
248
|
+
for await (const video of this.videos(10, 0, kwargs)) {
|
|
249
|
+
const data = video.asDict ?? {};
|
|
250
|
+
const isPinned = data["isPinned"] === true ||
|
|
251
|
+
data["is_pinned"] === true ||
|
|
252
|
+
data["isTop"] === true ||
|
|
253
|
+
data["is_top"] === true ||
|
|
254
|
+
data["isTopItem"] === true ||
|
|
255
|
+
data["is_top_item"] === true;
|
|
256
|
+
if (isPinned) {
|
|
257
|
+
yield video;
|
|
258
|
+
found++;
|
|
259
|
+
if (found >= count)
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Returns a user's liked posts (if public).
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* for await (const like of api.user({ username: 'davidteathercodes' }).liked()) {
|
|
270
|
+
* console.log(like.id);
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
async *liked(count = 30, cursor = 0, kwargs = {}) {
|
|
275
|
+
if (!this.secUid) {
|
|
276
|
+
await this.info(kwargs);
|
|
277
|
+
}
|
|
278
|
+
let found = 0;
|
|
279
|
+
while (found < count) {
|
|
280
|
+
const params = {
|
|
281
|
+
secUid: this.secUid,
|
|
282
|
+
count: 30,
|
|
283
|
+
cursor,
|
|
284
|
+
};
|
|
285
|
+
const resp = await this.parent.makeRequest({
|
|
286
|
+
url: "https://www.tiktok.com/api/favorite/item_list",
|
|
287
|
+
params,
|
|
288
|
+
headers: kwargs.headers,
|
|
289
|
+
sessionIndex: kwargs.sessionIndex,
|
|
290
|
+
});
|
|
291
|
+
if (resp == null) {
|
|
292
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
293
|
+
}
|
|
294
|
+
const itemList = resp["itemList"] ?? [];
|
|
295
|
+
for (const item of itemList) {
|
|
296
|
+
yield this.parent.video({ data: item });
|
|
297
|
+
found++;
|
|
298
|
+
}
|
|
299
|
+
if (!resp["hasMore"])
|
|
300
|
+
return;
|
|
301
|
+
cursor = resp["cursor"];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Returns a user's reposted videos (if available).
|
|
306
|
+
* Note: TikTok might restrict visibility based on authentication or region.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* for await (const repost of api.user({ username: 'davidteathercodes' }).reposts()) {
|
|
311
|
+
* console.log(repost.id);
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
async *reposts(count = 30, cursor = 0, kwargs = {}) {
|
|
316
|
+
if (!this.secUid) {
|
|
317
|
+
await this.info(kwargs);
|
|
318
|
+
}
|
|
319
|
+
// "well now you can stalk your crush repost without knowing" - Al Ghozali Ramadhan
|
|
320
|
+
let found = 0;
|
|
321
|
+
while (found < count) {
|
|
322
|
+
const params = {
|
|
323
|
+
secUid: this.secUid,
|
|
324
|
+
count: 30,
|
|
325
|
+
cursor,
|
|
326
|
+
};
|
|
327
|
+
const resp = await this.parent.makeRequest({
|
|
328
|
+
url: "https://www.tiktok.com/api/repost/item_list/",
|
|
329
|
+
params,
|
|
330
|
+
headers: kwargs.headers,
|
|
331
|
+
sessionIndex: kwargs.sessionIndex,
|
|
332
|
+
});
|
|
333
|
+
if (resp == null) {
|
|
334
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
335
|
+
}
|
|
336
|
+
const itemList = resp["itemList"] ?? [];
|
|
337
|
+
for (const item of itemList) {
|
|
338
|
+
yield this.parent.video({ data: item });
|
|
339
|
+
found++;
|
|
340
|
+
}
|
|
341
|
+
if (!resp["hasMore"])
|
|
342
|
+
return;
|
|
343
|
+
cursor = resp["cursor"];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Returns a user's favorited/bookmarked videos (Collections).
|
|
348
|
+
* Note: This relies entirely on the user's privacy settings.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* for await (const fav of api.user({ username: 'davidteathercodes' }).favorited()) {
|
|
353
|
+
* console.log(fav.id);
|
|
354
|
+
* }
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
async *favorited(count = 30, cursor = 0, kwargs = {}) {
|
|
358
|
+
if (!this.secUid) {
|
|
359
|
+
await this.info(kwargs);
|
|
360
|
+
}
|
|
361
|
+
let found = 0;
|
|
362
|
+
while (found < count) {
|
|
363
|
+
const params = {
|
|
364
|
+
secUid: this.secUid,
|
|
365
|
+
count: 30,
|
|
366
|
+
cursor,
|
|
367
|
+
};
|
|
368
|
+
const resp = await this.parent.makeRequest({
|
|
369
|
+
url: "https://www.tiktok.com/api/user/collect/item_list/",
|
|
370
|
+
params,
|
|
371
|
+
headers: kwargs.headers,
|
|
372
|
+
sessionIndex: kwargs.sessionIndex,
|
|
373
|
+
});
|
|
374
|
+
if (resp == null) {
|
|
375
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
376
|
+
}
|
|
377
|
+
const itemList = resp["itemList"] ?? [];
|
|
378
|
+
for (const item of itemList) {
|
|
379
|
+
yield this.parent.video({ data: item });
|
|
380
|
+
found++;
|
|
381
|
+
}
|
|
382
|
+
if (!resp["hasMore"])
|
|
383
|
+
return;
|
|
384
|
+
cursor = resp["cursor"];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Returns a user's followers list.
|
|
389
|
+
* Note: This endpoint is heavily guarded and usually requires a logged-in session (cookies).
|
|
390
|
+
* It may also quickly return errors or bot challenges.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* for await (const follower of api.user({ username: 'davidteathercodes' }).followersList()) {
|
|
395
|
+
* console.log(follower.username);
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
async *followersList(count = 30, cursor = 0, kwargs = {}) {
|
|
400
|
+
if (!this.secUid) {
|
|
401
|
+
await this.info(kwargs);
|
|
402
|
+
}
|
|
403
|
+
let found = 0;
|
|
404
|
+
while (found < count) {
|
|
405
|
+
const params = {
|
|
406
|
+
secUid: this.secUid,
|
|
407
|
+
count: 30,
|
|
408
|
+
minCursor: cursor,
|
|
409
|
+
maxCursor: cursor,
|
|
410
|
+
};
|
|
411
|
+
const resp = await this.parent.makeRequest({
|
|
412
|
+
url: "https://www.tiktok.com/api/user/list/",
|
|
413
|
+
params,
|
|
414
|
+
headers: kwargs.headers,
|
|
415
|
+
sessionIndex: kwargs.sessionIndex,
|
|
416
|
+
});
|
|
417
|
+
if (resp == null) {
|
|
418
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
419
|
+
}
|
|
420
|
+
const userList = resp["userList"] ?? [];
|
|
421
|
+
for (const item of userList) {
|
|
422
|
+
yield this.parent.user({ data: item });
|
|
423
|
+
found++;
|
|
424
|
+
}
|
|
425
|
+
if (!resp["hasMore"])
|
|
426
|
+
return;
|
|
427
|
+
cursor = resp["minCursor"] || resp["maxCursor"];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Returns a user's following list.
|
|
432
|
+
* Note: Like followers, this is heavily guarded and requires authentication.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* for await (const following of api.user({ username: 'davidteathercodes' }).followingList()) {
|
|
437
|
+
* console.log(following.username);
|
|
438
|
+
* }
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
async *followingList(count = 30, cursor = 0, kwargs = {}) {
|
|
442
|
+
if (!this.secUid) {
|
|
443
|
+
await this.info(kwargs);
|
|
444
|
+
}
|
|
445
|
+
let found = 0;
|
|
446
|
+
while (found < count) {
|
|
447
|
+
const params = {
|
|
448
|
+
secUid: this.secUid,
|
|
449
|
+
count: 30,
|
|
450
|
+
minCursor: cursor,
|
|
451
|
+
maxCursor: cursor,
|
|
452
|
+
};
|
|
453
|
+
const resp = await this.parent.makeRequest({
|
|
454
|
+
url: "https://www.tiktok.com/api/user/following/",
|
|
455
|
+
params,
|
|
456
|
+
headers: kwargs.headers,
|
|
457
|
+
sessionIndex: kwargs.sessionIndex,
|
|
458
|
+
});
|
|
459
|
+
if (resp == null) {
|
|
460
|
+
throw new exceptions_1.InvalidResponseException(resp, "TikTok returned an invalid response.");
|
|
461
|
+
}
|
|
462
|
+
const userList = resp["userList"] ?? [];
|
|
463
|
+
for (const item of userList) {
|
|
464
|
+
yield this.parent.user({ data: item });
|
|
465
|
+
found++;
|
|
466
|
+
}
|
|
467
|
+
if (!resp["hasMore"])
|
|
468
|
+
return;
|
|
469
|
+
cursor = resp["minCursor"] || resp["maxCursor"];
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
_extractFromData() {
|
|
473
|
+
const data = this.asDict ?? {};
|
|
474
|
+
const keys = Object.keys(data);
|
|
475
|
+
if (keys.includes("userInfo")) {
|
|
476
|
+
const userInfo = data["userInfo"]["user"];
|
|
477
|
+
this._updateIdSecUidUsername(userInfo["id"], userInfo["secUid"], userInfo["uniqueId"]);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
this._updateIdSecUidUsername(data["id"], data["secUid"], data["uniqueId"]);
|
|
481
|
+
}
|
|
482
|
+
if (!this.username || !this.userId || !this.secUid) {
|
|
483
|
+
this.parent.logger.error(`Failed to create User with data: ${JSON.stringify(data)}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
_updateIdSecUidUsername(id, secUid, username) {
|
|
487
|
+
if (id != null)
|
|
488
|
+
this.userId = id;
|
|
489
|
+
if (secUid != null)
|
|
490
|
+
this.secUid = secUid;
|
|
491
|
+
if (username != null)
|
|
492
|
+
this.username = username;
|
|
493
|
+
}
|
|
494
|
+
toString() {
|
|
495
|
+
return `TikTokApi.user(username='${this.username}', user_id='${this.userId}', sec_uid='${this.secUid}')`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
exports.User = User;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { TikTokApi } from "../tiktok";
|
|
2
|
+
import type { User } from "./user";
|
|
3
|
+
import type { Sound } from "./sound";
|
|
4
|
+
import type { Hashtag } from "./hashtag";
|
|
5
|
+
import type { Comment } from "./comment";
|
|
6
|
+
export interface VideoOptions {
|
|
7
|
+
id?: string | null;
|
|
8
|
+
url?: string | null;
|
|
9
|
+
data?: Record<string, unknown> | null;
|
|
10
|
+
sessionIndex?: number;
|
|
11
|
+
proxy?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare class Video {
|
|
14
|
+
/** Static reference to the parent TikTokApi instance */
|
|
15
|
+
parent: TikTokApi;
|
|
16
|
+
/** TikTok's ID of the Video */
|
|
17
|
+
id?: string;
|
|
18
|
+
/** The URL of the Video */
|
|
19
|
+
url?: string;
|
|
20
|
+
/** The creation time of the Video */
|
|
21
|
+
createTime?: Date;
|
|
22
|
+
/** TikTok's stats for the Video */
|
|
23
|
+
stats?: Record<string, unknown>;
|
|
24
|
+
/** The User who created the Video */
|
|
25
|
+
author?: User;
|
|
26
|
+
/** The Sound associated with the Video */
|
|
27
|
+
sound?: Sound;
|
|
28
|
+
/** A list of Hashtags on the Video */
|
|
29
|
+
hashtags?: Hashtag[];
|
|
30
|
+
/** The raw data associated with this Video */
|
|
31
|
+
asDict?: Record<string, unknown>;
|
|
32
|
+
/** Gets the description/caption of the video */
|
|
33
|
+
get description(): string | null;
|
|
34
|
+
/** Gets the play/view count of the video */
|
|
35
|
+
get plays(): number;
|
|
36
|
+
/** Gets the digg/like count of the video */
|
|
37
|
+
get likes(): number;
|
|
38
|
+
/** Gets the comment count of the video */
|
|
39
|
+
get commentsCount(): number;
|
|
40
|
+
/** Gets the share count of the video */
|
|
41
|
+
get shares(): number;
|
|
42
|
+
/** Gets the collect/save count of the video */
|
|
43
|
+
get saves(): number;
|
|
44
|
+
/** Gets whether the video is pinned by the creator */
|
|
45
|
+
get isPinned(): boolean;
|
|
46
|
+
constructor(parent: TikTokApi, { id, url, data, sessionIndex, proxy }?: VideoOptions);
|
|
47
|
+
/**
|
|
48
|
+
* Async factory that follows redirects to resolve the video ID from a short URL.
|
|
49
|
+
* Use this instead of `new Video({ url })` when you have a short/redirect URL.
|
|
50
|
+
*/
|
|
51
|
+
static fromUrl(parent: TikTokApi, url: string, kwargs?: {
|
|
52
|
+
sessionIndex?: number;
|
|
53
|
+
proxy?: string;
|
|
54
|
+
}): Promise<Video>;
|
|
55
|
+
/**
|
|
56
|
+
* Returns a dictionary of all data associated with a TikTok Video.
|
|
57
|
+
* Note: This is slow since it requires an HTTP request.
|
|
58
|
+
*
|
|
59
|
+
* Python uses `requests.get`; TS uses `axios.get` (equivalent sync-style).
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const info = await api.video({ url: 'https://www.tiktok.com/@.../video/...' }).info();
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
info(kwargs?: {
|
|
67
|
+
headers?: Record<string, string>;
|
|
68
|
+
sessionIndex?: number;
|
|
69
|
+
proxy?: string;
|
|
70
|
+
}): Promise<Record<string, unknown>>;
|
|
71
|
+
/**
|
|
72
|
+
* Returns the raw bytes of a TikTok Video.
|
|
73
|
+
*
|
|
74
|
+
* Python uses `requests.get` / `httpx.AsyncClient` for streaming.
|
|
75
|
+
* TS uses `axios` equivalents.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const buf = await video.bytes() as Buffer;
|
|
80
|
+
* fs.writeFileSync('video.mp4', buf);
|
|
81
|
+
*
|
|
82
|
+
* // Streaming
|
|
83
|
+
* for await (const chunk of await video.bytes({ stream: true }) as AsyncGenerator<Buffer>) { ... }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
bytes(options?: {
|
|
87
|
+
stream?: boolean;
|
|
88
|
+
} & Record<string, unknown>): Promise<Buffer | AsyncGenerator<Buffer>>;
|
|
89
|
+
/**
|
|
90
|
+
* Returns the comments of a TikTok Video.
|
|
91
|
+
*
|
|
92
|
+
* Python key: `has_more` (snake_case) — preserved in TS.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* for await (const comment of video.comments(20)) { ... }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
comments(count?: number, cursor?: number, kwargs?: {
|
|
100
|
+
headers?: Record<string, string>;
|
|
101
|
+
sessionIndex?: number;
|
|
102
|
+
}): AsyncGenerator<Comment>;
|
|
103
|
+
/**
|
|
104
|
+
* Returns related videos of a TikTok Video.
|
|
105
|
+
* Note: Python's related_videos does NOT increment `found` after the inner loop — bug or intentional.
|
|
106
|
+
* We preserve that exact behaviour (no double-increment).
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* for await (const rel of video.relatedVideos(30)) { ... }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
relatedVideos(count?: number, cursor?: number, kwargs?: {
|
|
114
|
+
headers?: Record<string, string>;
|
|
115
|
+
sessionIndex?: number;
|
|
116
|
+
}): AsyncGenerator<Video>;
|
|
117
|
+
private _extractFromData;
|
|
118
|
+
toString(): string;
|
|
119
|
+
}
|