tt-help-cli-ycl 1.3.47 → 1.3.49
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 +33 -33
- package/cli.js +9 -9
- package/package.json +52 -52
- package/scripts/run-explore copy.bat +101 -101
- package/scripts/run-explore.bat +134 -134
- package/scripts/run-explore.ps1 +159 -159
- package/scripts/run-explore.sh +121 -121
- package/scripts/test-captcha-lib.mjs +68 -0
- package/scripts/test-captcha.mjs +81 -0
- package/scripts/test-incognito-lib.mjs +36 -0
- package/scripts/test-login-state.mjs +128 -0
- package/scripts/test-safe-click.mjs +45 -0
- package/scripts/test-watch-db-smoke.mjs +246 -0
- package/src/cli/attach.js +331 -331
- package/src/cli/auto.js +265 -265
- package/src/cli/comments.js +620 -620
- package/src/cli/config.js +170 -170
- package/src/cli/db-import.js +51 -51
- package/src/cli/explore.js +555 -555
- package/src/cli/open.js +111 -111
- package/src/cli/progress.js +111 -111
- package/src/cli/refresh.js +288 -288
- package/src/cli/scrape.js +47 -47
- package/src/cli/utils.js +18 -18
- package/src/cli/videos.js +41 -41
- package/src/cli/videostats.js +196 -196
- package/src/cli/watch.js +30 -30
- package/src/lib/api-interceptor.js +161 -161
- package/src/lib/args.js +809 -809
- package/src/lib/browser/anti-detect.js +23 -23
- package/src/lib/browser/cdp.js +261 -261
- package/src/lib/browser/health-checker.js +114 -114
- package/src/lib/browser/launch.js +43 -43
- package/src/lib/browser/page.js +184 -184
- package/src/lib/constants.js +297 -297
- package/src/lib/delay.js +54 -54
- package/src/lib/explore-fetch.js +118 -118
- package/src/lib/fetcher.js +45 -45
- package/src/lib/filter.js +66 -66
- package/src/lib/io.js +54 -54
- package/src/lib/output.js +80 -80
- package/src/lib/page-error-detector.js +109 -109
- package/src/lib/parse-ssr.mjs +69 -69
- package/src/lib/parser.js +47 -47
- package/src/lib/retry.js +45 -45
- package/src/lib/scrape.js +90 -90
- package/src/lib/target-locations.js +61 -61
- package/src/lib/tiktok-scraper.mjs +91 -40
- package/src/lib/url.js +52 -52
- package/src/main.js +73 -73
- package/src/npm-main.js +70 -70
- package/src/results/user-videos-bar.lar.lar.moeta.json +37 -0
- package/src/scraper/auto-core.js +203 -203
- package/src/scraper/core.js +255 -255
- package/src/scraper/explore-core.js +208 -208
- package/src/scraper/modules/captcha-handler.js +114 -114
- package/src/scraper/modules/follow-extractor.js +250 -250
- package/src/scraper/modules/guess-extractor.js +51 -51
- package/src/scraper/modules/page-helpers.js +48 -48
- package/src/scraper/refresh-core.js +213 -213
- package/src/videos/core.js +143 -143
- package/src/watch/data-store.js +2980 -2980
- package/src/watch/public/index.html +2355 -2355
- package/src/watch/server.js +727 -727
|
@@ -9,7 +9,7 @@ const DEFAULT_WAF_TTL = 120000;
|
|
|
9
9
|
const DEFAULT_WARM_URL = "https://www.tiktok.com/@nike";
|
|
10
10
|
const BROWSER_CLOSE_TIMEOUT = 5000;
|
|
11
11
|
const DEFAULT_MAX_REQUESTS_PER_PAGE = 50;
|
|
12
|
-
const FALLBACK_PROFILE_PORT =
|
|
12
|
+
const FALLBACK_PROFILE_PORT = 9222;
|
|
13
13
|
|
|
14
14
|
function delay(ms) {
|
|
15
15
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -25,12 +25,6 @@ function getFallbackProfileDir() {
|
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function fallbackProfileExists() {
|
|
29
|
-
const fs = require("fs");
|
|
30
|
-
const dir = getFallbackProfileDir();
|
|
31
|
-
return fs.existsSync(dir);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
28
|
class PageSlot {
|
|
35
29
|
constructor(page) {
|
|
36
30
|
this.page = page;
|
|
@@ -87,7 +81,10 @@ async function initContext(executablePath, poolSize, userDataDir) {
|
|
|
87
81
|
const slots = [];
|
|
88
82
|
|
|
89
83
|
if (userDataDir) {
|
|
90
|
-
context = await chromium.launchPersistentContext(
|
|
84
|
+
context = await chromium.launchPersistentContext(
|
|
85
|
+
userDataDir,
|
|
86
|
+
createLaunchOptions(executablePath),
|
|
87
|
+
);
|
|
91
88
|
const existing = context.pages();
|
|
92
89
|
if (existing.length > 0) {
|
|
93
90
|
slots.push(new PageSlot(existing[0]));
|
|
@@ -136,7 +133,11 @@ export class TikTokScraper {
|
|
|
136
133
|
"未找到本地浏览器(Chrome/Edge),请先安装浏览器或执行 npx playwright install",
|
|
137
134
|
);
|
|
138
135
|
}
|
|
139
|
-
const { browser, context, slots } = await initContext(
|
|
136
|
+
const { browser, context, slots } = await initContext(
|
|
137
|
+
executablePath,
|
|
138
|
+
this.poolSize,
|
|
139
|
+
null,
|
|
140
|
+
);
|
|
140
141
|
this.browser = browser;
|
|
141
142
|
this.context = context;
|
|
142
143
|
this.slots = slots;
|
|
@@ -145,16 +146,18 @@ export class TikTokScraper {
|
|
|
145
146
|
|
|
146
147
|
async _ensureFallback(executablePath) {
|
|
147
148
|
if (this._fallbackContext) return;
|
|
148
|
-
if (!fallbackProfileExists()) {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
149
|
const fallbackDir = getFallbackProfileDir();
|
|
152
|
-
console.error(
|
|
153
|
-
|
|
150
|
+
console.error(
|
|
151
|
+
`[TikTokScraper] 无登录态获取失败,fallback 到登录态 profile: ${fallbackDir}`,
|
|
152
|
+
);
|
|
153
|
+
const { browser, context, slots } = await initContext(
|
|
154
|
+
executablePath,
|
|
155
|
+
1,
|
|
156
|
+
fallbackDir,
|
|
157
|
+
);
|
|
154
158
|
this._fallbackBrowser = browser;
|
|
155
159
|
this._fallbackContext = context;
|
|
156
160
|
this._fallbackSlots = slots;
|
|
157
|
-
return true;
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
async close() {
|
|
@@ -168,7 +171,9 @@ export class TikTokScraper {
|
|
|
168
171
|
});
|
|
169
172
|
await Promise.race([
|
|
170
173
|
closePromise,
|
|
171
|
-
delay(BROWSER_CLOSE_TIMEOUT).then(() => {
|
|
174
|
+
delay(BROWSER_CLOSE_TIMEOUT).then(() => {
|
|
175
|
+
closeTimedOut = true;
|
|
176
|
+
}),
|
|
172
177
|
]);
|
|
173
178
|
if (closeTimedOut) {
|
|
174
179
|
console.error(
|
|
@@ -182,7 +187,9 @@ export class TikTokScraper {
|
|
|
182
187
|
await closeAll(this.browser);
|
|
183
188
|
// fallback 的 context 是 launchPersistentContext 返回的,直接 close
|
|
184
189
|
if (this._fallbackContext) {
|
|
185
|
-
try {
|
|
190
|
+
try {
|
|
191
|
+
await this._fallbackContext.close();
|
|
192
|
+
} catch {}
|
|
186
193
|
}
|
|
187
194
|
this.browser = null;
|
|
188
195
|
this.context = null;
|
|
@@ -237,7 +244,10 @@ export class TikTokScraper {
|
|
|
237
244
|
|
|
238
245
|
async _ensurePage(slot, context) {
|
|
239
246
|
try {
|
|
240
|
-
if (
|
|
247
|
+
if (
|
|
248
|
+
!slot.page.isClosed() &&
|
|
249
|
+
slot.requestCount < this.maxRequestsPerPage
|
|
250
|
+
) {
|
|
241
251
|
return slot.page;
|
|
242
252
|
}
|
|
243
253
|
} catch {}
|
|
@@ -248,23 +258,51 @@ export class TikTokScraper {
|
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
async _fetchViewSource(url, ctx) {
|
|
251
|
-
const slots =
|
|
252
|
-
|
|
253
|
-
|
|
261
|
+
const slots =
|
|
262
|
+
ctx === this._fallbackContext ? this._fallbackSlots : this.slots;
|
|
263
|
+
const slotIdx =
|
|
264
|
+
ctx === this._fallbackContext ? this._fallbackSlotIdx : this.slotIdx;
|
|
265
|
+
const slot = slots[slotIdx % slots.length];
|
|
266
|
+
if (ctx === this._fallbackContext) {
|
|
267
|
+
this._fallbackSlotIdx++;
|
|
268
|
+
} else {
|
|
269
|
+
this.slotIdx++;
|
|
270
|
+
}
|
|
254
271
|
|
|
255
272
|
try {
|
|
256
273
|
return await slot.lock.run(async () => {
|
|
257
274
|
const page = await this._ensurePage(slot, ctx);
|
|
258
|
-
|
|
275
|
+
|
|
259
276
|
if (ctx === this.context && this.isWarmExpired()) {
|
|
260
277
|
await this.warmWaf();
|
|
261
278
|
}
|
|
262
|
-
|
|
279
|
+
|
|
280
|
+
await page.goto("view-source:" + url, {
|
|
263
281
|
waitUntil: "domcontentloaded",
|
|
264
282
|
timeout: 15000,
|
|
265
283
|
});
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
|
|
285
|
+
const content = await page.evaluate(() => {
|
|
286
|
+
const rows = document.querySelectorAll("tr");
|
|
287
|
+
let content = "";
|
|
288
|
+
rows.forEach((r) => {
|
|
289
|
+
const lc = r.querySelector(".line-content");
|
|
290
|
+
if (lc) content += lc.textContent + "\n";
|
|
291
|
+
});
|
|
292
|
+
return content;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// 导航到 about:blank 释放当前页面的 DOM 和 JS 堆
|
|
296
|
+
await page
|
|
297
|
+
.goto("about:blank", {
|
|
298
|
+
waitUntil: "domcontentloaded",
|
|
299
|
+
timeout: 5000,
|
|
300
|
+
})
|
|
301
|
+
.catch(() => {});
|
|
302
|
+
|
|
303
|
+
slot.requestCount += 1;
|
|
304
|
+
|
|
305
|
+
return content;
|
|
268
306
|
});
|
|
269
307
|
} catch (e) {
|
|
270
308
|
console.error(`[TikTokScraper] _fetchViewSource failed: ${e.message}`);
|
|
@@ -277,38 +315,51 @@ export class TikTokScraper {
|
|
|
277
315
|
? username.slice(1)
|
|
278
316
|
: username;
|
|
279
317
|
const url = `https://www.tiktok.com/@${normalizedUsername}`;
|
|
280
|
-
const executablePath = detectBrowser();
|
|
281
318
|
|
|
282
319
|
// 第一趟:无登录态
|
|
283
320
|
let rawHtml = await this._fetchViewSource(url, this.context);
|
|
284
321
|
let result = rawHtml ? parseUserInfo(rawHtml) : null;
|
|
285
322
|
|
|
286
|
-
//
|
|
323
|
+
// 解析失败:先尝试 warmWaf 后重试
|
|
287
324
|
if (!result) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
325
|
+
try {
|
|
326
|
+
await this.warmWaf();
|
|
327
|
+
} catch {}
|
|
328
|
+
rawHtml = await this._fetchViewSource(url, this.context);
|
|
329
|
+
result = rawHtml ? parseUserInfo(rawHtml) : null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 仍然失败:fallback 到登录态
|
|
333
|
+
if (!result) {
|
|
334
|
+
const executablePath = detectBrowser();
|
|
335
|
+
await this._ensureFallback(executablePath);
|
|
336
|
+
rawHtml = await this._fetchViewSource(url, this._fallbackContext);
|
|
337
|
+
result = rawHtml ? parseUserInfo(rawHtml) : null;
|
|
293
338
|
}
|
|
294
339
|
|
|
295
340
|
return result || null;
|
|
296
341
|
}
|
|
297
342
|
|
|
298
343
|
async getVideoInfo(videoUrl) {
|
|
299
|
-
const executablePath = detectBrowser();
|
|
300
|
-
|
|
301
344
|
// 第一趟:无登录态
|
|
302
345
|
let rawHtml = await this._fetchViewSource(videoUrl, this.context);
|
|
303
346
|
let result = rawHtml ? parseVideoInfo(rawHtml) : null;
|
|
304
347
|
|
|
305
|
-
//
|
|
348
|
+
// 解析失败:先尝试 warmWaf 后重试
|
|
306
349
|
if (!result) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
350
|
+
try {
|
|
351
|
+
await this.warmWaf();
|
|
352
|
+
} catch {}
|
|
353
|
+
rawHtml = await this._fetchViewSource(videoUrl, this.context);
|
|
354
|
+
result = rawHtml ? parseVideoInfo(rawHtml) : null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 仍然失败:fallback 到登录态
|
|
358
|
+
if (!result) {
|
|
359
|
+
const executablePath = detectBrowser();
|
|
360
|
+
await this._ensureFallback(executablePath);
|
|
361
|
+
rawHtml = await this._fetchViewSource(videoUrl, this._fallbackContext);
|
|
362
|
+
result = rawHtml ? parseVideoInfo(rawHtml) : null;
|
|
312
363
|
}
|
|
313
364
|
|
|
314
365
|
return result || null;
|
package/src/lib/url.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
const BASE_URL = 'https://www.tiktok.com';
|
|
2
|
-
|
|
3
|
-
export function extractUniqueId(url) {
|
|
4
|
-
const m = url.match(/\/@([\w.-]+)/);
|
|
5
|
-
return m ? m[1] : null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function extractVideoId(url) {
|
|
9
|
-
const m = url.match(/\/video\/(\d+)/);
|
|
10
|
-
return m ? m[1] : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function normalizeUsername(input) {
|
|
14
|
-
return (input || '').replace(/^@/, '');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function toProfileUrl(handle) {
|
|
18
|
-
const clean = normalizeUsername(handle);
|
|
19
|
-
return `${BASE_URL}/@${clean}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function toVideoUrl(handle, videoId) {
|
|
23
|
-
const clean = normalizeUsername(handle);
|
|
24
|
-
return `${BASE_URL}/@${clean}/video/${videoId}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function ensureAbsoluteUrl(href) {
|
|
28
|
-
if (href.startsWith('http')) return href;
|
|
29
|
-
return `${BASE_URL}${href}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function isProfileUrl(url) {
|
|
33
|
-
return /\/@[\w.-]+(?:$|[?#])/.test(url);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function isVideoUrl(url) {
|
|
37
|
-
return /\/video\/\d+/.test(url);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function extractDisplayPath(url) {
|
|
41
|
-
try {
|
|
42
|
-
const parts = new URL(url).pathname.split('/').filter(Boolean);
|
|
43
|
-
return parts.slice(-2).join('/');
|
|
44
|
-
} catch {
|
|
45
|
-
return url;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function extractAuthorFromVideoUrl(url) {
|
|
50
|
-
const m = url.match(/@([^/]+)\/video/);
|
|
51
|
-
return m ? '@' + m[1] : null;
|
|
52
|
-
}
|
|
1
|
+
const BASE_URL = 'https://www.tiktok.com';
|
|
2
|
+
|
|
3
|
+
export function extractUniqueId(url) {
|
|
4
|
+
const m = url.match(/\/@([\w.-]+)/);
|
|
5
|
+
return m ? m[1] : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function extractVideoId(url) {
|
|
9
|
+
const m = url.match(/\/video\/(\d+)/);
|
|
10
|
+
return m ? m[1] : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeUsername(input) {
|
|
14
|
+
return (input || '').replace(/^@/, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toProfileUrl(handle) {
|
|
18
|
+
const clean = normalizeUsername(handle);
|
|
19
|
+
return `${BASE_URL}/@${clean}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function toVideoUrl(handle, videoId) {
|
|
23
|
+
const clean = normalizeUsername(handle);
|
|
24
|
+
return `${BASE_URL}/@${clean}/video/${videoId}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ensureAbsoluteUrl(href) {
|
|
28
|
+
if (href.startsWith('http')) return href;
|
|
29
|
+
return `${BASE_URL}${href}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isProfileUrl(url) {
|
|
33
|
+
return /\/@[\w.-]+(?:$|[?#])/.test(url);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isVideoUrl(url) {
|
|
37
|
+
return /\/video\/\d+/.test(url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function extractDisplayPath(url) {
|
|
41
|
+
try {
|
|
42
|
+
const parts = new URL(url).pathname.split('/').filter(Boolean);
|
|
43
|
+
return parts.slice(-2).join('/');
|
|
44
|
+
} catch {
|
|
45
|
+
return url;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function extractAuthorFromVideoUrl(url) {
|
|
50
|
+
const m = url.match(/@([^/]+)\/video/);
|
|
51
|
+
return m ? '@' + m[1] : null;
|
|
52
|
+
}
|
package/src/main.js
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { parseArgs } from "./lib/args.js";
|
|
2
|
-
import { proxy, HELP_TEXT, getConfigText } from "./lib/constants.js";
|
|
3
|
-
import { handleInfo } from "./cli/info.js";
|
|
4
|
-
import { handleExplore } from "./cli/explore.js";
|
|
5
|
-
import { handleAttach } from "./cli/attach.js";
|
|
6
|
-
import { handleWatch } from "./cli/watch.js";
|
|
7
|
-
import { handleConfig, showConfig, showUsage, version } from "./cli/config.js";
|
|
8
|
-
import { handleOpen } from "./cli/open.js";
|
|
9
|
-
import { handleComments } from "./cli/comments.js";
|
|
10
|
-
import { handleVideoStats } from "./cli/videostats.js";
|
|
11
|
-
import { handleDbImport } from "./cli/db-import.js";
|
|
12
|
-
import { handleWebserver } from "./cli/webserver.js";
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
const parsed = parseArgs();
|
|
16
|
-
|
|
17
|
-
switch (parsed.subcommand) {
|
|
18
|
-
case "explore":
|
|
19
|
-
return handleExplore(parsed);
|
|
20
|
-
case "info":
|
|
21
|
-
return handleInfo(parsed);
|
|
22
|
-
case "attach":
|
|
23
|
-
return handleAttach(parsed);
|
|
24
|
-
case "watch":
|
|
25
|
-
return handleWatch(parsed);
|
|
26
|
-
case "webserver":
|
|
27
|
-
return handleWebserver(parsed);
|
|
28
|
-
case "open":
|
|
29
|
-
return handleOpen(parsed);
|
|
30
|
-
case "comments":
|
|
31
|
-
return handleComments(parsed);
|
|
32
|
-
case "videostats":
|
|
33
|
-
return handleVideoStats(parsed);
|
|
34
|
-
case "db-import":
|
|
35
|
-
return handleDbImport(parsed);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
urls,
|
|
40
|
-
outputFile,
|
|
41
|
-
outputFormat,
|
|
42
|
-
exploreCount,
|
|
43
|
-
showConfig: showCfg,
|
|
44
|
-
showHelp,
|
|
45
|
-
showVersion,
|
|
46
|
-
customProxy,
|
|
47
|
-
configAction,
|
|
48
|
-
configKey,
|
|
49
|
-
configValue,
|
|
50
|
-
} = parsed;
|
|
51
|
-
|
|
52
|
-
if (showVersion) {
|
|
53
|
-
console.log(version);
|
|
54
|
-
process.exit(0);
|
|
55
|
-
}
|
|
56
|
-
if (showHelp) return showUsage();
|
|
57
|
-
if (configAction) return handleConfig(configAction, configKey, configValue);
|
|
58
|
-
if (showCfg) return showConfig(urls, outputFile);
|
|
59
|
-
if (urls.length === 0 && exploreCount === 0) return showUsage();
|
|
60
|
-
|
|
61
|
-
// 默认行为:URL 走 info,--explore 走 explore
|
|
62
|
-
if (exploreCount > 0) {
|
|
63
|
-
return handleExplore({ ...parsed, subcommand: "explore" });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 有 URL 默认走 info
|
|
67
|
-
return handleInfo(parsed);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
main().catch((err) => {
|
|
71
|
-
console.error(`错误: ${err.message}`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
});
|
|
1
|
+
import { parseArgs } from "./lib/args.js";
|
|
2
|
+
import { proxy, HELP_TEXT, getConfigText } from "./lib/constants.js";
|
|
3
|
+
import { handleInfo } from "./cli/info.js";
|
|
4
|
+
import { handleExplore } from "./cli/explore.js";
|
|
5
|
+
import { handleAttach } from "./cli/attach.js";
|
|
6
|
+
import { handleWatch } from "./cli/watch.js";
|
|
7
|
+
import { handleConfig, showConfig, showUsage, version } from "./cli/config.js";
|
|
8
|
+
import { handleOpen } from "./cli/open.js";
|
|
9
|
+
import { handleComments } from "./cli/comments.js";
|
|
10
|
+
import { handleVideoStats } from "./cli/videostats.js";
|
|
11
|
+
import { handleDbImport } from "./cli/db-import.js";
|
|
12
|
+
import { handleWebserver } from "./cli/webserver.js";
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const parsed = parseArgs();
|
|
16
|
+
|
|
17
|
+
switch (parsed.subcommand) {
|
|
18
|
+
case "explore":
|
|
19
|
+
return handleExplore(parsed);
|
|
20
|
+
case "info":
|
|
21
|
+
return handleInfo(parsed);
|
|
22
|
+
case "attach":
|
|
23
|
+
return handleAttach(parsed);
|
|
24
|
+
case "watch":
|
|
25
|
+
return handleWatch(parsed);
|
|
26
|
+
case "webserver":
|
|
27
|
+
return handleWebserver(parsed);
|
|
28
|
+
case "open":
|
|
29
|
+
return handleOpen(parsed);
|
|
30
|
+
case "comments":
|
|
31
|
+
return handleComments(parsed);
|
|
32
|
+
case "videostats":
|
|
33
|
+
return handleVideoStats(parsed);
|
|
34
|
+
case "db-import":
|
|
35
|
+
return handleDbImport(parsed);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
urls,
|
|
40
|
+
outputFile,
|
|
41
|
+
outputFormat,
|
|
42
|
+
exploreCount,
|
|
43
|
+
showConfig: showCfg,
|
|
44
|
+
showHelp,
|
|
45
|
+
showVersion,
|
|
46
|
+
customProxy,
|
|
47
|
+
configAction,
|
|
48
|
+
configKey,
|
|
49
|
+
configValue,
|
|
50
|
+
} = parsed;
|
|
51
|
+
|
|
52
|
+
if (showVersion) {
|
|
53
|
+
console.log(version);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
if (showHelp) return showUsage();
|
|
57
|
+
if (configAction) return handleConfig(configAction, configKey, configValue);
|
|
58
|
+
if (showCfg) return showConfig(urls, outputFile);
|
|
59
|
+
if (urls.length === 0 && exploreCount === 0) return showUsage();
|
|
60
|
+
|
|
61
|
+
// 默认行为:URL 走 info,--explore 走 explore
|
|
62
|
+
if (exploreCount > 0) {
|
|
63
|
+
return handleExplore({ ...parsed, subcommand: "explore" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 有 URL 默认走 info
|
|
67
|
+
return handleInfo(parsed);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main().catch((err) => {
|
|
71
|
+
console.error(`错误: ${err.message}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
package/src/npm-main.js
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import { parseArgs } from "./lib/args.js";
|
|
2
|
-
import { PUBLIC_HELP_TEXT } from "./lib/constants.js";
|
|
3
|
-
import { handleInfo } from "./cli/info.js";
|
|
4
|
-
import { handleExplore } from "./cli/explore.js";
|
|
5
|
-
import { handleAttach } from "./cli/attach.js";
|
|
6
|
-
import { handleConfig, showConfig, showUsage, version } from "./cli/config.js";
|
|
7
|
-
import { handleOpen } from "./cli/open.js";
|
|
8
|
-
import { handleComments } from "./cli/comments.js";
|
|
9
|
-
|
|
10
|
-
function exitUnsupportedCommand(command) {
|
|
11
|
-
console.error(
|
|
12
|
-
`[${command}] 当前 npm 发布包不包含该命令;如需使用,请在仓库源码环境中运行 node src/main.js ${command} ...`,
|
|
13
|
-
);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function main() {
|
|
18
|
-
const parsed = parseArgs();
|
|
19
|
-
|
|
20
|
-
switch (parsed.subcommand) {
|
|
21
|
-
case "explore":
|
|
22
|
-
return handleExplore(parsed);
|
|
23
|
-
case "info":
|
|
24
|
-
return handleInfo(parsed);
|
|
25
|
-
case "attach":
|
|
26
|
-
return handleAttach(parsed);
|
|
27
|
-
case "watch":
|
|
28
|
-
case "webserver":
|
|
29
|
-
case "videostats":
|
|
30
|
-
case "db-import":
|
|
31
|
-
return exitUnsupportedCommand(parsed.subcommand);
|
|
32
|
-
case "open":
|
|
33
|
-
return handleOpen(parsed);
|
|
34
|
-
case "comments":
|
|
35
|
-
return handleComments(parsed);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
urls,
|
|
40
|
-
outputFile,
|
|
41
|
-
exploreCount,
|
|
42
|
-
showConfig: showCfg,
|
|
43
|
-
showHelp,
|
|
44
|
-
showVersion,
|
|
45
|
-
configAction,
|
|
46
|
-
configKey,
|
|
47
|
-
configValue,
|
|
48
|
-
} = parsed;
|
|
49
|
-
|
|
50
|
-
if (showVersion) {
|
|
51
|
-
console.log(version);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
if (showHelp) return showUsage(PUBLIC_HELP_TEXT);
|
|
55
|
-
if (configAction) return handleConfig(configAction, configKey, configValue);
|
|
56
|
-
if (showCfg) return showConfig(urls, outputFile);
|
|
57
|
-
if (urls.length === 0 && exploreCount === 0)
|
|
58
|
-
return showUsage(PUBLIC_HELP_TEXT);
|
|
59
|
-
|
|
60
|
-
if (exploreCount > 0) {
|
|
61
|
-
return handleExplore({ ...parsed, subcommand: "explore" });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return handleInfo(parsed);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
main().catch((err) => {
|
|
68
|
-
console.error(`错误: ${err.message}`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
});
|
|
1
|
+
import { parseArgs } from "./lib/args.js";
|
|
2
|
+
import { PUBLIC_HELP_TEXT } from "./lib/constants.js";
|
|
3
|
+
import { handleInfo } from "./cli/info.js";
|
|
4
|
+
import { handleExplore } from "./cli/explore.js";
|
|
5
|
+
import { handleAttach } from "./cli/attach.js";
|
|
6
|
+
import { handleConfig, showConfig, showUsage, version } from "./cli/config.js";
|
|
7
|
+
import { handleOpen } from "./cli/open.js";
|
|
8
|
+
import { handleComments } from "./cli/comments.js";
|
|
9
|
+
|
|
10
|
+
function exitUnsupportedCommand(command) {
|
|
11
|
+
console.error(
|
|
12
|
+
`[${command}] 当前 npm 发布包不包含该命令;如需使用,请在仓库源码环境中运行 node src/main.js ${command} ...`,
|
|
13
|
+
);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const parsed = parseArgs();
|
|
19
|
+
|
|
20
|
+
switch (parsed.subcommand) {
|
|
21
|
+
case "explore":
|
|
22
|
+
return handleExplore(parsed);
|
|
23
|
+
case "info":
|
|
24
|
+
return handleInfo(parsed);
|
|
25
|
+
case "attach":
|
|
26
|
+
return handleAttach(parsed);
|
|
27
|
+
case "watch":
|
|
28
|
+
case "webserver":
|
|
29
|
+
case "videostats":
|
|
30
|
+
case "db-import":
|
|
31
|
+
return exitUnsupportedCommand(parsed.subcommand);
|
|
32
|
+
case "open":
|
|
33
|
+
return handleOpen(parsed);
|
|
34
|
+
case "comments":
|
|
35
|
+
return handleComments(parsed);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
urls,
|
|
40
|
+
outputFile,
|
|
41
|
+
exploreCount,
|
|
42
|
+
showConfig: showCfg,
|
|
43
|
+
showHelp,
|
|
44
|
+
showVersion,
|
|
45
|
+
configAction,
|
|
46
|
+
configKey,
|
|
47
|
+
configValue,
|
|
48
|
+
} = parsed;
|
|
49
|
+
|
|
50
|
+
if (showVersion) {
|
|
51
|
+
console.log(version);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
if (showHelp) return showUsage(PUBLIC_HELP_TEXT);
|
|
55
|
+
if (configAction) return handleConfig(configAction, configKey, configValue);
|
|
56
|
+
if (showCfg) return showConfig(urls, outputFile);
|
|
57
|
+
if (urls.length === 0 && exploreCount === 0)
|
|
58
|
+
return showUsage(PUBLIC_HELP_TEXT);
|
|
59
|
+
|
|
60
|
+
if (exploreCount > 0) {
|
|
61
|
+
return handleExplore({ ...parsed, subcommand: "explore" });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return handleInfo(parsed);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((err) => {
|
|
68
|
+
console.error(`错误: ${err.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"user": {
|
|
3
|
+
"uniqueId": "bar.lar.lar.moeta",
|
|
4
|
+
"secUid": "MS4wLjABAAAA3cgKTWvKfga0JAWeakAzx3zQ-aFAC8RuQvxD4HQFraKKsc_TbOIyMo3_ofVlXofV",
|
|
5
|
+
"nickname": "Bar Lar Lar Moetain",
|
|
6
|
+
"ttSeller": false,
|
|
7
|
+
"verified": false,
|
|
8
|
+
"followerCount": 24000,
|
|
9
|
+
"videoCount": 749,
|
|
10
|
+
"followingCount": 4293,
|
|
11
|
+
"heartCount": 254300,
|
|
12
|
+
"signature": ""
|
|
13
|
+
},
|
|
14
|
+
"totalVideos": 5,
|
|
15
|
+
"videos": [
|
|
16
|
+
{
|
|
17
|
+
"id": "7638231799084158228",
|
|
18
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638231799084158228"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "7638162444698914068",
|
|
22
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638162444698914068"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "7638116251767819541",
|
|
26
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638116251767819541"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "7638069637321690388",
|
|
30
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638069637321690388"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "7637927171025112341",
|
|
34
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7637927171025112341"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|