@tiktool/live 2.6.1 → 2.6.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/dist/index.mjs CHANGED
@@ -297,8 +297,10 @@ function parseBattleTeamFromArmies(itemBuf) {
297
297
  const userBufs = getAllBytes(gf, 1);
298
298
  for (const ub of userBufs) {
299
299
  try {
300
+ const userFields = decodeProto(ub);
301
+ const individualScore = getInt(userFields, 2);
300
302
  const user = parseUser(ub);
301
- users.push({ user, score: points });
303
+ users.push({ user, score: individualScore });
302
304
  } catch {
303
305
  }
304
306
  }
@@ -1129,6 +1131,206 @@ var TikTokLive = class extends EventEmitter {
1129
1131
  }
1130
1132
  };
1131
1133
 
1134
+ // src/captions.ts
1135
+ import { EventEmitter as EventEmitter2 } from "events";
1136
+ import WebSocket2 from "ws";
1137
+ var DEFAULT_CAPTIONS_SERVER = "wss://api.tik.tools";
1138
+ var TikTokCaptions = class extends EventEmitter2 {
1139
+ ws = null;
1140
+ _connected = false;
1141
+ intentionalClose = false;
1142
+ reconnectAttempts = 0;
1143
+ uniqueId;
1144
+ apiKey;
1145
+ serverUrl;
1146
+ autoReconnect;
1147
+ maxReconnectAttempts;
1148
+ debug;
1149
+ _translate;
1150
+ _diarization;
1151
+ _maxDurationMinutes;
1152
+ _language;
1153
+ constructor(options) {
1154
+ super();
1155
+ this.uniqueId = options.uniqueId.replace(/^@/, "");
1156
+ if (!options.apiKey) throw new Error("apiKey is required. Get a free key at https://tik.tools");
1157
+ this.apiKey = options.apiKey;
1158
+ this._language = options.language || "";
1159
+ this._translate = options.translate || "";
1160
+ this._diarization = options.diarization ?? true;
1161
+ this._maxDurationMinutes = options.maxDurationMinutes ?? 60;
1162
+ this.serverUrl = (options.signServerUrl || DEFAULT_CAPTIONS_SERVER).replace(/\/$/, "");
1163
+ this.autoReconnect = options.autoReconnect ?? true;
1164
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
1165
+ this.debug = options.debug ?? false;
1166
+ }
1167
+ /**
1168
+ * Start real-time captions for the configured TikTok user.
1169
+ * Connects to the captions WebSocket relay and begins transcription
1170
+ * once the user goes live (or immediately if already live).
1171
+ */
1172
+ async start() {
1173
+ this.intentionalClose = false;
1174
+ const wsUrl = this.buildWsUrl();
1175
+ if (this.debug) console.log(`[Captions] Connecting to ${wsUrl}`);
1176
+ return new Promise((resolve, reject) => {
1177
+ this.ws = new WebSocket2(wsUrl);
1178
+ this.ws.on("open", () => {
1179
+ this._connected = true;
1180
+ this.reconnectAttempts = 0;
1181
+ if (this.debug) console.log("[Captions] Connected");
1182
+ this.emit("connected");
1183
+ resolve();
1184
+ });
1185
+ this.ws.on("message", (data) => {
1186
+ this.handleMessage(typeof data === "string" ? data : data.toString());
1187
+ });
1188
+ this.ws.on("close", (code, reason) => {
1189
+ this._connected = false;
1190
+ const reasonStr = reason?.toString() || "";
1191
+ if (this.debug) console.log(`[Captions] Disconnected: ${code} ${reasonStr}`);
1192
+ this.emit("disconnected", code, reasonStr);
1193
+ if (!this.intentionalClose && this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
1194
+ this.reconnectAttempts++;
1195
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), 3e4);
1196
+ if (this.debug) console.log(`[Captions] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
1197
+ setTimeout(() => this.start().catch((e) => this.emit("error", { code: "RECONNECT_FAILED", message: e.message })), delay);
1198
+ }
1199
+ });
1200
+ this.ws.on("error", (err) => {
1201
+ this.emit("error", { code: "WS_ERROR", message: err.message });
1202
+ if (!this._connected) reject(err);
1203
+ });
1204
+ });
1205
+ }
1206
+ /**
1207
+ * Stop captions and disconnect.
1208
+ */
1209
+ stop() {
1210
+ this.intentionalClose = true;
1211
+ if (this.ws) {
1212
+ this.send({ action: "stop" });
1213
+ this.ws.close(1e3);
1214
+ this.ws = null;
1215
+ }
1216
+ this._connected = false;
1217
+ }
1218
+ /**
1219
+ * Switch the translation target language on-the-fly.
1220
+ * Causes a brief interruption while the transcription engine reconfigures.
1221
+ */
1222
+ setLanguage(language) {
1223
+ this._language = language;
1224
+ this.send({ action: "set_language", language });
1225
+ }
1226
+ /**
1227
+ * Request a credit balance update from the server.
1228
+ */
1229
+ getCredits() {
1230
+ this.send({ action: "get_credits" });
1231
+ }
1232
+ /** Whether the WebSocket is currently connected */
1233
+ get connected() {
1234
+ return this._connected;
1235
+ }
1236
+ /** The current target language */
1237
+ get language() {
1238
+ return this._language;
1239
+ }
1240
+ on(event, listener) {
1241
+ return super.on(event, listener);
1242
+ }
1243
+ once(event, listener) {
1244
+ return super.once(event, listener);
1245
+ }
1246
+ off(event, listener) {
1247
+ return super.off(event, listener);
1248
+ }
1249
+ emit(event, ...args) {
1250
+ return super.emit(event, ...args);
1251
+ }
1252
+ buildWsUrl() {
1253
+ const base = this.serverUrl.replace(/^http/, "ws");
1254
+ const params = new URLSearchParams({
1255
+ uniqueId: this.uniqueId,
1256
+ apiKey: this.apiKey
1257
+ });
1258
+ if (this._language) params.set("language", this._language);
1259
+ if (this._translate) params.set("translate", this._translate);
1260
+ if (this._diarization !== void 0) params.set("diarization", String(this._diarization));
1261
+ if (this._maxDurationMinutes) params.set("max_duration_minutes", String(this._maxDurationMinutes));
1262
+ return `${base}/captions?${params}`;
1263
+ }
1264
+ send(msg) {
1265
+ if (this.ws?.readyState === WebSocket2.OPEN) {
1266
+ this.ws.send(JSON.stringify(msg));
1267
+ }
1268
+ }
1269
+ handleMessage(raw) {
1270
+ try {
1271
+ const msg = JSON.parse(raw);
1272
+ switch (msg.type) {
1273
+ case "caption":
1274
+ this.emit("caption", {
1275
+ text: msg.text,
1276
+ language: msg.language,
1277
+ isFinal: msg.isFinal,
1278
+ confidence: msg.confidence,
1279
+ speaker: msg.speaker,
1280
+ startMs: msg.startMs,
1281
+ endMs: msg.endMs
1282
+ });
1283
+ break;
1284
+ case "translation":
1285
+ this.emit("translation", {
1286
+ text: msg.text,
1287
+ language: msg.language,
1288
+ isFinal: msg.isFinal,
1289
+ confidence: msg.confidence,
1290
+ speaker: msg.speaker
1291
+ });
1292
+ break;
1293
+ case "status":
1294
+ this.emit("status", {
1295
+ status: msg.status,
1296
+ uniqueId: msg.uniqueId,
1297
+ roomId: msg.roomId,
1298
+ language: msg.language,
1299
+ message: msg.message
1300
+ });
1301
+ break;
1302
+ case "credits":
1303
+ this.emit("credits", {
1304
+ remaining: msg.remaining,
1305
+ total: msg.total,
1306
+ used: msg.used,
1307
+ warning: msg.warning
1308
+ });
1309
+ break;
1310
+ case "credits_low":
1311
+ this.emit("credits_low", {
1312
+ remaining: msg.remaining,
1313
+ total: msg.total,
1314
+ percent: msg.percent
1315
+ });
1316
+ break;
1317
+ case "error":
1318
+ this.emit("error", {
1319
+ code: msg.code,
1320
+ message: msg.message
1321
+ });
1322
+ break;
1323
+ default:
1324
+ if (this.debug) {
1325
+ console.log(`[Captions] Unknown message type: ${msg.type}`, msg);
1326
+ }
1327
+ }
1328
+ } catch {
1329
+ if (this.debug) console.error("[Captions] Failed to parse message:", raw);
1330
+ }
1331
+ }
1332
+ };
1333
+
1132
1334
  // src/api.ts
1133
1335
  var DEFAULT_SIGN_SERVER2 = "https://api.tik.tools";
1134
1336
  var PAGE_CACHE_TTL = 5 * 60 * 1e3;
@@ -1185,6 +1387,7 @@ async function getRegionalRanklist(opts) {
1185
1387
  return data;
1186
1388
  }
1187
1389
  export {
1390
+ TikTokCaptions,
1188
1391
  TikTokLive,
1189
1392
  getRanklist,
1190
1393
  getRegionalRanklist