tokwatchr 0.7.0 → 0.7.1

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.
@@ -1,13 +1,13 @@
1
1
  #! /usr/bin/env node
2
2
  import { cac } from "cac";
3
+ import { basename, dirname, join } from "node:path";
3
4
  import pc from "picocolors";
4
5
  import { Impit } from "impit";
5
6
  import { spawn, spawnSync } from "node:child_process";
6
7
  import { EventEmitter } from "node:events";
7
8
  import { createWriteStream, existsSync, mkdirSync } from "node:fs";
8
- import { join } from "node:path";
9
9
  //#region package.json
10
- var version = "0.7.0";
10
+ var version = "0.7.1";
11
11
  //#endregion
12
12
  //#region src/api/client.ts
13
13
  /**
@@ -1250,9 +1250,7 @@ function formatSpeed(bytesPerSec) {
1250
1250
  async function executeDownload(username, options) {
1251
1251
  let downloader;
1252
1252
  const onSignal = async () => {
1253
- console.log(`\n\n ${pc.red("Stopping...")}`);
1254
1253
  await downloader.stop();
1255
- process.exit(0);
1256
1254
  };
1257
1255
  process.on("SIGINT", onSignal);
1258
1256
  process.on("SIGTERM", onSignal);
@@ -1279,7 +1277,7 @@ async function executeDownload(username, options) {
1279
1277
  process.stderr.write(`\n ${pc.dim("Remuxing...")}`);
1280
1278
  break;
1281
1279
  case "completed":
1282
- process.stderr.write(`\r ${pc.green("Remuxed:")} ${info.outputPath}\n`);
1280
+ process.stderr.write(`\r ${pc.green("Remuxed:")} ${basename(info.outputPath ?? "")}\n`);
1283
1281
  break;
1284
1282
  case "failed":
1285
1283
  process.stderr.write(`\n ${pc.yellow("Remux failed, keeping .ts as fallback")}\n`);
@@ -1288,9 +1286,10 @@ async function executeDownload(username, options) {
1288
1286
  });
1289
1287
  downloader.on("complete", (results) => {
1290
1288
  process.stderr.write("\n");
1291
- for (const r of results) console.log(` ${pc.green("Saved:")} ${r.filePath} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`);
1289
+ for (const r of results) console.log(` ${pc.green("Saved:")} ${basename(r.filePath)} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`);
1292
1290
  const totalMB = results.reduce((sum, r) => sum + r.sizeMB, 0);
1293
1291
  console.log(` Done — ${results.length} segment(s), ${totalMB.toFixed(1)}MB total`);
1292
+ if (results.length > 0) console.log(` ${pc.dim(`Output: ${dirname(results[0]?.filePath ?? "")}/`)}`);
1294
1293
  });
1295
1294
  try {
1296
1295
  await downloader.startRecording();
@@ -1317,10 +1316,10 @@ async function executeDownload(username, options) {
1317
1316
  */
1318
1317
  async function executeWatch(username, options) {
1319
1318
  let downloader;
1319
+ let stopped = false;
1320
1320
  const onSignal = async () => {
1321
- console.log(`\n\n ${pc.red("Stopping...")}`);
1321
+ stopped = true;
1322
1322
  await downloader.stop();
1323
- process.exit(0);
1324
1323
  };
1325
1324
  process.on("SIGINT", onSignal);
1326
1325
  process.on("SIGTERM", onSignal);
@@ -1352,7 +1351,7 @@ async function executeWatch(username, options) {
1352
1351
  process.stderr.write(`\n ${pc.dim("Remuxing...")}`);
1353
1352
  break;
1354
1353
  case "completed":
1355
- process.stderr.write(`\r ${pc.green("Remuxed:")} ${info.outputPath}\n`);
1354
+ process.stderr.write(`\r ${pc.green("Remuxed:")} ${basename(info.outputPath ?? "")}\n`);
1356
1355
  break;
1357
1356
  case "failed":
1358
1357
  process.stderr.write(`\n ${pc.yellow("Remux failed, keeping .ts as fallback")}\n`);
@@ -1361,26 +1360,32 @@ async function executeWatch(username, options) {
1361
1360
  });
1362
1361
  downloader.on("segment", (result, partNum) => {
1363
1362
  process.stderr.write("\n");
1364
- console.log(` ${pc.green("Segment")} ${partNum}: ${result.filePath} ${pc.dim(`(${formatBytes(result.sizeBytes)}, ${formatDuration(result.duration)})`)}`);
1363
+ console.log(` ${pc.green("Segment")} ${partNum}: ${basename(result.filePath)} ${pc.dim(`(${formatBytes(result.sizeBytes)}, ${formatDuration(result.duration)})`)}`);
1365
1364
  });
1366
1365
  downloader.on("complete", (results) => {
1367
1366
  process.stderr.write("\n");
1368
- for (const r of results) console.log(` ${pc.green("Saved:")} ${r.filePath} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`);
1367
+ for (const r of results) console.log(` ${pc.green("Saved:")} ${basename(r.filePath)} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`);
1369
1368
  const totalMB = results.reduce((sum, r) => sum + r.sizeMB, 0);
1370
1369
  console.log(` Done — ${results.length} segment(s), ${totalMB.toFixed(1)}MB total`);
1370
+ if (results.length > 0) console.log(` ${pc.dim(`Output: ${dirname(results[0]?.filePath ?? "")}/`)}`);
1371
1371
  });
1372
1372
  console.log(`${pc.dim("Waiting for ")}${pc.blue(username)}${pc.dim(" to go live...")}`);
1373
- while (true) try {
1374
- await downloader.start();
1375
- console.log(`\n ${pc.dim("Stream ended, watching for next...")}`);
1376
- } catch (error) {
1377
- if (error instanceof UserNotFoundError) {
1378
- console.error(pc.red("[error]"), "User not found. Check the username and try again.");
1379
- process.exit(1);
1373
+ while (true) {
1374
+ if (stopped) break;
1375
+ try {
1376
+ await downloader.start();
1377
+ if (stopped) break;
1378
+ console.log(`\n ${pc.dim("Stream ended, watching for next...")}`);
1379
+ } catch (error) {
1380
+ if (stopped) break;
1381
+ if (error instanceof UserNotFoundError) {
1382
+ console.error(pc.red("[error]"), "User not found. Check the username and try again.");
1383
+ process.exit(1);
1384
+ }
1385
+ console.error(`\n ${pc.yellow(`[warning] ${error}`)}`);
1386
+ await new Promise((r) => setTimeout(r, 1e4));
1387
+ console.log(` ${pc.dim("Retrying...")}`);
1380
1388
  }
1381
- console.error(`\n ${pc.yellow(`[warning] ${error}`)}`);
1382
- await new Promise((r) => setTimeout(r, 1e4));
1383
- console.log(` ${pc.dim("Retrying...")}`);
1384
1389
  }
1385
1390
  }
1386
1391
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokwatchr",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Download TikTok livestreams. Given a username, download the livestream.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,3 +1,4 @@
1
+ import { basename, dirname } from "node:path";
1
2
  import pc from "picocolors";
2
3
  import type {
3
4
  DownloadResult,
@@ -28,9 +29,8 @@ export async function executeDownload(
28
29
  // ─── SIGINT / SIGTERM (registered BEFORE any async work) ─────
29
30
 
30
31
  const onSignal = async () => {
31
- console.log(`\n\n ${pc.red("Stopping...")}`);
32
32
  await downloader.stop();
33
- process.exit(0);
33
+ // No process.exit — let event loop drain naturally
34
34
  };
35
35
  process.on("SIGINT", onSignal);
36
36
  process.on("SIGTERM", onSignal);
@@ -68,7 +68,7 @@ export async function executeDownload(
68
68
  break;
69
69
  case "completed":
70
70
  process.stderr.write(
71
- `\r ${pc.green("Remuxed:")} ${info.outputPath}\n`,
71
+ `\r ${pc.green("Remuxed:")} ${basename(info.outputPath ?? "")}\n`,
72
72
  );
73
73
  break;
74
74
  case "failed":
@@ -83,13 +83,18 @@ export async function executeDownload(
83
83
  process.stderr.write("\n");
84
84
  for (const r of results) {
85
85
  console.log(
86
- ` ${pc.green("Saved:")} ${r.filePath} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`,
86
+ ` ${pc.green("Saved:")} ${basename(r.filePath)} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`,
87
87
  );
88
88
  }
89
89
  const totalMB = results.reduce((sum, r) => sum + r.sizeMB, 0);
90
90
  console.log(
91
91
  ` Done — ${results.length} segment(s), ${totalMB.toFixed(1)}MB total`,
92
92
  );
93
+ if (results.length > 0) {
94
+ console.log(
95
+ ` ${pc.dim(`Output: ${dirname(results[0]?.filePath ?? "")}/`)}`,
96
+ );
97
+ }
93
98
  });
94
99
 
95
100
  // ─── Start ──────────────────────────────────────────────
@@ -1,3 +1,4 @@
1
+ import { basename, dirname } from "node:path";
1
2
  import pc from "picocolors";
2
3
  import type {
3
4
  DownloadResult,
@@ -21,13 +22,14 @@ export async function executeWatch(
21
22
  options: WatchCliOptions,
22
23
  ): Promise<void> {
23
24
  let downloader: TikTokLiveDownloader;
25
+ let stopped = false;
24
26
 
25
27
  // ─── SIGINT / SIGTERM (registered BEFORE any async work) ─────
26
28
 
27
29
  const onSignal = async () => {
28
- console.log(`\n\n ${pc.red("Stopping...")}`);
30
+ stopped = true;
29
31
  await downloader.stop();
30
- process.exit(0);
32
+ // No process.exit — let event loop drain naturally
31
33
  };
32
34
  process.on("SIGINT", onSignal);
33
35
  process.on("SIGTERM", onSignal);
@@ -73,7 +75,7 @@ export async function executeWatch(
73
75
  break;
74
76
  case "completed":
75
77
  process.stderr.write(
76
- `\r ${pc.green("Remuxed:")} ${info.outputPath}\n`,
78
+ `\r ${pc.green("Remuxed:")} ${basename(info.outputPath ?? "")}\n`,
77
79
  );
78
80
  break;
79
81
  case "failed":
@@ -87,7 +89,7 @@ export async function executeWatch(
87
89
  downloader.on("segment", (result: DownloadResult, partNum: number) => {
88
90
  process.stderr.write("\n");
89
91
  console.log(
90
- ` ${pc.green("Segment")} ${partNum}: ${result.filePath} ${pc.dim(`(${formatBytes(result.sizeBytes)}, ${formatDuration(result.duration)})`)}`,
92
+ ` ${pc.green("Segment")} ${partNum}: ${basename(result.filePath)} ${pc.dim(`(${formatBytes(result.sizeBytes)}, ${formatDuration(result.duration)})`)}`,
91
93
  );
92
94
  });
93
95
 
@@ -95,13 +97,18 @@ export async function executeWatch(
95
97
  process.stderr.write("\n");
96
98
  for (const r of results) {
97
99
  console.log(
98
- ` ${pc.green("Saved:")} ${r.filePath} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`,
100
+ ` ${pc.green("Saved:")} ${basename(r.filePath)} ${pc.dim(`(${formatBytes(r.sizeBytes)}, ${formatDuration(r.duration)})`)}`,
99
101
  );
100
102
  }
101
103
  const totalMB = results.reduce((sum, r) => sum + r.sizeMB, 0);
102
104
  console.log(
103
105
  ` Done — ${results.length} segment(s), ${totalMB.toFixed(1)}MB total`,
104
106
  );
107
+ if (results.length > 0) {
108
+ console.log(
109
+ ` ${pc.dim(`Output: ${dirname(results[0]?.filePath ?? "")}/`)}`,
110
+ );
111
+ }
105
112
  });
106
113
 
107
114
  // ─── Start (persistent loop) ────────────────────────────
@@ -111,12 +118,15 @@ export async function executeWatch(
111
118
  );
112
119
 
113
120
  while (true) {
121
+ if (stopped) break;
114
122
  try {
115
123
  await downloader.start();
124
+ if (stopped) break;
116
125
  // Stream ended — complete event already printed results.
117
126
  // Continue watching for the next one.
118
127
  console.log(`\n ${pc.dim("Stream ended, watching for next...")}`);
119
128
  } catch (error) {
129
+ if (stopped) break;
120
130
  if (error instanceof UserNotFoundError) {
121
131
  console.error(
122
132
  pc.red("[error]"),