chatlab-cli 0.31.3 → 0.33.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.
Files changed (152) hide show
  1. package/README.md +2 -1
  2. package/dist/{chat-command-ZIXKNLSE.mjs → chat-command-IHRNO7J4.mjs} +8 -8
  3. package/dist/{chunk-3J7WTUEE.mjs → chunk-4V6UP7EW.mjs} +2 -2
  4. package/dist/{chunk-YVNZ73O5.mjs → chunk-C3B7RXES.mjs} +3919 -3773
  5. package/dist/chunk-C3B7RXES.mjs.map +1 -0
  6. package/dist/{chunk-FDLLEDNW.mjs → chunk-HGV4MUIW.mjs} +4 -4
  7. package/dist/{chunk-75FZ26HM.mjs → chunk-KWJ5FZ74.mjs} +1196 -1045
  8. package/dist/chunk-KWJ5FZ74.mjs.map +1 -0
  9. package/dist/{chunk-DV4U34MR.mjs → chunk-NHTOSDYI.mjs} +2 -2
  10. package/dist/{chunk-UIUEG7JB.mjs → chunk-NSWFHMBZ.mjs} +112 -16
  11. package/dist/chunk-NSWFHMBZ.mjs.map +1 -0
  12. package/dist/{chunk-EINCXXWY.mjs → chunk-RHA534GU.mjs} +3 -3
  13. package/dist/{chunk-23AIEWCD.mjs → chunk-S7YPBKQ3.mjs} +2 -2
  14. package/dist/{chunk-BLOBEOCG.mjs → chunk-U4F6BNGZ.mjs} +2 -2
  15. package/dist/{chunk-MFOCDGCC.mjs → chunk-ZERQIKI4.mjs} +121 -19
  16. package/dist/chunk-ZERQIKI4.mjs.map +1 -0
  17. package/dist/{chunk-GZO3NAHC.mjs → chunk-ZTMZ7S7T.mjs} +65 -16
  18. package/dist/chunk-ZTMZ7S7T.mjs.map +1 -0
  19. package/dist/cli.mjs +8 -8
  20. package/dist/contacts-worker.mjs +2 -2
  21. package/dist/global-insight-worker.mjs +1 -1
  22. package/dist/{http-WDG2OHOV.mjs → http-Y2WC2UK4.mjs} +11 -11
  23. package/dist/{import-4BDC37F7.mjs → import-4HF2UNQI.mjs} +7 -7
  24. package/dist/index.mjs +11 -11
  25. package/dist/{mcp-4KUVRGFF.mjs → mcp-DJPDRFTW.mjs} +8 -8
  26. package/dist/people-relationships-worker.mjs +2 -2
  27. package/dist/semantic-index-worker.mjs +1 -1
  28. package/dist/{src-ZOCLFBCJ.mjs → src-R7MZLKXQ.mjs} +17 -9
  29. package/dist/{stream-import-ERTT7U6V.mjs → stream-import-DX57ANUL.mjs} +7 -7
  30. package/dist/{worker-runtime-2TUZHV7W.mjs → worker-runtime-QMN7OKOZ.mjs} +5 -5
  31. package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-hwRVh048.js +110 -0
  32. package/{dist-web/assets/BufferResource-DgKoy0Ii.js → dist-cli-web/assets/BufferResource-DwfMBlAa.js} +1 -1
  33. package/dist-cli-web/assets/CanvasRenderer-q312LRsp.js +1 -0
  34. package/dist-cli-web/assets/DataDirCleanupNotice--ee1dY0d.js +1 -0
  35. package/dist-cli-web/assets/EChartWordcloud-BaMgUl0Y.js +1 -0
  36. package/{dist-web/assets/Filter-D32zqY5x.js → dist-cli-web/assets/Filter-BOb5cLBx.js} +1 -1
  37. package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-DK2z0Jrh.js +1 -0
  38. package/dist-cli-web/assets/MessageExportModal-CFbovWh1.js +1 -0
  39. package/dist-cli-web/assets/PageHeader-B3-W9mvh.js +1 -0
  40. package/{dist-web/assets/RelationshipGalaxyCanvas-DsMOubHV.js → dist-cli-web/assets/RelationshipGalaxyCanvas-GEpsKHxV.js} +4 -4
  41. package/{dist-web/assets/RelationshipGalaxyThreeCanvas-Dh5HjVHr.js → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-DHOU66Gj.js} +2 -2
  42. package/{dist-web/assets/RenderTargetSystem-CovK4x_D.js → dist-cli-web/assets/RenderTargetSystem-PEnnW2yb.js} +1 -1
  43. package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-sNw3hP4c.js +1 -0
  44. package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-BtUvMehe.js +1 -0
  45. package/{dist-web/assets/WebGLRenderer-CawhbcYU.js → dist-cli-web/assets/WebGLRenderer-BjP_1do7.js} +2 -2
  46. package/{dist-web/assets/WebGPURenderer-B7P1D2sA.js → dist-cli-web/assets/WebGPURenderer-ldVv92u7.js} +2 -2
  47. package/dist-cli-web/assets/annual-summary-time-range-BHClzNvd.js +1 -0
  48. package/dist-cli-web/assets/base-DBJ95yXf.js +1 -0
  49. package/{dist-web/assets/browserAll-CtQkc1DF.js → dist-cli-web/assets/browserAll-CPoMOLKh.js} +1 -1
  50. package/dist-cli-web/assets/cli-web-B5VYO-Dh.js +1 -0
  51. package/{dist-web/assets/dateFormat-Cfb89ZjM.js → dist-cli-web/assets/dateFormat-Cf2xuaE-.js} +1 -1
  52. package/dist-cli-web/assets/fetch-80O3EH4a.js +1 -0
  53. package/dist-cli-web/assets/fetch-B1km2u3N.js +1 -0
  54. package/dist-cli-web/assets/fetch-BFDKzf1P.js +1 -0
  55. package/dist-cli-web/assets/fetch-BuWxo6oK.js +1 -0
  56. package/{dist-web/assets/fetch-DkfUUq-R.js → dist-cli-web/assets/fetch-C0HHT1Yn.js} +3 -3
  57. package/dist-cli-web/assets/fetch-CXIdj2-_.js +1 -0
  58. package/{dist-web/assets/fetch-DVGCzZ0E.js → dist-cli-web/assets/fetch-CtyVktRW.js} +3 -3
  59. package/{dist-web/assets/fetch-D2hvh7lO.js → dist-cli-web/assets/fetch-DGi_9my2.js} +2 -2
  60. package/dist-cli-web/assets/fetch-Dr41uuXL.js +1 -0
  61. package/dist-cli-web/assets/fetch-J67jyI2k.js +1 -0
  62. package/dist-cli-web/assets/index-5UdyJtVU.js +1 -0
  63. package/dist-cli-web/assets/index-7jm3YI9u.js +53 -0
  64. package/dist-cli-web/assets/index-B7R74Yp7.js +1 -0
  65. package/dist-cli-web/assets/index-BGiTw98l.js +1 -0
  66. package/dist-cli-web/assets/index-Ba21yU86.js +1 -0
  67. package/{dist-web/assets/index-CuPE4SSh.js → dist-cli-web/assets/index-Bf3gFaid.js} +4 -4
  68. package/dist-cli-web/assets/index-ByQSR8Bj.js +2 -0
  69. package/dist-cli-web/assets/index-CPZYydbc.js +2 -0
  70. package/dist-cli-web/assets/index-D8Q3U-6e.js +1 -0
  71. package/dist-cli-web/assets/index-DGtDxRCd.js +173 -0
  72. package/{dist-web/assets/index-DBb03wVF.css → dist-cli-web/assets/index-DTxnpQiv.css} +1 -1
  73. package/dist-cli-web/assets/index-DmrrdYXH.css +1 -0
  74. package/dist-cli-web/assets/index-DtlTo3nt.js +1 -0
  75. package/dist-cli-web/assets/index-DzQT-qXF.js +1 -0
  76. package/dist-cli-web/assets/index-gpTDI3sq.js +64 -0
  77. package/{dist-web/assets/index-D8Uq3_Dc.css → dist-cli-web/assets/index-lwhCMqhd.css} +1 -1
  78. package/dist-cli-web/assets/index-qsbWn8VP.css +1 -0
  79. package/dist-cli-web/assets/people-page-header-BvQy1GQj.js +1 -0
  80. package/{dist-web/assets/webworkerAll-BzRoT-6m.js → dist-cli-web/assets/webworkerAll-DgKQKbNn.js} +1 -1
  81. package/{dist-web → dist-cli-web}/index.html +2 -4
  82. package/package.json +4 -4
  83. package/dist/chunk-75FZ26HM.mjs.map +0 -1
  84. package/dist/chunk-GZO3NAHC.mjs.map +0 -1
  85. package/dist/chunk-MFOCDGCC.mjs.map +0 -1
  86. package/dist/chunk-UIUEG7JB.mjs.map +0 -1
  87. package/dist/chunk-YVNZ73O5.mjs.map +0 -1
  88. package/dist-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-GgQ5ueMX.js +0 -110
  89. package/dist-web/assets/CanvasRenderer-CKpp71xg.js +0 -1
  90. package/dist-web/assets/EChartWordcloud-CSOuFvyM.js +0 -1
  91. package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-DUAtrSZD.js +0 -1
  92. package/dist-web/assets/MessageExportModal-BYLw4vZ8.js +0 -1
  93. package/dist-web/assets/PageHeader-Dmu6cdAk.js +0 -1
  94. package/dist-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-DhUfYrEx.js +0 -1
  95. package/dist-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-CaumhCAS.js +0 -1
  96. package/dist-web/assets/annual-summary-time-range-Bya4h2j6.js +0 -1
  97. package/dist-web/assets/base-ClGWE9aq.js +0 -1
  98. package/dist-web/assets/electron-Brd7jS6W.js +0 -1
  99. package/dist-web/assets/electron-DGxVMsvC.js +0 -1
  100. package/dist-web/assets/electron-hcWiB7Gg.js +0 -1
  101. package/dist-web/assets/fetch-BJ3lnhzK.js +0 -1
  102. package/dist-web/assets/fetch-BoB63ns8.js +0 -1
  103. package/dist-web/assets/fetch-BtK7YvLp.js +0 -1
  104. package/dist-web/assets/fetch-CeREJiFL.js +0 -1
  105. package/dist-web/assets/fetch-DWoMmOlr.js +0 -1
  106. package/dist-web/assets/fetch-Dr_Fjtvp.js +0 -1
  107. package/dist-web/assets/fetch-u7ivV7Uy.js +0 -1
  108. package/dist-web/assets/index-BHnQaeQY.js +0 -1
  109. package/dist-web/assets/index-BKFGuwJu.css +0 -1
  110. package/dist-web/assets/index-BXaKyn8J.js +0 -2
  111. package/dist-web/assets/index-BzH9cnfy.js +0 -1
  112. package/dist-web/assets/index-D-qNxFxp.js +0 -173
  113. package/dist-web/assets/index-D494nYee.js +0 -64
  114. package/dist-web/assets/index-DJ8kB4US.js +0 -2
  115. package/dist-web/assets/index-DNMWR2Vj.js +0 -1
  116. package/dist-web/assets/index-DSPls1YV.js +0 -1
  117. package/dist-web/assets/index-DcAqhCpS.js +0 -1
  118. package/dist-web/assets/index-DnkaYOFI.js +0 -1
  119. package/dist-web/assets/index-_UKteDZB.js +0 -25
  120. package/dist-web/assets/index-gi0wgh0M.css +0 -1
  121. package/dist-web/assets/index-w_1EjXiF.js +0 -1
  122. package/dist-web/assets/people-page-header-YGQM9G53.js +0 -1
  123. package/dist-web/assets/types-CZnACqaB.js +0 -1
  124. package/dist-web/assets/vendor-nuxt-ui-DmkGVKod.js +0 -15
  125. package/dist-web/assets/vendor-reka-ui-BkhadcDj.js +0 -15
  126. package/dist-web/assets/web-zXxtk2KZ.js +0 -1
  127. /package/dist/{chat-command-ZIXKNLSE.mjs.map → chat-command-IHRNO7J4.mjs.map} +0 -0
  128. /package/dist/{chunk-3J7WTUEE.mjs.map → chunk-4V6UP7EW.mjs.map} +0 -0
  129. /package/dist/{chunk-FDLLEDNW.mjs.map → chunk-HGV4MUIW.mjs.map} +0 -0
  130. /package/dist/{chunk-DV4U34MR.mjs.map → chunk-NHTOSDYI.mjs.map} +0 -0
  131. /package/dist/{chunk-EINCXXWY.mjs.map → chunk-RHA534GU.mjs.map} +0 -0
  132. /package/dist/{chunk-23AIEWCD.mjs.map → chunk-S7YPBKQ3.mjs.map} +0 -0
  133. /package/dist/{chunk-BLOBEOCG.mjs.map → chunk-U4F6BNGZ.mjs.map} +0 -0
  134. /package/dist/{http-WDG2OHOV.mjs.map → http-Y2WC2UK4.mjs.map} +0 -0
  135. /package/dist/{import-4BDC37F7.mjs.map → import-4HF2UNQI.mjs.map} +0 -0
  136. /package/dist/{mcp-4KUVRGFF.mjs.map → mcp-DJPDRFTW.mjs.map} +0 -0
  137. /package/dist/{src-ZOCLFBCJ.mjs.map → src-R7MZLKXQ.mjs.map} +0 -0
  138. /package/dist/{stream-import-ERTT7U6V.mjs.map → stream-import-DX57ANUL.mjs.map} +0 -0
  139. /package/dist/{worker-runtime-2TUZHV7W.mjs.map → worker-runtime-QMN7OKOZ.mjs.map} +0 -0
  140. /package/{dist-web → dist-cli-web}/assets/ActionToolsPanel-Dp-ojXjO.css +0 -0
  141. /package/{dist-web → dist-cli-web}/assets/PageHeader-XIXMTomn.css +0 -0
  142. /package/{dist-web → dist-cli-web}/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css +0 -0
  143. /package/{dist-web → dist-cli-web}/assets/index-BfO_LpDv.css +0 -0
  144. /package/{dist-web → dist-cli-web}/assets/index-ByUaiVQK.css +0 -0
  145. /package/{dist-web → dist-cli-web}/assets/index-ZsZwPLWy.css +0 -0
  146. /package/{dist-web → dist-cli-web}/assets/logo-DXIuG_WE.svg +0 -0
  147. /package/{dist-web → dist-cli-web}/assets/relationship-galaxy-viewport-Cwfa51ER.js +0 -0
  148. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-B-i_56Av.js +0 -0
  149. /package/{dist-web → dist-cli-web}/assets/vendor-echarts-wordcloud-Ca8n6qS6.js +0 -0
  150. /package/{dist-web → dist-cli-web}/assets/vendor-snapdom--nZvo8Sg.js +0 -0
  151. /package/{dist-web → dist-cli-web}/assets/vendor-zrender-5DcYjSZk.js +0 -0
  152. /package/{dist-web → dist-cli-web}/favicon.ico +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  createEmptyContactsDiagnostics,
10
10
  normalizeContactsTimeRangePreset,
11
11
  resolveContactsTimeRange
12
- } from "./chunk-3J7WTUEE.mjs";
12
+ } from "./chunk-4V6UP7EW.mjs";
13
13
  import {
14
14
  PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
15
15
  buildPeopleRelationshipsNeighborhoodGraph,
@@ -18,7 +18,7 @@ import {
18
18
  computePeopleRelationshipsSnapshot,
19
19
  normalizePeopleRelationshipsTimeRangePreset,
20
20
  resolvePeopleRelationshipsTimeRange
21
- } from "./chunk-23AIEWCD.mjs";
21
+ } from "./chunk-S7YPBKQ3.mjs";
22
22
  import {
23
23
  ANNUAL_SUMMARY_ALGORITHM_VERSION
24
24
  } from "./chunk-YAQLYL52.mjs";
@@ -27,6 +27,8 @@ import {
27
27
  BUILTIN_PROVIDERS,
28
28
  BUILTIN_TOOL_CATALOG,
29
29
  CHART_CAPABILITY_SKILL_ID,
30
+ CHATLAB_FORMAT_VERSION,
31
+ CHATLAB_SUPPORTED_FORMAT_VERSIONS,
30
32
  CHAT_DB_INDEXES,
31
33
  CHAT_DB_SCHEMA,
32
34
  DEFAULT_GENERAL_ASSISTANT_ID,
@@ -34,6 +36,7 @@ import {
34
36
  FTS_TABLE_SCHEMA,
35
37
  GENERAL_ASSISTANT_IDS,
36
38
  KNOWN_PLATFORMS,
39
+ MessageType,
37
40
  StreamingThinkTagParser,
38
41
  appLogger,
39
42
  batchSegmentChineseWithStats,
@@ -76,6 +79,7 @@ import {
76
79
  isChatSessionDb,
77
80
  isNameMatchPlatform,
78
81
  isReasoningModel,
82
+ isSupportedChatLabFormatVersion,
79
83
  matchOwnerProfile,
80
84
  mergeConfirmedNames,
81
85
  mergeMembers,
@@ -97,8 +101,9 @@ import {
97
101
  updateMemberAliases,
98
102
  updateSessionOwnerId,
99
103
  writeAnnualSummarySnapshot,
100
- writeAuthProfile
101
- } from "./chunk-YVNZ73O5.mjs";
104
+ writeAuthProfile,
105
+ writeParseResultToDb
106
+ } from "./chunk-C3B7RXES.mjs";
102
107
  import {
103
108
  __commonJS,
104
109
  __export,
@@ -714,8 +719,8 @@ var require_FilterBase = __commonJS({
714
719
  var FilterBase = class _FilterBase extends Transform {
715
720
  static stringFilter(string, separator) {
716
721
  return (stack) => {
717
- const path33 = stack.join(separator);
718
- return path33.length === string.length && path33 === string || path33.length > string.length && path33.substr(0, string.length) === string && path33.substr(string.length, separator.length) === separator;
722
+ const path30 = stack.join(separator);
723
+ return path30.length === string.length && path30 === string || path30.length > string.length && path30.substr(0, string.length) === string && path30.substr(string.length, separator.length) === separator;
719
724
  };
720
725
  }
721
726
  static regExpFilter(regExp, separator) {
@@ -1057,9 +1062,9 @@ var require_stream_chain = __commonJS({
1057
1062
  var require_withParser = __commonJS({
1058
1063
  "../../node_modules/stream-json/utils/withParser.js"(exports, module) {
1059
1064
  "use strict";
1060
- var { chain: chain10 } = require_stream_chain();
1065
+ var { chain: chain9 } = require_stream_chain();
1061
1066
  var Parser = require_Parser();
1062
- var withParser = (fn, options2) => chain10([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1067
+ var withParser = (fn, options2) => chain9([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
1063
1068
  module.exports = withParser;
1064
1069
  }
1065
1070
  });
@@ -1137,7 +1142,7 @@ var require_Assembler = __commonJS({
1137
1142
  this.current = new Ctr();
1138
1143
  this.key = null;
1139
1144
  };
1140
- var Assembler = class _Assembler extends EventEmitter {
1145
+ var Assembler2 = class _Assembler extends EventEmitter {
1141
1146
  static connectTo(stream, options2) {
1142
1147
  return new _Assembler(options2).connectTo(stream);
1143
1148
  }
@@ -1169,12 +1174,12 @@ var require_Assembler = __commonJS({
1169
1174
  return (this.stack.length >> 1) + (this.done ? 0 : 1);
1170
1175
  }
1171
1176
  get path() {
1172
- const path33 = [];
1177
+ const path30 = [];
1173
1178
  for (let i = 0; i < this.stack.length; i += 2) {
1174
1179
  const key = this.stack[i + 1];
1175
- path33.push(key === null ? this.stack[i].length : key);
1180
+ path30.push(key === null ? this.stack[i].length : key);
1176
1181
  }
1177
- return path33;
1182
+ return path30;
1178
1183
  }
1179
1184
  dropToLevel(level) {
1180
1185
  if (level < this.depth) {
@@ -1256,11 +1261,11 @@ var require_Assembler = __commonJS({
1256
1261
  }
1257
1262
  }
1258
1263
  };
1259
- Assembler.prototype.stringValue = Assembler.prototype._saveValue;
1260
- Assembler.prototype.startObject = startObject(Object);
1261
- Assembler.prototype.startArray = startObject(Array);
1262
- Assembler.prototype.endArray = Assembler.prototype.endObject;
1263
- module.exports = Assembler;
1264
+ Assembler2.prototype.stringValue = Assembler2.prototype._saveValue;
1265
+ Assembler2.prototype.startObject = startObject(Object);
1266
+ Assembler2.prototype.startArray = startObject(Array);
1267
+ Assembler2.prototype.endArray = Assembler2.prototype.endObject;
1268
+ module.exports = Assembler2;
1264
1269
  }
1265
1270
  });
1266
1271
 
@@ -1269,7 +1274,7 @@ var require_StreamBase = __commonJS({
1269
1274
  "../../node_modules/stream-json/streamers/StreamBase.js"(exports, module) {
1270
1275
  "use strict";
1271
1276
  var { Transform } = __require("stream");
1272
- var Assembler = require_Assembler();
1277
+ var Assembler2 = require_Assembler();
1273
1278
  var Counter = class {
1274
1279
  constructor(initialDepth) {
1275
1280
  this.depth = initialDepth;
@@ -1298,7 +1303,7 @@ var require_StreamBase = __commonJS({
1298
1303
  this._filter = this._transform;
1299
1304
  }
1300
1305
  this._transform = this._wait || this._filter;
1301
- this._assembler = new Assembler(options2);
1306
+ this._assembler = new Assembler2(options2);
1302
1307
  }
1303
1308
  _transform(chunk, encoding, callback) {
1304
1309
  if (this._assembler[chunk.name]) {
@@ -1912,9 +1917,9 @@ var require_yauzl = __commonJS({
1912
1917
  exports.Entry = Entry;
1913
1918
  exports.LocalFileHeader = LocalFileHeader;
1914
1919
  exports.RandomAccessReader = RandomAccessReader;
1915
- function openPromise2(path33, options2) {
1920
+ function openPromise2(path30, options2) {
1916
1921
  return new Promise((resolve4, reject) => {
1917
- open(path33, { ...options2, lazyEntries: true }, function(err, zipfile) {
1922
+ open(path30, { ...options2, lazyEntries: true }, function(err, zipfile) {
1918
1923
  if (err) return reject(err);
1919
1924
  resolve4(zipfile);
1920
1925
  });
@@ -1944,7 +1949,7 @@ var require_yauzl = __commonJS({
1944
1949
  });
1945
1950
  });
1946
1951
  }
1947
- function open(path33, options2, callback) {
1952
+ function open(path30, options2, callback) {
1948
1953
  if (typeof options2 === "function") {
1949
1954
  callback = options2;
1950
1955
  options2 = null;
@@ -1956,7 +1961,7 @@ var require_yauzl = __commonJS({
1956
1961
  if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
1957
1962
  if (options2.strictFileNames == null) options2.strictFileNames = false;
1958
1963
  if (callback == null) callback = defaultCallback;
1959
- fs37.open(path33, "r", function(err, fd) {
1964
+ fs37.open(path30, "r", function(err, fd) {
1960
1965
  if (err) return callback(err);
1961
1966
  fromFd(fd, options2, function(err2, zipfile) {
1962
1967
  if (err2) fs37.close(fd, defaultCallback);
@@ -2810,122 +2815,9 @@ function removeChatLabTempDir(dirPath, scope, options2 = {}) {
2810
2815
  fs.rmSync(resolvedDir, { recursive: true, force: true });
2811
2816
  }
2812
2817
 
2813
- // ../../packages/node-runtime/src/import/write-parse-result.ts
2814
- function writeParseResultToDb(db, meta, members, messages) {
2815
- let messageCount = 0;
2816
- let skippedCount = 0;
2817
- db.transaction(() => {
2818
- db.prepare(
2819
- `INSERT INTO meta (name, platform, type, imported_at, group_id, group_avatar, owner_id)
2820
- VALUES (?, ?, ?, ?, ?, ?, ?)`
2821
- ).run(
2822
- meta.name,
2823
- meta.platform,
2824
- meta.type,
2825
- Math.floor(Date.now() / 1e3),
2826
- meta.groupId || null,
2827
- meta.groupAvatar || null,
2828
- meta.ownerId || null
2829
- );
2830
- const insertMember = db.prepare(
2831
- `INSERT OR IGNORE INTO member (platform_id, account_name, group_nickname, aliases, avatar, roles) VALUES (?, ?, ?, ?, ?, ?)`
2832
- );
2833
- const getMemberId = db.prepare(`SELECT id FROM member WHERE platform_id = ?`);
2834
- const memberIdMap = /* @__PURE__ */ new Map();
2835
- for (const member of members) {
2836
- insertMember.run(
2837
- member.platformId,
2838
- member.accountName || null,
2839
- member.groupNickname || null,
2840
- member.aliases ? JSON.stringify(member.aliases) : "[]",
2841
- member.avatar || null,
2842
- member.roles ? JSON.stringify(member.roles) : "[]"
2843
- );
2844
- const row = getMemberId.get(member.platformId);
2845
- memberIdMap.set(member.platformId, row.id);
2846
- }
2847
- const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
2848
- const accountNameTracker = /* @__PURE__ */ new Map();
2849
- const groupNicknameTracker = /* @__PURE__ */ new Map();
2850
- const insertMessage = db.prepare(
2851
- `INSERT INTO message (sender_id, sender_account_name, sender_group_nickname, ts, type, content, reply_to_message_id, platform_message_id)
2852
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
2853
- );
2854
- const insertNameHistory = db.prepare(
2855
- `INSERT INTO member_name_history (member_id, name_type, name, start_ts, end_ts) VALUES (?, ?, ?, ?, ?)`
2856
- );
2857
- const updateMemberAccountName = db.prepare(`UPDATE member SET account_name = ? WHERE platform_id = ?`);
2858
- const updateMemberGroupNickname = db.prepare(`UPDATE member SET group_nickname = ? WHERE platform_id = ?`);
2859
- const updateNameHistoryEndTs = db.prepare(
2860
- `UPDATE member_name_history SET end_ts = ? WHERE member_id = ? AND name_type = ? AND end_ts IS NULL`
2861
- );
2862
- for (const msg of sortedMessages) {
2863
- const senderId = memberIdMap.get(msg.senderPlatformId);
2864
- if (senderId === void 0) {
2865
- skippedCount++;
2866
- continue;
2867
- }
2868
- insertMessage.run(
2869
- senderId,
2870
- msg.senderAccountName || null,
2871
- msg.senderGroupNickname || null,
2872
- msg.timestamp,
2873
- msg.type,
2874
- msg.content,
2875
- msg.replyToMessageId || null,
2876
- msg.platformMessageId || null
2877
- );
2878
- messageCount++;
2879
- const accountName = msg.senderAccountName;
2880
- if (accountName) {
2881
- const tracker = accountNameTracker.get(msg.senderPlatformId);
2882
- if (!tracker) {
2883
- accountNameTracker.set(msg.senderPlatformId, {
2884
- currentName: accountName,
2885
- lastSeenTs: msg.timestamp
2886
- });
2887
- insertNameHistory.run(senderId, "account_name", accountName, msg.timestamp, null);
2888
- } else if (tracker.currentName !== accountName) {
2889
- updateNameHistoryEndTs.run(msg.timestamp, senderId, "account_name");
2890
- insertNameHistory.run(senderId, "account_name", accountName, msg.timestamp, null);
2891
- tracker.currentName = accountName;
2892
- tracker.lastSeenTs = msg.timestamp;
2893
- } else {
2894
- tracker.lastSeenTs = msg.timestamp;
2895
- }
2896
- }
2897
- const groupNickname = msg.senderGroupNickname;
2898
- if (groupNickname) {
2899
- const tracker = groupNicknameTracker.get(msg.senderPlatformId);
2900
- if (!tracker) {
2901
- groupNicknameTracker.set(msg.senderPlatformId, {
2902
- currentName: groupNickname,
2903
- lastSeenTs: msg.timestamp
2904
- });
2905
- insertNameHistory.run(senderId, "group_nickname", groupNickname, msg.timestamp, null);
2906
- } else if (tracker.currentName !== groupNickname) {
2907
- updateNameHistoryEndTs.run(msg.timestamp, senderId, "group_nickname");
2908
- insertNameHistory.run(senderId, "group_nickname", groupNickname, msg.timestamp, null);
2909
- tracker.currentName = groupNickname;
2910
- tracker.lastSeenTs = msg.timestamp;
2911
- } else {
2912
- tracker.lastSeenTs = msg.timestamp;
2913
- }
2914
- }
2915
- }
2916
- for (const [platformId, tracker] of accountNameTracker.entries()) {
2917
- updateMemberAccountName.run(tracker.currentName, platformId);
2918
- }
2919
- for (const [platformId, tracker] of groupNicknameTracker.entries()) {
2920
- updateMemberGroupNickname.run(tracker.currentName, platformId);
2921
- }
2922
- });
2923
- return { messageCount, memberCount: members.length, skippedCount };
2924
- }
2925
-
2926
2818
  // ../../packages/parser/src/index.ts
2927
2819
  import * as fs18 from "fs";
2928
- import * as path14 from "path";
2820
+ import * as path11 from "path";
2929
2821
 
2930
2822
  // ../../packages/parser/src/sniffer.ts
2931
2823
  import * as fs2 from "fs";
@@ -3315,8 +3207,8 @@ var { pick } = import_Pick.default;
3315
3207
  var { streamValues } = import_StreamValues.default;
3316
3208
  var { chain } = import_stream_chain.default;
3317
3209
  function extractNameFromFilePath(filePath) {
3318
- const basename13 = path3.basename(filePath);
3319
- const name = basename13.replace(/\.json$/i, "");
3210
+ const basename10 = path3.basename(filePath);
3211
+ const name = basename10.replace(/\.json$/i, "");
3320
3212
  return name || "\u672A\u77E5\u7FA4\u804A";
3321
3213
  }
3322
3214
  var feature = {
@@ -3486,8 +3378,8 @@ import * as fs5 from "fs";
3486
3378
  import * as readline from "readline";
3487
3379
  import * as path4 from "path";
3488
3380
  function extractNameFromFilePath2(filePath) {
3489
- const basename13 = path4.basename(filePath);
3490
- const name = basename13.replace(/\.jsonl$/i, "");
3381
+ const basename10 = path4.basename(filePath);
3382
+ const name = basename10.replace(/\.jsonl$/i, "");
3491
3383
  return name || "\u672A\u77E5\u7FA4\u804A";
3492
3384
  }
3493
3385
  function parseLine(line) {
@@ -3509,8 +3401,10 @@ var feature2 = {
3509
3401
  // 低优先级,让其他格式先匹配
3510
3402
  extensions: [".jsonl"],
3511
3403
  signatures: {
3512
- // 第一行必须是 header 类型,包含 chatlab 信息
3513
- head: [/^\s*\{.*"_type"\s*:\s*"header".*"chatlab"/]
3404
+ // 跳过格式允许的空行和注释后,分别匹配首条数据中的 header 类型和 chatlab 对象。
3405
+ head: [
3406
+ /^\uFEFF?(?:[ \t]*(?:#[^\r\n]*)?\r?\n)*[ \t]*\{(?=[^\r\n]*"_type"\s*:\s*"header")(?=[^\r\n]*"chatlab"\s*:)[^\r\n]*\}/
3407
+ ]
3514
3408
  }
3515
3409
  };
3516
3410
  async function* parseChatLabJsonl(options2) {
@@ -3525,6 +3419,7 @@ async function* parseChatLabJsonl(options2) {
3525
3419
  const members = [];
3526
3420
  const memberMap = /* @__PURE__ */ new Map();
3527
3421
  const messageBatch = [];
3422
+ let explicitMembersEmitted = false;
3528
3423
  let meta = null;
3529
3424
  let headerParsed = false;
3530
3425
  const fileStream = fs5.createReadStream(filePath, { encoding: "utf-8" });
@@ -3560,7 +3455,9 @@ async function* parseChatLabJsonl(options2) {
3560
3455
  platformId: parsed.platformId,
3561
3456
  accountName: parsed.accountName,
3562
3457
  groupNickname: parsed.groupNickname,
3563
- avatar: parsed.avatar
3458
+ aliases: parsed.aliases,
3459
+ avatar: parsed.avatar,
3460
+ roles: parsed.roles
3564
3461
  };
3565
3462
  members.push(member);
3566
3463
  memberMap.set(parsed.platformId, member);
@@ -3595,6 +3492,11 @@ async function* parseChatLabJsonl(options2) {
3595
3492
  });
3596
3493
  messagesProcessed++;
3597
3494
  if (messageBatch.length >= batchSize) {
3495
+ if (members.length > 0 && !explicitMembersEmitted) {
3496
+ yield { type: "members", data: members.slice() };
3497
+ explicitMembersEmitted = true;
3498
+ }
3499
+ yield { type: "messages", data: messageBatch.splice(0) };
3598
3500
  const progress = createProgress(
3599
3501
  "parsing",
3600
3502
  bytesRead,
@@ -3602,6 +3504,7 @@ async function* parseChatLabJsonl(options2) {
3602
3504
  messagesProcessed,
3603
3505
  `\u5DF2\u5904\u7406 ${messagesProcessed.toLocaleString()} \u6761\u6D88\u606F...`
3604
3506
  );
3507
+ yield { type: "progress", data: progress };
3605
3508
  onProgress?.(progress);
3606
3509
  }
3607
3510
  break;
@@ -3616,13 +3519,14 @@ async function* parseChatLabJsonl(options2) {
3616
3519
  yield { type: "meta", data: meta };
3617
3520
  }
3618
3521
  if (members.length > 0) {
3619
- yield { type: "members", data: members };
3522
+ if (!explicitMembersEmitted) {
3523
+ yield { type: "members", data: members };
3524
+ }
3620
3525
  } else if (memberMap.size > 0) {
3621
3526
  yield { type: "members", data: Array.from(memberMap.values()) };
3622
3527
  }
3623
- for (let i = 0; i < messageBatch.length; i += batchSize) {
3624
- const batch = messageBatch.slice(i, i + batchSize);
3625
- yield { type: "messages", data: batch };
3528
+ if (messageBatch.length > 0) {
3529
+ yield { type: "messages", data: messageBatch };
3626
3530
  }
3627
3531
  const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
3628
3532
  yield { type: "progress", data: doneProgress };
@@ -3952,8 +3856,8 @@ function extractJsonObject2(content, key) {
3952
3856
  return null;
3953
3857
  }
3954
3858
  function extractNameFromFilePath3(filePath) {
3955
- const basename13 = path6.basename(filePath);
3956
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3859
+ const basename10 = path6.basename(filePath);
3860
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
3957
3861
  }
3958
3862
  function isPlaceholderSenderId(id) {
3959
3863
  return id === "0" || id === "\u672A\u77E5";
@@ -4553,8 +4457,8 @@ var { pick: pick4 } = import_Pick4.default;
4553
4457
  var { streamValues: streamValues4 } = import_StreamValues4.default;
4554
4458
  var { chain: chain4 } = import_stream_chain4.default;
4555
4459
  function extractNameFromFilePath4(filePath) {
4556
- const basename13 = path8.basename(filePath);
4557
- const name = basename13.replace(/\.json$/i, "");
4460
+ const basename10 = path8.basename(filePath);
4461
+ const name = basename10.replace(/\.json$/i, "");
4558
4462
  return name || "\u672A\u77E5\u804A\u5929";
4559
4463
  }
4560
4464
  var feature5 = {
@@ -5465,13 +5369,16 @@ var module_8 = {
5465
5369
  var telegram_native_default = module_8;
5466
5370
 
5467
5371
  // ../../packages/parser/src/formats/telegram-native-single.ts
5468
- var import_stream_chain7 = __toESM(require_stream_chain(), 1);
5372
+ var import_Assembler = __toESM(require_Assembler(), 1);
5469
5373
  var import_stream_json7 = __toESM(require_stream_json(), 1);
5470
- var import_StreamValues7 = __toESM(require_StreamValues(), 1);
5471
5374
  import * as fs12 from "fs";
5472
- var { chain: chain7 } = import_stream_chain7.default;
5375
+
5376
+ // ../../packages/parser/src/browser/telegram.ts
5377
+ var TELEGRAM_SINGLE_CHAT_TYPE_PATTERN = /"type"\s*:\s*"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)"/;
5378
+ var TELEGRAM_SINGLE_HEAD_SIGNATURES = [/^\s*\{\s*(?:\r?\n\s*)?"name"\s*:/];
5379
+
5380
+ // ../../packages/parser/src/formats/telegram-native-single.ts
5473
5381
  var { parser: parser8 } = import_stream_json7.default;
5474
- var { streamValues: streamValues7 } = import_StreamValues7.default;
5475
5382
  var feature9 = {
5476
5383
  id: "telegram-native-single",
5477
5384
  name: "Telegram \u5355\u804A\u5929\u5BFC\u51FA (JSON)",
@@ -5479,98 +5386,97 @@ var feature9 = {
5479
5386
  priority: 23,
5480
5387
  extensions: [".json"],
5481
5388
  signatures: {
5482
- // Telegram 单聊天导出特征:文件以 "name" 作为第一个 JSON 键
5483
- // 这与全量导出(以 "about" 开头)有效区分,避免嵌套字段误匹配
5484
- head: [/^\s*\{\s*\r?\n\s*"name"\s*:/],
5389
+ head: TELEGRAM_SINGLE_HEAD_SIGNATURES,
5485
5390
  requiredFields: ["messages"],
5486
- fieldPatterns: {
5487
- // Telegram 特有的聊天类型值,精确区分
5488
- telegramChatType: /"type"\s*:\s*"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)"/
5489
- }
5391
+ fieldPatterns: { telegramChatType: TELEGRAM_SINGLE_CHAT_TYPE_PATTERN }
5490
5392
  }
5491
5393
  };
5492
5394
  async function* parseTelegramSingle(options2) {
5493
5395
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5494
5396
  const totalBytes = getFileSize(filePath);
5495
- let messagesProcessed = 0;
5496
5397
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5497
5398
  yield { type: "progress", data: initialProgress };
5498
5399
  onProgress?.(initialProgress);
5499
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 Telegram \u5355\u804A\u5929 JSON \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5500
- const chatData = await new Promise((resolve4, reject) => {
5501
- const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5502
- const pipeline2 = chain7([readStream, parser8(), streamValues7()]);
5503
- let found = false;
5504
- pipeline2.on("data", ({ value }) => {
5505
- if (!found) {
5506
- found = true;
5507
- resolve4(value);
5508
- }
5509
- });
5510
- pipeline2.on("end", () => {
5511
- if (!found) resolve4(null);
5512
- });
5513
- pipeline2.on("error", reject);
5514
- });
5515
- if (!chatData) {
5516
- onLog?.("error", "\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6");
5517
- yield { type: "error", data: new Error("\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6") };
5518
- return;
5519
- }
5520
- onLog?.("info", `\u804A\u5929: "${chatData.name}", \u7C7B\u578B: ${chatData.type}, \u6D88\u606F\u6570: ${chatData.messages?.length || 0}`);
5521
- const chatType = mapChatType(chatData.type);
5522
- const meta = {
5523
- name: chatData.name || `Telegram Chat ${chatData.id}`,
5524
- platform: KNOWN_PLATFORMS.TELEGRAM,
5525
- type: chatType,
5526
- groupId: chatType === "group" /* GROUP */ ? String(chatData.id) : void 0
5527
- };
5528
- yield { type: "meta", data: meta };
5400
+ onLog?.("info", `Starting Telegram single-chat JSON parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5401
+ let messagesProcessed = 0;
5529
5402
  const memberMap = /* @__PURE__ */ new Map();
5530
5403
  const messageBatch = [];
5531
- const messages = chatData.messages || [];
5532
- for (const msg of messages) {
5533
- let senderPlatformId;
5534
- let senderName;
5535
- if (msg.type === "service") {
5536
- senderPlatformId = extractPlatformId(msg.actor_id);
5537
- senderName = msg.actor || "\u7CFB\u7EDF";
5538
- } else {
5539
- senderPlatformId = extractPlatformId(msg.from_id);
5540
- senderName = msg.from || senderPlatformId;
5541
- }
5542
- if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
5543
- memberMap.set(senderPlatformId, {
5544
- platformId: senderPlatformId,
5545
- accountName: senderName
5546
- });
5404
+ try {
5405
+ const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
5406
+ const tokenStream = readStream.pipe(parser8());
5407
+ let depth = 0;
5408
+ let rootKey = null;
5409
+ let chatName = null;
5410
+ let telegramType = null;
5411
+ let chatId = null;
5412
+ let messagesArrayDepth = null;
5413
+ let messageAssembler = null;
5414
+ let meta = null;
5415
+ for await (const rawToken of tokenStream) {
5416
+ const token = rawToken;
5417
+ if (messageAssembler) {
5418
+ messageAssembler.consume(token);
5419
+ if (messageAssembler.done) {
5420
+ const message = messageAssembler.current;
5421
+ messageAssembler = null;
5422
+ const parsedMessage = parseTelegramMessage(message, memberMap);
5423
+ if (parsedMessage) {
5424
+ messageBatch.push(parsedMessage);
5425
+ messagesProcessed++;
5426
+ }
5427
+ if (messageBatch.length >= batchSize) {
5428
+ yield { type: "messages", data: messageBatch.splice(0) };
5429
+ const progress = createProgress(
5430
+ "parsing",
5431
+ readStream.bytesRead,
5432
+ totalBytes,
5433
+ messagesProcessed,
5434
+ `Processed ${messagesProcessed} messages`
5435
+ );
5436
+ yield { type: "progress", data: progress };
5437
+ onProgress?.(progress);
5438
+ }
5439
+ }
5440
+ } else if (token.name === "keyValue" && depth === 1) {
5441
+ rootKey = token.value ?? null;
5442
+ } else if (token.name === "stringValue" && depth === 1) {
5443
+ if (rootKey === "name") chatName = token.value ?? "";
5444
+ if (rootKey === "type") telegramType = token.value ?? "";
5445
+ } else if (token.name === "numberValue" && depth === 1 && rootKey === "id") {
5446
+ const parsedId = Number(token.value);
5447
+ if (Number.isFinite(parsedId)) chatId = parsedId;
5448
+ }
5449
+ if (token.name === "startArray" && depth === 1 && rootKey === "messages") {
5450
+ if (chatName === null || telegramType === null || chatId === null) {
5451
+ throw new Error("Invalid Telegram single-chat JSON export");
5452
+ }
5453
+ const chatType = mapChatType(telegramType);
5454
+ meta = {
5455
+ name: chatName || `Telegram Chat ${chatId}`,
5456
+ platform: KNOWN_PLATFORMS.TELEGRAM,
5457
+ type: chatType,
5458
+ groupId: chatType === "group" /* GROUP */ ? String(chatId) : void 0
5459
+ };
5460
+ messagesArrayDepth = depth + 1;
5461
+ yield { type: "meta", data: meta };
5462
+ } else if (token.name === "startObject" && messagesArrayDepth !== null && depth === messagesArrayDepth) {
5463
+ messageAssembler = new import_Assembler.default();
5464
+ messageAssembler.consume(token);
5465
+ }
5466
+ if (token.name === "startObject" || token.name === "startArray") {
5467
+ depth++;
5468
+ } else if (token.name === "endObject" || token.name === "endArray") {
5469
+ depth--;
5470
+ }
5547
5471
  }
5548
- const timestamp = parseInt(msg.date_unixtime, 10);
5549
- if (isNaN(timestamp)) continue;
5550
- const parsedMsg = {
5551
- platformMessageId: String(msg.id),
5552
- senderPlatformId,
5553
- senderAccountName: senderName,
5554
- timestamp,
5555
- type: detectMessageType(msg),
5556
- content: buildContent(msg),
5557
- replyToMessageId: msg.reply_to_message_id ? String(msg.reply_to_message_id) : void 0
5558
- };
5559
- messageBatch.push(parsedMsg);
5560
- messagesProcessed++;
5561
- if (messageBatch.length >= batchSize) {
5562
- yield { type: "messages", data: [...messageBatch] };
5563
- messageBatch.length = 0;
5564
- const progress = createProgress(
5565
- "parsing",
5566
- 0,
5567
- totalBytes,
5568
- messagesProcessed,
5569
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
5570
- );
5571
- yield { type: "progress", data: progress };
5572
- onProgress?.(progress);
5472
+ if (!meta || messagesArrayDepth === null || messageAssembler) {
5473
+ throw new Error("Invalid Telegram single-chat JSON export");
5573
5474
  }
5475
+ } catch (error) {
5476
+ const parseError = error instanceof Error ? error : new Error(String(error));
5477
+ onLog?.("error", "Telegram single-chat JSON parsing failed");
5478
+ yield { type: "error", data: parseError };
5479
+ return;
5574
5480
  }
5575
5481
  yield { type: "members", data: Array.from(memberMap.values()) };
5576
5482
  if (messageBatch.length > 0) {
@@ -5579,77 +5485,63 @@ async function* parseTelegramSingle(options2) {
5579
5485
  const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5580
5486
  yield { type: "progress", data: doneProgress };
5581
5487
  onProgress?.(doneProgress);
5582
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5488
+ onLog?.(
5489
+ "info",
5490
+ `Telegram single-chat JSON parsing completed: ${messagesProcessed} messages, ${memberMap.size} members`
5491
+ );
5583
5492
  yield {
5584
5493
  type: "done",
5585
5494
  data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5586
5495
  };
5587
5496
  }
5588
- var parser_8 = {
5589
- feature: feature9,
5590
- parse: parseTelegramSingle
5591
- };
5592
- var module_9 = {
5593
- feature: feature9,
5594
- parser: parser_8
5595
- };
5497
+ function parseTelegramMessage(message, memberMap) {
5498
+ const isService = message.type === "service";
5499
+ const senderPlatformId = extractPlatformId(isService ? message.actor_id : message.from_id);
5500
+ const senderName = isService ? message.actor || "System" : message.from || senderPlatformId;
5501
+ if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
5502
+ memberMap.set(senderPlatformId, {
5503
+ platformId: senderPlatformId,
5504
+ accountName: senderName
5505
+ });
5506
+ }
5507
+ const timestamp = Number.parseInt(message.date_unixtime, 10);
5508
+ if (Number.isNaN(timestamp)) return null;
5509
+ return {
5510
+ platformMessageId: String(message.id),
5511
+ senderPlatformId,
5512
+ senderAccountName: senderName,
5513
+ timestamp,
5514
+ type: detectMessageType(message),
5515
+ content: buildContent(message),
5516
+ replyToMessageId: message.reply_to_message_id ? String(message.reply_to_message_id) : void 0
5517
+ };
5518
+ }
5519
+ var parser_8 = { feature: feature9, parse: parseTelegramSingle };
5520
+ var module_9 = { feature: feature9, parser: parser_8 };
5596
5521
  var telegram_native_single_default = module_9;
5597
5522
 
5598
5523
  // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5599
5524
  import * as fs13 from "fs";
5600
- import * as path9 from "path";
5601
5525
  import * as readline3 from "readline";
5602
- function extractNameFromFilePath5(filePath) {
5603
- const basename13 = path9.basename(filePath);
5604
- const matchZhCn = basename13.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5605
- if (matchZhCn) return matchZhCn[1].trim();
5606
- const matchZhTw = basename13.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
5607
- if (matchZhTw) return matchZhTw[1].trim();
5608
- return basename13.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
5609
- }
5610
- function shouldInferPrivateChatName(filePath, fallbackName) {
5611
- const basename13 = path9.basename(filePath);
5612
- return /^_chat\.txt$/i.test(basename13) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
5613
- }
5614
- function inferPrivateChatName(memberMap, fallbackName) {
5615
- const member = Array.from(memberMap.values()).find((item) => item.platformId !== "system");
5616
- return member?.nickname || fallbackName;
5617
- }
5618
- var feature10 = {
5619
- id: "whatsapp-native-txt",
5620
- name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
5621
- platform: KNOWN_PLATFORMS.WHATSAPP,
5622
- priority: 25,
5623
- extensions: [".txt"],
5624
- signatures: {
5625
- // WhatsApp 导出文件的特征(中文/英文)
5626
- // 注意:仅保留 WhatsApp 独有的特征,避免误匹配其他 TXT 格式(如 LINE)
5627
- head: [
5628
- /消息和通话已进行端到端加密/,
5629
- // 简体中文加密提示(WhatsApp 独有)
5630
- /訊息與通話已受端對端加密保護/,
5631
- // 繁体中文加密提示(WhatsApp 独有)
5632
- /Messages and calls are end-to-end encrypted/i,
5633
- // 英文加密提示(WhatsApp 独有)
5634
- /你发送给自己的消息已进行端到端加密/,
5635
- // 简体中文自己对话提示(WhatsApp 独有)
5636
- /\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?\s+- /i,
5637
- // 消息行格式特征(无方括号,含 " - " 分隔符,WhatsApp 独有)
5638
- /\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
5639
- // 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)
5640
- ],
5641
- // 文件名特征:与xxx的 WhatsApp 聊天.txt
5642
- filename: [/^与.+的\s*WhatsApp\s*聊天\.txt$/i, /^與.+的\s*WhatsApp\s*對話\.txt$/i, /WhatsApp/i]
5643
- }
5644
- };
5645
- function cleanLine(line) {
5646
- return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
5647
- }
5648
- var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|上午|下午|午前|午後|오전|오후))?) - (.+)$/i;
5526
+
5527
+ // ../../packages/parser/src/browser/whatsapp.ts
5528
+ var WHATSAPP_HEAD_SIGNATURES = [
5529
+ /消息和通话已进行端到端加密/,
5530
+ /訊息與通話已受端對端加密保護/,
5531
+ /Messages and calls are end-to-end encrypted/i,
5532
+ /你发送给自己的消息已进行端到端加密/,
5533
+ /\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|\u4e0a午|\u4e0b午|\u5348前|\u5348後|오전|오후))?\s+- /i,
5534
+ /\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
5535
+ ];
5536
+ var WHATSAPP_FILENAME_SIGNATURES = [
5537
+ /^与.+的\s*WhatsApp\s*聊天\.txt$/i,
5538
+ /^與.+的\s*WhatsApp\s*對話\.txt$/i,
5539
+ /WhatsApp/i
5540
+ ];
5541
+ var MESSAGE_LINE_REGEX_V1 = /^(\d{1,4}\/\d{1,2}\/\d{2,4},?\s+\d{1,2}:\d{2}(?::\d{2})?(?:[\s\u2009\u202F]*(?:AM|PM|A\.M\.|P\.M\.|\u4e0a午|\u4e0b午|\u5348前|\u5348後|오전|오후))?) - (.+)$/i;
5649
5542
  var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
5650
5543
  var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
5651
5544
  var SYSTEM_MESSAGE_PATTERNS = [
5652
- // 简体中文系统消息
5653
5545
  /消息和通话已进行端到端加密/,
5654
5546
  /创建了此群组/,
5655
5547
  /加入群组/,
@@ -5661,7 +5553,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5661
5553
  /这条消息已删除/,
5662
5554
  /限时消息功能/,
5663
5555
  /正在等待此消息/,
5664
- // 繁体中文系统消息
5665
5556
  /訊息與通話已受端對端加密保護/,
5666
5557
  /建立了此群組/,
5667
5558
  /加入了群組/,
@@ -5671,7 +5562,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5671
5562
  /已變更本群組/,
5672
5563
  /此訊息已刪除/,
5673
5564
  /限時訊息/,
5674
- // 英文系统消息
5675
5565
  /Messages and calls are end-to-end encrypted/i,
5676
5566
  /created this group/i,
5677
5567
  /joined the group/i,
@@ -5682,21 +5572,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
5682
5572
  /This message was deleted/i,
5683
5573
  /disappearing messages/i
5684
5574
  ];
5685
- function isSystemMessage(content) {
5686
- return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
5687
- }
5688
- function detectMessageType2(content) {
5689
- const trimmed = content.trim();
5690
- if (trimmed === "<\u7701\u7565\u5F71\u97F3\u5185\u5BB9>" || trimmed === "<\u5DF2\u7701\u7565\u591A\u5A92\u9AD4\u6A94\u6848>" || trimmed === "\u5716\u7247\u5DF2\u7565\u53BB" || trimmed === "\u5F71\u7247\u5DF2\u7565\u53BB" || trimmed === "\u97F3\u8A0A\u5DF2\u7565\u53BB" || trimmed === "image omitted" || trimmed === "video omitted" || trimmed === "audio omitted")
5691
- return 1 /* IMAGE */;
5692
- if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A"))
5693
- return 4 /* FILE */;
5694
- if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
5695
- if (trimmed === "\u8FD9\u6761\u6D88\u606F\u5DF2\u5220\u9664" || trimmed.startsWith("\u6B64\u8A0A\u606F\u5DF2\u522A\u9664") || trimmed.startsWith("\u4F60\u5DF2\u522A\u9664\u6B64\u8A0A\u606F"))
5696
- return 81 /* RECALL */;
5697
- if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
5698
- return 0 /* TEXT */;
5699
- }
5700
5575
  var AMPM_MARKERS = [
5701
5576
  [/\bPM\b/i, true],
5702
5577
  [/\bP\.M\.(?!\w)/i, true],
@@ -5709,191 +5584,226 @@ var AMPM_MARKERS = [
5709
5584
  [/午前/, false],
5710
5585
  [/오전/, false]
5711
5586
  ];
5712
- function parseFlexibleV2Timestamp(raw) {
5713
- let str = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
5714
- let isPM = null;
5715
- for (const [pattern, pm] of AMPM_MARKERS) {
5716
- if (pattern.test(str)) {
5717
- isPM = pm;
5718
- str = str.replace(pattern, "").trim();
5719
- break;
5587
+ var WhatsAppTextAccumulator = class {
5588
+ constructor(fileName) {
5589
+ this.fileName = fileName;
5590
+ this.fileChatName = extractNameFromFileName(fileName);
5591
+ }
5592
+ fileName;
5593
+ fileChatName;
5594
+ memberMap = /* @__PURE__ */ new Map();
5595
+ messages = [];
5596
+ currentMessage = null;
5597
+ skippedLines = 0;
5598
+ get messageCount() {
5599
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5600
+ }
5601
+ pushLine(line) {
5602
+ const cleanedLine = cleanLine(line);
5603
+ const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
5604
+ if (v1Match) {
5605
+ this.saveCurrentMessage();
5606
+ this.currentMessage = createPendingMessage(parseFlexibleTimestamp(v1Match[1]) ?? 0, v1Match[2]);
5607
+ return;
5608
+ }
5609
+ const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
5610
+ if (v2Match) {
5611
+ const timestamp = parseFlexibleTimestamp(v2Match[1]);
5612
+ if (timestamp !== null) {
5613
+ this.saveCurrentMessage();
5614
+ this.currentMessage = createPendingMessage(timestamp, v2Match[2]);
5615
+ return;
5616
+ }
5617
+ }
5618
+ if (this.currentMessage && cleanedLine) {
5619
+ this.currentMessage.contentLines.push(cleanedLine);
5620
+ } else if (cleanedLine) {
5621
+ this.skippedLines += 1;
5622
+ }
5623
+ }
5624
+ finish() {
5625
+ this.saveCurrentMessage();
5626
+ const chatType = this.memberMap.size > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
5627
+ const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(this.fileName, this.fileChatName) ? inferPrivateChatName(this.memberMap, this.fileChatName) : this.fileChatName;
5628
+ return {
5629
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.WHATSAPP, type: chatType },
5630
+ members: Array.from(this.memberMap.values(), (member) => ({
5631
+ platformId: member.platformId,
5632
+ accountName: member.nickname
5633
+ })),
5634
+ messages: this.messages,
5635
+ skippedLines: this.skippedLines
5636
+ };
5637
+ }
5638
+ saveCurrentMessage() {
5639
+ if (!this.currentMessage) return;
5640
+ const content = this.currentMessage.contentLines.join("\n").trim();
5641
+ const senderPlatformId = this.currentMessage.sender ?? "system";
5642
+ const senderName = this.currentMessage.sender ?? "\u7CFB\u7EDF\u6D88\u606F";
5643
+ this.messages.push({
5644
+ senderPlatformId,
5645
+ senderAccountName: senderName,
5646
+ timestamp: this.currentMessage.timestamp,
5647
+ type: detectMessageType2(content),
5648
+ content: content || null
5649
+ });
5650
+ if (this.currentMessage.sender) {
5651
+ this.memberMap.set(senderPlatformId, { platformId: senderPlatformId, nickname: senderName });
5720
5652
  }
5653
+ this.currentMessage = null;
5654
+ }
5655
+ };
5656
+ function createPendingMessage(timestamp, restContent) {
5657
+ const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5658
+ if (senderMatch && !isSystemMessage(restContent)) {
5659
+ return { timestamp, sender: senderMatch[1].trim(), contentLines: [senderMatch[2]] };
5660
+ }
5661
+ return { timestamp, sender: null, contentLines: [restContent] };
5662
+ }
5663
+ function extractNameFromFileName(fileName) {
5664
+ const basename10 = getBasename(fileName);
5665
+ const simplifiedChinese = basename10.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
5666
+ if (simplifiedChinese) return simplifiedChinese[1].trim();
5667
+ const traditionalChinese = basename10.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
5668
+ if (traditionalChinese) return traditionalChinese[1].trim();
5669
+ return basename10.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
5670
+ }
5671
+ function shouldInferPrivateChatName(fileName, fallbackName) {
5672
+ const basename10 = getBasename(fileName);
5673
+ return /^_chat\.txt$/i.test(basename10) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
5674
+ }
5675
+ function inferPrivateChatName(memberMap, fallbackName) {
5676
+ return Array.from(memberMap.values()).find((member) => member.platformId !== "system")?.nickname ?? fallbackName;
5677
+ }
5678
+ function getBasename(fileName) {
5679
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5680
+ }
5681
+ function cleanLine(line) {
5682
+ return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
5683
+ }
5684
+ function isSystemMessage(content) {
5685
+ return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
5686
+ }
5687
+ function detectMessageType2(content) {
5688
+ const trimmed = content.trim();
5689
+ if (trimmed === "<\u7701\u7565\u5F71\u97F3\u5185\u5BB9>" || trimmed === "<\u5DF2\u7701\u7565\u591A\u5A92\u9AD4\u6A94\u6848>" || trimmed === "\u5716\u7247\u5DF2\u7565\u53BB" || trimmed === "\u5F71\u7247\u5DF2\u7565\u53BB" || trimmed === "\u97F3\u8A0A\u5DF2\u7565\u53BB" || trimmed === "image omitted" || trimmed === "video omitted" || trimmed === "audio omitted") {
5690
+ return 1 /* IMAGE */;
5691
+ }
5692
+ if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A")) {
5693
+ return 4 /* FILE */;
5694
+ }
5695
+ if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
5696
+ if (trimmed === "\u8FD9\u6761\u6D88\u606F\u5DF2\u5220\u9664" || trimmed.startsWith("\u6B64\u8A0A\u606F\u5DF2\u522A\u9664") || trimmed.startsWith("\u4F60\u5DF2\u522A\u9664\u6B64\u8A0A\u606F")) {
5697
+ return 81 /* RECALL */;
5698
+ }
5699
+ if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
5700
+ return 0 /* TEXT */;
5701
+ }
5702
+ function parseFlexibleTimestamp(raw) {
5703
+ let value = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
5704
+ let isPm = null;
5705
+ for (const [pattern, pm] of AMPM_MARKERS) {
5706
+ if (!pattern.test(value)) continue;
5707
+ isPm = pm;
5708
+ value = value.replace(pattern, "").trim();
5709
+ break;
5721
5710
  }
5722
- str = str.replace(/,/g, " ").replace(/\s+/g, " ").trim();
5723
- const match = str.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
5711
+ value = value.replace(/,/g, " ").replace(/\s+/g, " ").trim();
5712
+ const match = value.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
5724
5713
  if (!match) return null;
5725
- const [, datePart, timePart] = match;
5726
- const dateParts = datePart.split("/").map((s) => parseInt(s, 10));
5727
- const timeParts = timePart.split(":").map((s) => parseInt(s, 10));
5728
- let year, month, day;
5714
+ const dateParts = match[1].split("/").map((part) => Number.parseInt(part, 10));
5715
+ const timeParts = match[2].split(":").map((part) => Number.parseInt(part, 10));
5716
+ let year;
5717
+ let month;
5718
+ let day;
5729
5719
  if (dateParts[0] > 31) {
5730
- year = dateParts[0];
5731
- month = dateParts[1];
5732
- day = dateParts[2];
5720
+ ;
5721
+ [year, month, day] = dateParts;
5733
5722
  } else if (dateParts[2] > 31) {
5734
- day = dateParts[0];
5735
- month = dateParts[1];
5736
- year = dateParts[2];
5723
+ ;
5724
+ [day, month, year] = dateParts;
5737
5725
  } else {
5738
- month = dateParts[0];
5739
- day = dateParts[1];
5726
+ ;
5727
+ [month, day] = dateParts;
5740
5728
  year = 2e3 + dateParts[2];
5741
5729
  }
5742
5730
  let hour = timeParts[0];
5743
5731
  const minute = timeParts[1];
5744
5732
  const second = timeParts[2] ?? 0;
5745
- if (isPM === true && hour !== 12) hour += 12;
5746
- if (isPM === false && hour === 12) hour = 0;
5733
+ if (isPm === true && hour !== 12) hour += 12;
5734
+ if (isPm === false && hour === 12) hour = 0;
5747
5735
  const date = new Date(year, month - 1, day, hour, minute, second);
5748
- const ts = Math.floor(date.getTime() / 1e3);
5749
- return isNaN(ts) ? null : ts;
5750
- }
5751
- function parseV1Timestamp(timeStr) {
5752
- return parseFlexibleV2Timestamp(timeStr) ?? 0;
5736
+ const timestamp = Math.floor(date.getTime() / 1e3);
5737
+ return Number.isNaN(timestamp) ? null : timestamp;
5753
5738
  }
5739
+
5740
+ // ../../packages/parser/src/formats/whatsapp-native-txt.ts
5741
+ var feature10 = {
5742
+ id: "whatsapp-native-txt",
5743
+ name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
5744
+ platform: KNOWN_PLATFORMS.WHATSAPP,
5745
+ priority: 25,
5746
+ extensions: [".txt"],
5747
+ signatures: {
5748
+ head: WHATSAPP_HEAD_SIGNATURES,
5749
+ filename: WHATSAPP_FILENAME_SIGNATURES
5750
+ }
5751
+ };
5754
5752
  async function* parseWhatsApp(options2) {
5755
5753
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5756
5754
  const totalBytes = getFileSize(filePath);
5755
+ const accumulator = new WhatsAppTextAccumulator(filePath);
5757
5756
  let bytesRead = 0;
5758
- let messagesProcessed = 0;
5759
- let skippedLines = 0;
5757
+ let lastReportedMessages = 0;
5760
5758
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5761
5759
  yield { type: "progress", data: initialProgress };
5762
5760
  onProgress?.(initialProgress);
5763
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 WhatsApp TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5764
- const fileChatName = extractNameFromFilePath5(filePath);
5765
- const memberMap = /* @__PURE__ */ new Map();
5766
- const messages = [];
5767
- let currentMessage = null;
5768
- const saveCurrentMessage = () => {
5769
- if (currentMessage) {
5770
- const content = currentMessage.contentLines.join("\n").trim();
5771
- const type = detectMessageType2(content);
5772
- const senderPlatformId = currentMessage.sender || "system";
5773
- const senderName = currentMessage.sender || "\u7CFB\u7EDF\u6D88\u606F";
5774
- messages.push({
5775
- senderPlatformId,
5776
- senderAccountName: senderName,
5777
- timestamp: currentMessage.timestamp,
5778
- type,
5779
- content: content || null
5780
- });
5781
- if (currentMessage.sender) {
5782
- memberMap.set(senderPlatformId, {
5783
- platformId: senderPlatformId,
5784
- nickname: senderName
5785
- });
5786
- }
5787
- messagesProcessed++;
5788
- }
5789
- };
5761
+ onLog?.("info", `Starting WhatsApp TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5790
5762
  const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
5791
- const rl = readline3.createInterface({
5792
- input: fileStream,
5793
- crlfDelay: Infinity
5794
- });
5763
+ const lines = readline3.createInterface({ input: fileStream, crlfDelay: Infinity });
5795
5764
  fileStream.on("data", (chunk) => {
5796
5765
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
5797
5766
  });
5798
- for await (const line of rl) {
5799
- const cleanedLine = cleanLine(line);
5800
- const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
5801
- if (v1Match) {
5802
- saveCurrentMessage();
5803
- const restContent = v1Match[2];
5804
- const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5805
- if (senderMatch && !isSystemMessage(restContent)) {
5806
- currentMessage = {
5807
- timestamp: parseV1Timestamp(v1Match[1]),
5808
- sender: senderMatch[1].trim(),
5809
- contentLines: [senderMatch[2]]
5810
- };
5811
- } else {
5812
- currentMessage = {
5813
- timestamp: parseV1Timestamp(v1Match[1]),
5814
- sender: null,
5815
- contentLines: [restContent]
5816
- };
5817
- }
5818
- if (messagesProcessed % 500 === 0) {
5819
- onProgress?.(
5820
- createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
5821
- );
5822
- }
5823
- continue;
5824
- }
5825
- const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
5826
- if (v2Match) {
5827
- const timestamp = parseFlexibleV2Timestamp(v2Match[1]);
5828
- if (timestamp !== null) {
5829
- saveCurrentMessage();
5830
- const restContent = v2Match[2];
5831
- const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
5832
- if (senderMatch && !isSystemMessage(restContent)) {
5833
- currentMessage = {
5834
- timestamp,
5835
- sender: senderMatch[1].trim(),
5836
- contentLines: [senderMatch[2]]
5837
- };
5838
- } else {
5839
- currentMessage = {
5840
- timestamp,
5841
- sender: null,
5842
- contentLines: [restContent]
5843
- };
5844
- }
5845
- if (messagesProcessed % 500 === 0) {
5846
- onProgress?.(
5847
- createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
5848
- );
5849
- }
5850
- continue;
5851
- }
5852
- }
5853
- if (currentMessage && cleanedLine) {
5854
- currentMessage.contentLines.push(cleanedLine);
5855
- } else if (cleanedLine) {
5856
- skippedLines++;
5767
+ for await (const line of lines) {
5768
+ accumulator.pushLine(line);
5769
+ if (accumulator.messageCount >= lastReportedMessages + 500) {
5770
+ lastReportedMessages = accumulator.messageCount;
5771
+ onProgress?.(
5772
+ createProgress(
5773
+ "parsing",
5774
+ bytesRead,
5775
+ totalBytes,
5776
+ accumulator.messageCount,
5777
+ `Processed ${accumulator.messageCount} messages`
5778
+ )
5779
+ );
5857
5780
  }
5858
5781
  }
5859
- saveCurrentMessage();
5860
- const hasSystemMember = memberMap.has("system");
5861
- const realMemberCount = hasSystemMember ? memberMap.size - 1 : memberMap.size;
5862
- const chatType = realMemberCount > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
5863
- const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(filePath, fileChatName) ? inferPrivateChatName(memberMap, fileChatName) : fileChatName;
5864
- const meta = {
5865
- name: chatName,
5866
- platform: KNOWN_PLATFORMS.WHATSAPP,
5867
- type: chatType
5868
- };
5869
- yield { type: "meta", data: meta };
5870
- const members = Array.from(memberMap.values()).map((m) => ({
5871
- platformId: m.platformId,
5872
- accountName: m.nickname
5873
- }));
5874
- yield { type: "members", data: members };
5875
- for (let i = 0; i < messages.length; i += batchSize) {
5876
- const batch = messages.slice(i, i + batchSize);
5877
- yield { type: "messages", data: batch };
5782
+ const result = accumulator.finish();
5783
+ yield { type: "meta", data: result.meta };
5784
+ yield { type: "members", data: result.members };
5785
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5786
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5878
5787
  }
5879
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5788
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
5880
5789
  yield { type: "progress", data: doneProgress };
5881
5790
  onProgress?.(doneProgress);
5882
5791
  const typeCounts = /* @__PURE__ */ new Map();
5883
- for (const msg of messages) {
5884
- typeCounts.set(msg.type, (typeCounts.get(msg.type) || 0) + 1);
5792
+ for (const message of result.messages) {
5793
+ typeCounts.set(message.type, (typeCounts.get(message.type) ?? 0) + 1);
5885
5794
  }
5886
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
5887
5795
  onLog?.(
5888
5796
  "info",
5889
- `\u6D88\u606F\u7C7B\u578B\u7EDF\u8BA1: ${Array.from(typeCounts.entries()).map(([type, count]) => `${type}=${count}`).join(", ")}`
5797
+ `WhatsApp TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members, type ${result.meta.type}`
5890
5798
  );
5891
- if (skippedLines > 0) {
5892
- onLog?.("info", `\u8DF3\u8FC7 ${skippedLines} \u884C\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9`);
5893
- }
5799
+ onLog?.(
5800
+ "info",
5801
+ `Message types: ${Array.from(typeCounts.entries(), ([type, count]) => `${type}=${count}`).join(", ")}`
5802
+ );
5803
+ if (result.skippedLines > 0) onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines`);
5894
5804
  yield {
5895
5805
  type: "done",
5896
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5806
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
5897
5807
  };
5898
5808
  }
5899
5809
  var parser_9 = {
@@ -5903,32 +5813,110 @@ var parser_9 = {
5903
5813
  var module_10 = {
5904
5814
  feature: feature10,
5905
5815
  parser: parser_9
5906
- // TXT 格式不需要预处理器
5907
5816
  };
5908
5817
  var whatsapp_native_txt_default = module_10;
5909
5818
 
5910
5819
  // ../../packages/parser/src/formats/qq-native-txt.ts
5911
5820
  import * as fs14 from "fs";
5912
- import * as path10 from "path";
5913
5821
  import * as readline4 from "readline";
5914
- function extractNameFromFilePath6(filePath) {
5915
- const basename13 = path10.basename(filePath);
5916
- const name = basename13.replace(/\.txt$/i, "");
5917
- return name || "\u672A\u77E5\u7FA4\u804A";
5918
- }
5919
- var feature11 = {
5920
- id: "qq-native-txt",
5921
- name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
5922
- platform: KNOWN_PLATFORMS.QQ,
5923
- priority: 30,
5924
- extensions: [".txt"],
5925
- signatures: {
5926
- // 支持群聊导出和多人聊天(讨论组)导出
5927
- head: [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/]
5928
- }
5929
- };
5822
+
5823
+ // ../../packages/parser/src/browser/qq.ts
5824
+ var QQ_HEAD_SIGNATURES = [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/];
5930
5825
  var MESSAGE_HEADER_REGEX = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.+?)(?:\(([^)]+)\)|<([^>]+)>)?$/;
5931
5826
  var GROUP_NAME_REGEX = /^消息对象:(.+)$/;
5827
+ var QqTextAccumulator = class {
5828
+ fallbackName;
5829
+ memberMap = /* @__PURE__ */ new Map();
5830
+ lastValidNickname = /* @__PURE__ */ new Map();
5831
+ messages = [];
5832
+ groupName = "\u672A\u77E5\u7FA4\u804A";
5833
+ currentMessage = null;
5834
+ skippedLines = 0;
5835
+ constructor(fileName) {
5836
+ this.fallbackName = extractNameFromFileName2(fileName);
5837
+ }
5838
+ get messageCount() {
5839
+ return this.messages.length + (this.currentMessage ? 1 : 0);
5840
+ }
5841
+ pushLine(line) {
5842
+ const groupMatch = line.match(GROUP_NAME_REGEX);
5843
+ if (groupMatch) {
5844
+ this.groupName = groupMatch[1].trim();
5845
+ return;
5846
+ }
5847
+ const headerMatch = line.match(MESSAGE_HEADER_REGEX);
5848
+ if (headerMatch) {
5849
+ this.saveCurrentMessage();
5850
+ const platformId = headerMatch[3] || headerMatch[4] || cleanNickname(headerMatch[2].trim());
5851
+ let nickname = cleanNickname(headerMatch[2].trim());
5852
+ if (nickname === platformId && headerMatch[3]) {
5853
+ nickname = this.lastValidNickname.get(platformId) ?? nickname;
5854
+ } else if (headerMatch[3] || headerMatch[4]) {
5855
+ this.lastValidNickname.set(platformId, nickname);
5856
+ }
5857
+ this.currentMessage = {
5858
+ timestamp: parseLocalTime(headerMatch[1]),
5859
+ platformId,
5860
+ nickname,
5861
+ contentLines: []
5862
+ };
5863
+ return;
5864
+ }
5865
+ if (this.currentMessage) {
5866
+ if (line.startsWith("=====") || line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) return;
5867
+ this.currentMessage.contentLines.push(line);
5868
+ return;
5869
+ }
5870
+ const trimmed = line.trim();
5871
+ if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
5872
+ this.skippedLines += 1;
5873
+ }
5874
+ }
5875
+ finish() {
5876
+ this.saveCurrentMessage();
5877
+ return {
5878
+ meta: {
5879
+ name: this.groupName === "\u672A\u77E5\u7FA4\u804A" ? this.fallbackName : this.groupName,
5880
+ platform: KNOWN_PLATFORMS.QQ,
5881
+ type: "group" /* GROUP */
5882
+ },
5883
+ members: Array.from(this.memberMap.values(), (member) => ({
5884
+ platformId: member.platformId,
5885
+ accountName: member.nickname
5886
+ })),
5887
+ messages: this.messages,
5888
+ skippedLines: this.skippedLines
5889
+ };
5890
+ }
5891
+ saveCurrentMessage() {
5892
+ if (!this.currentMessage) return;
5893
+ const content = this.currentMessage.contentLines.join("\n").trim();
5894
+ this.messages.push({
5895
+ senderPlatformId: this.currentMessage.platformId,
5896
+ senderAccountName: this.currentMessage.nickname,
5897
+ timestamp: this.currentMessage.timestamp,
5898
+ type: detectMessageType3(content),
5899
+ content: content || null
5900
+ });
5901
+ this.memberMap.set(this.currentMessage.platformId, {
5902
+ platformId: this.currentMessage.platformId,
5903
+ nickname: this.currentMessage.nickname
5904
+ });
5905
+ this.currentMessage = null;
5906
+ }
5907
+ };
5908
+ function extractNameFromFileName2(fileName) {
5909
+ return getBasename2(fileName).replace(/\.txt$/i, "") || "\u672A\u77E5\u7FA4\u804A";
5910
+ }
5911
+ function getBasename2(fileName) {
5912
+ return fileName.split(/[\\/]/).pop() ?? fileName;
5913
+ }
5914
+ function cleanNickname(nickname) {
5915
+ return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
5916
+ }
5917
+ function parseLocalTime(timeText) {
5918
+ return Math.floor(new Date(timeText.replace(" ", "T")).getTime() / 1e3);
5919
+ }
5932
5920
  function detectMessageType3(content) {
5933
5921
  const trimmed = content.trim();
5934
5922
  if (trimmed === "[\u56FE\u7247]") return 1 /* IMAGE */;
@@ -5941,7 +5929,9 @@ function detectMessageType3(content) {
5941
5929
  if (trimmed === "[\u7EA2\u5305]" || trimmed.includes("\u53D1\u51FA\u4E86\u7EA2\u5305")) return 20 /* RED_PACKET */;
5942
5930
  if (trimmed === "[\u8F6C\u8D26]" || trimmed.includes("\u5411\u4F60\u8F6C\u8D26")) return 21 /* TRANSFER */;
5943
5931
  if (trimmed.includes("\u62CD\u4E86\u62CD") || trimmed === "[\u62CD\u4E00\u62CD]") return 22 /* POKE */;
5944
- if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) return 23 /* CALL */;
5932
+ if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) {
5933
+ return 23 /* CALL */;
5934
+ }
5945
5935
  if (trimmed === "[\u5206\u4EAB]" || trimmed === "[\u97F3\u4E50]" || trimmed === "[\u5C0F\u7A0B\u5E8F]") return 24 /* SHARE */;
5946
5936
  if (trimmed.startsWith("[\u56DE\u590D]")) return 25 /* REPLY */;
5947
5937
  if (trimmed === "[\u8F6C\u53D1]" || trimmed === "[\u804A\u5929\u8BB0\u5F55]") return 26 /* FORWARD */;
@@ -5952,150 +5942,64 @@ function detectMessageType3(content) {
5952
5942
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) return 99 /* OTHER */;
5953
5943
  return 0 /* TEXT */;
5954
5944
  }
5955
- function parseLocalTime(timeStr) {
5956
- const date = new Date(timeStr.replace(" ", "T"));
5957
- return Math.floor(date.getTime() / 1e3);
5958
- }
5959
- function cleanNickname(nickname) {
5960
- return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
5961
- }
5962
- var lastValidNickname = /* @__PURE__ */ new Map();
5963
- async function* parseTxt(options2) {
5945
+
5946
+ // ../../packages/parser/src/formats/qq-native-txt.ts
5947
+ var feature11 = {
5948
+ id: "qq-native-txt",
5949
+ name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
5950
+ platform: KNOWN_PLATFORMS.QQ,
5951
+ priority: 30,
5952
+ extensions: [".txt"],
5953
+ signatures: { head: QQ_HEAD_SIGNATURES }
5954
+ };
5955
+ async function* parseQq(options2) {
5964
5956
  const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
5965
5957
  const totalBytes = getFileSize(filePath);
5966
5958
  let bytesRead = 0;
5967
- let messagesProcessed = 0;
5968
- let skippedLines = 0;
5969
5959
  const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
5970
5960
  yield { type: "progress", data: initialProgress };
5971
5961
  onProgress?.(initialProgress);
5972
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 QQ TXT \u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
5973
- let groupName = "\u672A\u77E5\u7FA4\u804A";
5974
- const memberMap = /* @__PURE__ */ new Map();
5975
- const messages = [];
5976
- let currentMessage = null;
5977
- const saveCurrentMessage = () => {
5978
- if (currentMessage) {
5979
- const content = currentMessage.contentLines.join("\n").trim();
5980
- const type = detectMessageType3(content);
5981
- messages.push({
5982
- senderPlatformId: currentMessage.platformId,
5983
- senderAccountName: currentMessage.nickname,
5984
- // QQ TXT 格式只有一个昵称,作为账号名称追踪历史
5985
- // 不设置 senderGroupNickname,避免同一昵称被重复追踪
5986
- timestamp: currentMessage.timestamp,
5987
- type,
5988
- content: content || null
5989
- });
5990
- memberMap.set(currentMessage.platformId, {
5991
- platformId: currentMessage.platformId,
5992
- nickname: currentMessage.nickname
5993
- });
5994
- messagesProcessed++;
5995
- }
5996
- };
5962
+ onLog?.("info", `Starting QQ TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
5963
+ const accumulator = new QqTextAccumulator(filePath);
5997
5964
  const fileStream = fs14.createReadStream(filePath, { encoding: "utf-8" });
5998
- const rl = readline4.createInterface({
5999
- input: fileStream,
6000
- crlfDelay: Infinity
6001
- });
5965
+ const lines = readline4.createInterface({ input: fileStream, crlfDelay: Infinity });
6002
5966
  fileStream.on("data", (chunk) => {
6003
5967
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6004
5968
  });
6005
- for await (const line of rl) {
6006
- const groupMatch = line.match(GROUP_NAME_REGEX);
6007
- if (groupMatch) {
6008
- groupName = groupMatch[1].trim();
6009
- continue;
6010
- }
6011
- const headerMatch = line.match(MESSAGE_HEADER_REGEX);
6012
- if (headerMatch) {
6013
- saveCurrentMessage();
6014
- const timeStr = headerMatch[1];
6015
- const rawNickname = headerMatch[2].trim();
6016
- let nickname = cleanNickname(rawNickname);
6017
- const platformId = headerMatch[3] || headerMatch[4] || nickname;
6018
- if (nickname === platformId && headerMatch[3]) {
6019
- const previousNickname = lastValidNickname.get(platformId);
6020
- if (previousNickname) {
6021
- nickname = previousNickname;
6022
- }
6023
- } else if (headerMatch[3] || headerMatch[4]) {
6024
- lastValidNickname.set(platformId, nickname);
6025
- }
6026
- currentMessage = {
6027
- timestamp: parseLocalTime(timeStr),
6028
- platformId,
6029
- nickname,
6030
- contentLines: []
6031
- };
6032
- if (messagesProcessed % 1e3 === 0) {
6033
- const progress = createProgress(
6034
- "parsing",
6035
- bytesRead,
6036
- totalBytes,
6037
- messagesProcessed,
6038
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
6039
- );
6040
- onProgress?.(progress);
6041
- }
6042
- continue;
6043
- }
6044
- if (currentMessage) {
6045
- if (line.startsWith("=====")) continue;
6046
- if (line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) continue;
6047
- currentMessage.contentLines.push(line);
6048
- } else {
6049
- const trimmed = line.trim();
6050
- if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
6051
- skippedLines++;
6052
- }
6053
- }
6054
- }
6055
- saveCurrentMessage();
6056
- const meta = {
6057
- name: groupName === "\u672A\u77E5\u7FA4\u804A" ? extractNameFromFilePath6(filePath) : groupName,
6058
- platform: KNOWN_PLATFORMS.QQ,
6059
- type: "group" /* GROUP */
6060
- };
6061
- yield { type: "meta", data: meta };
6062
- const members = Array.from(memberMap.values()).map((m) => ({
6063
- platformId: m.platformId,
6064
- accountName: m.nickname
6065
- // QQ TXT 格式只有昵称,作为账号名称
6066
- // 不设置 groupNickname,避免同一昵称被重复追踪
6067
- }));
6068
- yield { type: "members", data: members };
6069
- for (let i = 0; i < messages.length; i += batchSize) {
6070
- const batch = messages.slice(i, i + batchSize);
6071
- yield { type: "messages", data: batch };
6072
- }
6073
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
5969
+ let lastReportedCount = -1;
5970
+ for await (const line of lines) {
5971
+ accumulator.pushLine(line);
5972
+ const messageCount = accumulator.messageCount;
5973
+ if (messageCount > 0 && messageCount % 1e3 === 0 && messageCount !== lastReportedCount) {
5974
+ lastReportedCount = messageCount;
5975
+ onProgress?.(createProgress("parsing", bytesRead, totalBytes, messageCount, `Processed ${messageCount} messages`));
5976
+ }
5977
+ }
5978
+ const result = accumulator.finish();
5979
+ yield { type: "meta", data: result.meta };
5980
+ yield { type: "members", data: result.members };
5981
+ for (let index = 0; index < result.messages.length; index += batchSize) {
5982
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
5983
+ }
5984
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6074
5985
  yield { type: "progress", data: doneProgress };
6075
5986
  onProgress?.(doneProgress);
6076
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458`);
6077
- if (skippedLines > 0) {
6078
- onLog?.("info", `\u8DF3\u8FC7 ${skippedLines} \u884C\u65E0\u6CD5\u89E3\u6790\u7684\u5185\u5BB9`);
5987
+ onLog?.("info", `QQ TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
5988
+ if (result.skippedLines > 0) {
5989
+ onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines while parsing QQ TXT`);
6079
5990
  }
6080
5991
  yield {
6081
5992
  type: "done",
6082
- data: { messageCount: messagesProcessed, memberCount: memberMap.size }
5993
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6083
5994
  };
6084
5995
  }
6085
- var parser_10 = {
6086
- feature: feature11,
6087
- parse: parseTxt
6088
- };
6089
- var module_11 = {
6090
- feature: feature11,
6091
- parser: parser_10
6092
- // TXT 格式不需要预处理器
6093
- };
5996
+ var parser_10 = { feature: feature11, parse: parseQq };
5997
+ var module_11 = { feature: feature11, parser: parser_10 };
6094
5998
  var qq_native_txt_default = module_11;
6095
5999
 
6096
6000
  // ../../packages/parser/src/formats/instagram-native.ts
6097
6001
  import * as fs15 from "fs";
6098
- import * as path11 from "path";
6002
+ import * as path9 from "path";
6099
6003
  var feature12 = {
6100
6004
  id: "instagram-native",
6101
6005
  name: "Instagram \u5B98\u65B9\u5BFC\u51FA",
@@ -6120,9 +6024,9 @@ function decodeInstagramText(text) {
6120
6024
  return text;
6121
6025
  }
6122
6026
  }
6123
- function extractNameFromFilePath7(filePath) {
6124
- const basename13 = path11.basename(filePath);
6125
- return basename13.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6027
+ function extractNameFromFilePath5(filePath) {
6028
+ const basename10 = path9.basename(filePath);
6029
+ return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
6126
6030
  }
6127
6031
  function isSystemMessage2(content) {
6128
6032
  const systemPatterns = [
@@ -6208,7 +6112,7 @@ async function* parseInstagram(options2) {
6208
6112
  ownerId = createMsg ? decodeInstagramText(createMsg.sender_name) : void 0;
6209
6113
  }
6210
6114
  const meta = {
6211
- name: decodeInstagramText(data.title) || extractNameFromFilePath7(filePath),
6115
+ name: decodeInstagramText(data.title) || extractNameFromFilePath5(filePath),
6212
6116
  platform: KNOWN_PLATFORMS.INSTAGRAM,
6213
6117
  type: chatType,
6214
6118
  ownerId
@@ -6281,16 +6185,16 @@ var module_12 = {
6281
6185
  var instagram_native_default = module_12;
6282
6186
 
6283
6187
  // ../../packages/parser/src/formats/google-chat-takeout.ts
6284
- var import_stream_chain8 = __toESM(require_stream_chain(), 1);
6188
+ var import_stream_chain7 = __toESM(require_stream_chain(), 1);
6285
6189
  var import_stream_json8 = __toESM(require_stream_json(), 1);
6286
6190
  var import_Pick7 = __toESM(require_Pick(), 1);
6287
- var import_StreamValues8 = __toESM(require_StreamValues(), 1);
6191
+ var import_StreamValues7 = __toESM(require_StreamValues(), 1);
6288
6192
  import * as fs16 from "fs";
6289
- import * as path12 from "path";
6290
- var { chain: chain8 } = import_stream_chain8.default;
6193
+ import * as path10 from "path";
6194
+ var { chain: chain7 } = import_stream_chain7.default;
6291
6195
  var { parser: parser9 } = import_stream_json8.default;
6292
6196
  var { pick: pick7 } = import_Pick7.default;
6293
- var { streamValues: streamValues8 } = import_StreamValues8.default;
6197
+ var { streamValues: streamValues7 } = import_StreamValues7.default;
6294
6198
  var ENGLISH_MONTHS = /* @__PURE__ */ new Map([
6295
6199
  ["january", 0],
6296
6200
  ["february", 1],
@@ -6372,9 +6276,9 @@ function readJson(filePath) {
6372
6276
  return JSON.parse(fs16.readFileSync(filePath, "utf8"));
6373
6277
  }
6374
6278
  function resolveManifestFile(manifestDir, relativePath) {
6375
- const resolved = path12.resolve(manifestDir, relativePath);
6376
- const relative2 = path12.relative(manifestDir, resolved);
6377
- if (relative2.startsWith("..") || path12.isAbsolute(relative2)) {
6279
+ const resolved = path10.resolve(manifestDir, relativePath);
6280
+ const relative2 = path10.relative(manifestDir, resolved);
6281
+ if (relative2.startsWith("..") || path10.isAbsolute(relative2)) {
6378
6282
  throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`);
6379
6283
  }
6380
6284
  return resolved;
@@ -6387,7 +6291,7 @@ function readManifest2(filePath) {
6387
6291
  if (manifest.chatType !== "private" && manifest.chatType !== "group") {
6388
6292
  throw new Error("Invalid Google Chat chat type");
6389
6293
  }
6390
- const manifestDir = path12.dirname(filePath);
6294
+ const manifestDir = path10.dirname(filePath);
6391
6295
  return {
6392
6296
  manifest,
6393
6297
  userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),
@@ -6471,7 +6375,7 @@ async function* parseGoogleChat(options2) {
6471
6375
  readStream.on("data", (chunk) => {
6472
6376
  bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
6473
6377
  });
6474
- const pipeline2 = chain8([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues8()]);
6378
+ const pipeline2 = chain7([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues7()]);
6475
6379
  const messageBatch = [];
6476
6380
  try {
6477
6381
  for await (const item of pipeline2) {
@@ -6519,418 +6423,270 @@ var google_chat_takeout_default = module_13;
6519
6423
 
6520
6424
  // ../../packages/parser/src/formats/line-native-txt.ts
6521
6425
  import * as fs17 from "fs";
6522
- import * as path13 from "path";
6523
- var feature14 = {
6524
- id: "line-native-txt",
6525
- name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
6526
- platform: KNOWN_PLATFORMS.LINE,
6527
- priority: 35,
6528
- extensions: [".txt"],
6529
- signatures: {
6530
- head: [
6531
- // 头部标识(多语言)
6532
- /^\[LINE\] /m,
6533
- /^(?:\[LINE\] )?Chat history (?:with|in) /m,
6534
- // Tab 分隔的消息格式(支持多种时间格式)
6535
- /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
6536
- // 空格分隔的消息格式(部分 LINE 导出)
6537
- /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
6538
- // LINE 独有的日期行格式:YYYY.MM.DD DayOfWeek(英文星期全称)
6539
- /^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
6540
- // LINE 日文/中文日期行格式:YYYY/M/D(曜日)
6541
- /^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
6542
- // LINE 中文日期行格式:YYYY/M/D周X
6543
- /^\d{4}\/\d{1,2}\/\d{1,2}周/m
6544
- ],
6545
- // 文件名特征:[LINE] 出现在文件名中
6546
- filename: [/\[LINE\]/i]
6547
- }
6548
- };
6549
- function extractNameFromFilePath8(filePath) {
6550
- const basename13 = path13.basename(filePath, ".txt");
6551
- const name = basename13.replace(/^\[LINE\]\s*/i, "").trim();
6552
- return name || "\u672A\u77E5\u804A\u5929";
6553
- }
6554
- function extractNameFromHeader(header) {
6555
- const enPrivateMatch = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
6556
- if (enPrivateMatch) return { name: enPrivateMatch[1].trim(), isGroup: false };
6557
- const enGroupMatch = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
6558
- if (enGroupMatch) return { name: enGroupMatch[1].trim(), isGroup: true };
6559
- const jaPrivateMatch = header.match(/^\[LINE\] (.+)とのトーク履歴/);
6560
- if (jaPrivateMatch) return { name: jaPrivateMatch[1].trim(), isGroup: false };
6561
- const jaGroupMatch = header.match(/^\[LINE\] (.+)のトーク履歴/);
6562
- if (jaGroupMatch) return { name: jaGroupMatch[1].trim(), isGroup: true };
6563
- const zhCnPrivateMatch = header.match(/^\[LINE\] 与(.+)的聊天记录/);
6564
- if (zhCnPrivateMatch) return { name: zhCnPrivateMatch[1].trim(), isGroup: false };
6565
- const zhCnGroupMatch = header.match(/^\[LINE\] (.+)的聊天记录/);
6566
- if (zhCnGroupMatch) return { name: zhCnGroupMatch[1].trim(), isGroup: true };
6567
- const zhTwPrivateMatch = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
6568
- if (zhTwPrivateMatch) return { name: zhTwPrivateMatch[1].trim(), isGroup: false };
6569
- const zhTwGroupMatch = header.match(/^\[LINE\] (.+)的聊天記錄/);
6570
- if (zhTwGroupMatch) return { name: zhTwGroupMatch[1].trim(), isGroup: true };
6571
- return null;
6572
- }
6426
+
6427
+ // ../../packages/parser/src/browser/line.ts
6428
+ var LINE_HEAD_SIGNATURES = [
6429
+ /^\[LINE\] /m,
6430
+ /^(?:\[LINE\] )?Chat history (?:with|in) /m,
6431
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
6432
+ /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
6433
+ /^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
6434
+ /^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
6435
+ /^\d{4}\/\d{1,2}\/\d{1,2}周/m
6436
+ ];
6437
+ var LINE_FILENAME_SIGNATURES = [/\[LINE\]/i];
6573
6438
  var DATE_PATTERNS = [
6574
- // 2025.12.10 Wednesday
6575
6439
  /^(\d{4})\.(\d{2})\.(\d{2})\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)?/,
6576
- // 2026/1/30周五 or 2026/1/30(金)
6577
6440
  /^(\d{4})\/(\d{1,2})\/(\d{1,2})/,
6578
- // Fri, 1/30/2026
6579
6441
  /^[A-Za-z]+,\s*(\d{1,2})\/(\d{1,2})\/(\d{4})/
6580
6442
  ];
6581
- function parseDateLine(line) {
6582
- for (const pattern of DATE_PATTERNS) {
6583
- const match = line.match(pattern);
6584
- if (match) {
6585
- if (pattern === DATE_PATTERNS[0] || pattern === DATE_PATTERNS[1]) {
6586
- const year = parseInt(match[1]);
6587
- const month = parseInt(match[2]) - 1;
6588
- const day = parseInt(match[3]);
6589
- return new Date(year, month, day);
6590
- } else if (pattern === DATE_PATTERNS[2]) {
6591
- const month = parseInt(match[1]) - 1;
6592
- const day = parseInt(match[2]);
6593
- const year = parseInt(match[3]);
6594
- return new Date(year, month, day);
6595
- }
6596
- }
6597
- }
6598
- return null;
6599
- }
6600
- var PRIVATE_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
6601
- var GROUP_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
6602
- var SYSTEM_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
6443
+ var TAB_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
6444
+ var SPACE_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
6445
+ var SYSTEM_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
6603
6446
  var SPECIAL_MESSAGE_TYPES = {
6604
- // 图片 / Photo
6605
6447
  "[Photo]": 1 /* IMAGE */,
6606
- // EN
6607
6448
  "[\u7167\u7247]": 1 /* IMAGE */,
6608
- // ZH-CN / ZH-TW
6609
6449
  "[\u5199\u771F]": 1 /* IMAGE */,
6610
- // JA
6611
6450
  Photos: 1 /* IMAGE */,
6612
- // EN (fallback)
6613
- // 语音 / Voice
6614
6451
  "[Voice message]": 2 /* VOICE */,
6615
- // EN
6616
6452
  "[\u8BED\u97F3\u4FE1\u606F]": 2 /* VOICE */,
6617
- // ZH-CN
6618
6453
  "[\u8A9E\u97F3\u8A0A\u606F]": 2 /* VOICE */,
6619
- // ZH-TW
6620
6454
  "[\u30DC\u30A4\u30B9\u30E1\u30C3\u30BB\u30FC\u30B8]": 2 /* VOICE */,
6621
- // JA
6622
6455
  Audio: 2 /* VOICE */,
6623
- // EN (fallback)
6624
- // 视频 / Video
6625
6456
  "[Video]": 3 /* VIDEO */,
6626
- // EN
6627
6457
  "[\u89C6\u9891]": 3 /* VIDEO */,
6628
- // ZH-CN
6629
6458
  "[\u5F71\u7247]": 3 /* VIDEO */,
6630
- // ZH-TW
6631
6459
  "[\u52D5\u753B]": 3 /* VIDEO */,
6632
- // JA
6633
6460
  Videos: 3 /* VIDEO */,
6634
- // EN (fallback)
6635
- // 文件 / File
6636
6461
  "[File]": 4 /* FILE */,
6637
- // EN
6638
6462
  "[\u6587\u4EF6]": 4 /* FILE */,
6639
- // ZH-CN
6640
6463
  "[\u6A94\u6848]": 4 /* FILE */,
6641
- // ZH-TW
6642
6464
  "[\u30D5\u30A1\u30A4\u30EB]": 4 /* FILE */,
6643
- // JA
6644
- // 贴纸 / Sticker
6645
6465
  "[Sticker]": 5 /* EMOJI */,
6646
- // EN
6647
6466
  "[\u8D34\u56FE]": 5 /* EMOJI */,
6648
- // ZH-CN
6649
6467
  "[\u8CBC\u5716]": 5 /* EMOJI */,
6650
- // ZH-TW
6651
6468
  "[\u30B9\u30BF\u30F3\u30D7]": 5 /* EMOJI */,
6652
- // JA
6653
6469
  Stickers: 5 /* EMOJI */,
6654
- // EN (fallback)
6655
- // 位置 / Location
6656
6470
  "[Location]": 8 /* LOCATION */,
6657
- // EN
6658
6471
  "[\u4F4D\u7F6E]": 8 /* LOCATION */,
6659
- // ZH-CN / ZH-TW
6660
6472
  "[\u4F4D\u7F6E\u60C5\u5831]": 8 /* LOCATION */,
6661
- // JA
6662
- // 记事本 / Notes
6663
6473
  "[Notes]": 0 /* TEXT */,
6664
- // EN
6665
6474
  "[\u8BB0\u4E8B\u672C]": 0 /* TEXT */,
6666
- // ZH-CN
6667
6475
  "[\u8A18\u4E8B\u672C]": 0 /* TEXT */,
6668
- // ZH-TW
6669
6476
  "[\u30CE\u30FC\u30C8]": 0 /* TEXT */
6670
- // JA
6671
6477
  };
6672
- function detectMessageType6(content) {
6673
- for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
6674
- if (content === pattern || content.startsWith(pattern)) {
6675
- return type;
6676
- }
6677
- }
6678
- if (content.startsWith("[null]") && content.includes("maps.google.com")) {
6679
- return 8 /* LOCATION */;
6680
- }
6681
- if (
6682
- // --- 加入群组 / Join group ---
6683
- content.includes(" joined the group") || // EN
6684
- content.includes("\u5DF2\u52A0\u5165\u8BE5\u7FA4") || // ZH-CN
6685
- content.includes("\u5DF2\u52A0\u5165\u7FA4\u7D44") || // ZH-TW
6686
- content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u306B\u53C2\u52A0\u3057\u307E\u3057\u305F") || // JA
6687
- // --- 拉人进群 / Added to group ---
6688
- content.includes(" added ") || // EN
6689
- content.includes(" to the group") || // EN
6690
- content.includes("\u5DF2\u5C06") || // ZH-CN
6691
- content.includes("\u6DFB\u52A0\u81F3\u7FA4") || // ZH-CN
6692
- content.includes("\u6DFB\u52A0\u5230\u7FA4") || // ZH-CN (另一格式)
6693
- content.includes("\u5DF2\u65B0\u589E") || // ZH-TW
6694
- content.includes("\u81F3\u7FA4\u7D44") || // ZH-TW
6695
- content.includes("\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F") || // JA
6696
- // --- 退出群组 / Left group ---
6697
- content.includes(" left the group") || // EN
6698
- content.includes("\u5DF2\u9000\u7FA4") || // ZH-CN
6699
- content.includes("\u5DF2\u96E2\u958B\u7FA4\u7D44") || // ZH-TW
6700
- content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u3092\u9000\u4F1A\u3057\u307E\u3057\u305F") || // JA
6701
- // --- 设定公告 / Announcement ---
6702
- content.includes("made an announcement") || // EN
6703
- content.includes("\u53D1\u5E03\u4E86\u901A\u544A") || // ZH-CN
6704
- content.includes("\u5DF2\u8A2D\u5B9A\u516C\u544A") || // ZH-TW
6705
- content.includes("\u304C\u30A2\u30CA\u30A6\u30F3\u30B9\u3057\u307E\u3057\u305F") || // JA
6706
- // --- 收回讯息 / Unsent message ---
6707
- content.includes("unsent a message") || // EN
6708
- content === "Message unsent." || // EN
6709
- content.includes("\u64A4\u56DE\u4E86\u4E00\u6761\u6D88\u606F") || // ZH-CN
6710
- content.includes("\u5DF2\u6536\u56DE\u8A0A\u606F") || // ZH-TW
6711
- content.includes("\u9001\u4FE1\u3092\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F") || // JA
6712
- // --- 其他 / Others ---
6713
- content.startsWith("Auto-reply")
6714
- ) {
6715
- return 80 /* SYSTEM */;
6716
- }
6717
- if (content.match(/^https?:\/\//)) {
6718
- return 7 /* LINK */;
6719
- }
6720
- return 0 /* TEXT */;
6721
- }
6722
- async function* parseLINE(options2) {
6723
- const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
6724
- const totalBytes = getFileSize(filePath);
6725
- let messagesProcessed = 0;
6726
- const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
6727
- yield { type: "progress", data: initialProgress };
6728
- onProgress?.(initialProgress);
6729
- onLog?.("info", `\u5F00\u59CB\u89E3\u6790 LINE \u5BFC\u51FA\u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
6730
- const content = fs17.readFileSync(filePath, "utf-8");
6478
+ async function parseLineText(content, fileName, options2 = {}) {
6731
6479
  const lines = content.split("\n").map((line) => line.replace(/\r$/, ""));
6732
- let currentDate = null;
6733
- let chatName = extractNameFromFilePath8(filePath);
6734
- let isPrivateChat = false;
6735
- let useTabSeparator = false;
6736
6480
  const memberMap = /* @__PURE__ */ new Map();
6737
6481
  const messages = [];
6482
+ const header = lines.length > 0 ? extractNameFromHeader(lines[0].trim()) : null;
6483
+ const chatName = header?.name ?? extractNameFromFileName3(fileName);
6484
+ let currentDate = null;
6738
6485
  let lastMessage = null;
6739
- let lineIndex = 0;
6740
- if (lines.length > 0) {
6741
- const firstLine = lines[0].trim();
6742
- onLog?.("debug", `LINE \u7B2C\u4E00\u884C: "${firstLine}"`);
6743
- const headerResult = extractNameFromHeader(firstLine);
6744
- if (headerResult) {
6745
- chatName = headerResult.name;
6746
- isPrivateChat = !headerResult.isGroup;
6747
- useTabSeparator = true;
6748
- lineIndex = 3;
6749
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u5934\u90E8\uFF0C\u540D\u79F0: ${headerResult.name}, \u7FA4\u804A: ${headerResult.isGroup}`);
6750
- }
6751
- }
6752
- if (!isPrivateChat && lines.length > 0) {
6486
+ let quotedMultiline = false;
6487
+ let lineIndex = header ? 3 : 0;
6488
+ let useTabSeparator = Boolean(header);
6489
+ const yieldEvery = Math.max(1, options2.yieldEvery ?? 1e3);
6490
+ if (!header) {
6753
6491
  for (const line of lines) {
6754
- if (PRIVATE_MSG_PATTERN.test(line)) {
6492
+ if (TAB_MESSAGE_PATTERN.test(line)) {
6755
6493
  useTabSeparator = true;
6756
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230 Tab \u5206\u9694\u683C\u5F0F`);
6757
6494
  break;
6758
6495
  }
6759
- if (GROUP_MSG_PATTERN.test(line)) {
6496
+ if (SPACE_MESSAGE_PATTERN.test(line)) {
6760
6497
  useTabSeparator = false;
6761
- onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u7A7A\u683C\u5206\u9694\u683C\u5F0F`);
6762
6498
  break;
6763
6499
  }
6764
6500
  }
6765
6501
  }
6766
- onLog?.("debug", `LINE \u89E3\u6790\u914D\u7F6E: useTabSeparator=${useTabSeparator}, lineIndex=${lineIndex}`);
6767
- let debugLogCount = 0;
6768
- for (let i = lineIndex; i < lines.length; i++) {
6769
- const line = lines[i];
6770
- const dateResult = parseDateLine(line);
6771
- if (dateResult) {
6772
- currentDate = dateResult;
6773
- if (debugLogCount < 5) {
6774
- onLog?.("debug", `LINE \u65E5\u671F\u884C[${i}]: ${line} -> ${dateResult.toISOString()}`);
6775
- }
6776
- continue;
6777
- }
6778
- const msgPattern = useTabSeparator ? PRIVATE_MSG_PATTERN : GROUP_MSG_PATTERN;
6779
- const msgMatch = line.match(msgPattern);
6780
- if (debugLogCount < 5 && line.trim()) {
6781
- onLog?.("debug", `LINE \u884C[${i}]: "${line.substring(0, 50)}..." match=${!!msgMatch}`);
6782
- debugLogCount++;
6783
- }
6784
- if (msgMatch) {
6785
- const [, timeStr, sender, contentRaw] = msgMatch;
6786
- let content2 = contentRaw.trim();
6787
- let isQuotedMultiline = false;
6788
- if (content2.startsWith('"')) {
6789
- content2 = content2.substring(1);
6790
- isQuotedMultiline = !content2.endsWith('"');
6791
- if (content2.endsWith('"')) {
6792
- content2 = content2.substring(0, content2.length - 1);
6793
- }
6794
- }
6795
- let hours = 0;
6796
- let minutes = 0;
6797
- const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1];
6798
- const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, "");
6799
- const partsMatch = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6800
- if (partsMatch) {
6801
- hours = parseInt(partsMatch[1]);
6802
- minutes = parseInt(partsMatch[2]);
6803
- const suffix = partsMatch[3]?.toLowerCase();
6804
- if (suffix === "pm" && hours < 12) hours += 12;
6805
- if (suffix === "am" && hours === 12) hours = 0;
6806
- if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6807
- if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6808
- } else {
6809
- const parts = timeStr.split(":").map(Number);
6810
- hours = parts[0];
6811
- minutes = parts[1];
6812
- }
6813
- let timestamp;
6814
- if (currentDate) {
6815
- const msgDate = new Date(currentDate);
6816
- msgDate.setHours(hours, minutes, 0, 0);
6817
- timestamp = Math.floor(msgDate.getTime() / 1e3);
6818
- } else {
6819
- const now = /* @__PURE__ */ new Date();
6820
- now.setHours(hours, minutes, 0, 0);
6821
- timestamp = Math.floor(now.getTime() / 1e3);
6822
- }
6823
- const msgType = detectMessageType6(content2);
6824
- if (!memberMap.has(sender)) {
6825
- memberMap.set(sender, {
6826
- platformId: sender,
6827
- accountName: sender
6828
- });
6829
- }
6830
- lastMessage = {
6831
- senderPlatformId: sender,
6832
- senderAccountName: sender,
6833
- timestamp,
6834
- type: msgType,
6835
- content: content2 || null,
6836
- _isQuotedMultiline: isQuotedMultiline
6837
- // 临时标记,用于追加多行内容时处理结尾引号
6838
- };
6839
- messages.push(lastMessage);
6840
- messagesProcessed++;
6841
- if (messagesProcessed % 1e3 === 0) {
6842
- const progress = createProgress(
6843
- "parsing",
6844
- i,
6845
- lines.length,
6846
- messagesProcessed,
6847
- `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
6848
- );
6849
- onProgress?.(progress);
6850
- }
6502
+ for (; lineIndex < lines.length; lineIndex += 1) {
6503
+ options2.checkCancelled?.();
6504
+ const line = lines[lineIndex];
6505
+ const date = parseDateLine(line);
6506
+ if (date) {
6507
+ currentDate = date;
6851
6508
  } else {
6852
- const systemMatch = line.match(SYSTEM_MSG_PATTERN);
6853
- if (systemMatch) {
6854
- const [, timeStr, contentRaw] = systemMatch;
6855
- const content2 = contentRaw.trim();
6856
- let hours = 0;
6857
- let minutes = 0;
6858
- const prefix = timeStr.match(/^(上午|下午|午前|午後)/)?.[1];
6859
- const cleanTime = timeStr.replace(/^(上午|下午|午前|午後)/, "");
6860
- const partsMatch = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6861
- if (partsMatch) {
6862
- hours = parseInt(partsMatch[1]);
6863
- minutes = parseInt(partsMatch[2]);
6864
- const suffix = partsMatch[3]?.toLowerCase();
6865
- if (suffix === "pm" && hours < 12) hours += 12;
6866
- if (suffix === "am" && hours === 12) hours = 0;
6867
- if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6868
- if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6869
- } else {
6870
- const parts = timeStr.split(":").map(Number);
6871
- hours = parts[0];
6872
- minutes = parts[1];
6509
+ const messagePattern = useTabSeparator ? TAB_MESSAGE_PATTERN : SPACE_MESSAGE_PATTERN;
6510
+ const messageMatch = line.match(messagePattern);
6511
+ if (messageMatch) {
6512
+ const [, timeText, sender, rawContent] = messageMatch;
6513
+ let messageContent = rawContent.trim();
6514
+ quotedMultiline = false;
6515
+ if (messageContent.startsWith('"')) {
6516
+ messageContent = messageContent.slice(1);
6517
+ quotedMultiline = !messageContent.endsWith('"');
6518
+ if (!quotedMultiline) messageContent = messageContent.slice(0, -1);
6873
6519
  }
6874
- let timestamp;
6875
- if (currentDate) {
6876
- const msgDate = new Date(currentDate);
6877
- msgDate.setHours(hours, minutes, 0, 0);
6878
- timestamp = Math.floor(msgDate.getTime() / 1e3);
6879
- } else {
6880
- const now = /* @__PURE__ */ new Date();
6881
- now.setHours(hours, minutes, 0, 0);
6882
- timestamp = Math.floor(now.getTime() / 1e3);
6520
+ if (!memberMap.has(sender)) {
6521
+ memberMap.set(sender, { platformId: sender, accountName: sender });
6883
6522
  }
6884
6523
  lastMessage = {
6885
- senderPlatformId: "system",
6886
- senderAccountName: "\u7CFB\u7D71",
6887
- timestamp,
6888
- type: 80 /* SYSTEM */,
6889
- content: content2 || null
6524
+ senderPlatformId: sender,
6525
+ senderAccountName: sender,
6526
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6527
+ type: detectMessageType6(messageContent),
6528
+ content: messageContent || null
6890
6529
  };
6891
6530
  messages.push(lastMessage);
6892
- messagesProcessed++;
6893
- } else if (line.trim() && lastMessage) {
6894
- let appendLine = line;
6895
- const quotedMsg = lastMessage;
6896
- if (quotedMsg._isQuotedMultiline && appendLine.endsWith('"')) {
6897
- appendLine = appendLine.substring(0, appendLine.length - 1);
6898
- delete quotedMsg._isQuotedMultiline;
6899
- }
6900
- if (lastMessage.content) {
6901
- lastMessage.content += "\n" + appendLine;
6902
- } else {
6903
- lastMessage.content = appendLine;
6531
+ } else {
6532
+ const systemMatch = line.match(SYSTEM_MESSAGE_PATTERN);
6533
+ if (systemMatch) {
6534
+ const [, timeText, rawContent] = systemMatch;
6535
+ const messageContent = rawContent.trim();
6536
+ quotedMultiline = false;
6537
+ lastMessage = {
6538
+ senderPlatformId: "system",
6539
+ senderAccountName: "\u7CFB\u7D71",
6540
+ timestamp: buildTimestamp(currentDate, timeText, options2.now),
6541
+ type: 80 /* SYSTEM */,
6542
+ content: messageContent || null
6543
+ };
6544
+ messages.push(lastMessage);
6545
+ } else if (line.trim() && lastMessage) {
6546
+ let continuation = line;
6547
+ if (quotedMultiline && continuation.endsWith('"')) {
6548
+ continuation = continuation.slice(0, -1);
6549
+ quotedMultiline = false;
6550
+ }
6551
+ lastMessage.content = lastMessage.content ? `${lastMessage.content}
6552
+ ${continuation}` : continuation;
6904
6553
  }
6905
6554
  }
6906
6555
  }
6556
+ if (lineIndex > 0 && lineIndex % yieldEvery === 0) {
6557
+ options2.onProgress?.({ progress: lineIndex / lines.length, messagesProcessed: messages.length });
6558
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
6559
+ options2.checkCancelled?.();
6560
+ }
6907
6561
  }
6562
+ options2.checkCancelled?.();
6908
6563
  const memberCount = memberMap.size;
6909
- const chatType = memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
6910
- const meta = {
6911
- name: chatName,
6912
- platform: KNOWN_PLATFORMS.LINE,
6913
- type: chatType
6564
+ const chatType = header ? header.isGroup ? "group" /* GROUP */ : "private" /* PRIVATE */ : memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
6565
+ const result = {
6566
+ meta: { name: chatName, platform: KNOWN_PLATFORMS.LINE, type: chatType },
6567
+ members: Array.from(memberMap.values()),
6568
+ messages
6914
6569
  };
6915
- yield { type: "meta", data: meta };
6916
- const members = Array.from(memberMap.values());
6917
- yield { type: "members", data: members };
6918
- for (let i = 0; i < messages.length; i += batchSize) {
6919
- const batch = messages.slice(i, i + batchSize);
6920
- yield { type: "messages", data: batch };
6570
+ options2.onProgress?.({ progress: 1, messagesProcessed: messages.length });
6571
+ return result;
6572
+ }
6573
+ function extractNameFromFileName3(fileName) {
6574
+ const basename10 = getBasename3(fileName).replace(/\.txt$/, "");
6575
+ return basename10.replace(/^\[LINE\]\s*/i, "").trim() || "\u672A\u77E5\u804A\u5929";
6576
+ }
6577
+ function extractNameFromHeader(header) {
6578
+ const englishPrivate = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
6579
+ if (englishPrivate) return { name: englishPrivate[1].trim(), isGroup: false };
6580
+ const englishGroup = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
6581
+ if (englishGroup) return { name: englishGroup[1].trim(), isGroup: true };
6582
+ const japanesePrivate = header.match(/^\[LINE\] (.+)とのトーク履歴/);
6583
+ if (japanesePrivate) return { name: japanesePrivate[1].trim(), isGroup: false };
6584
+ const japaneseGroup = header.match(/^\[LINE\] (.+)のトーク履歴/);
6585
+ if (japaneseGroup) return { name: japaneseGroup[1].trim(), isGroup: true };
6586
+ const simplifiedPrivate = header.match(/^\[LINE\] 与(.+)的聊天记录/);
6587
+ if (simplifiedPrivate) return { name: simplifiedPrivate[1].trim(), isGroup: false };
6588
+ const simplifiedGroup = header.match(/^\[LINE\] (.+)的聊天记录/);
6589
+ if (simplifiedGroup) return { name: simplifiedGroup[1].trim(), isGroup: true };
6590
+ const traditionalPrivate = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
6591
+ if (traditionalPrivate) return { name: traditionalPrivate[1].trim(), isGroup: false };
6592
+ const traditionalGroup = header.match(/^\[LINE\] (.+)的聊天記錄/);
6593
+ return traditionalGroup ? { name: traditionalGroup[1].trim(), isGroup: true } : null;
6594
+ }
6595
+ function getBasename3(fileName) {
6596
+ return fileName.split(/[\\/]/).pop() ?? fileName;
6597
+ }
6598
+ function parseDateLine(line) {
6599
+ for (let index = 0; index < DATE_PATTERNS.length; index += 1) {
6600
+ const match = line.match(DATE_PATTERNS[index]);
6601
+ if (!match) continue;
6602
+ if (index < 2) {
6603
+ return new Date(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10) - 1, Number.parseInt(match[3], 10));
6604
+ }
6605
+ return new Date(Number.parseInt(match[3], 10), Number.parseInt(match[1], 10) - 1, Number.parseInt(match[2], 10));
6921
6606
  }
6922
- const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
6607
+ return null;
6608
+ }
6609
+ function buildTimestamp(currentDate, timeText, now) {
6610
+ const { hours, minutes } = parseTime(timeText);
6611
+ const value = currentDate ? new Date(currentDate) : now?.() ?? /* @__PURE__ */ new Date();
6612
+ value.setHours(hours, minutes, 0, 0);
6613
+ return Math.floor(value.getTime() / 1e3);
6614
+ }
6615
+ function parseTime(timeText) {
6616
+ const prefix = timeText.match(/^(上午|下午|午前|午後)/)?.[1];
6617
+ const cleanTime = timeText.replace(/^(上午|下午|午前|午後)/, "");
6618
+ const match = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
6619
+ if (!match) {
6620
+ const [hours2, minutes2] = timeText.split(":").map(Number);
6621
+ return { hours: hours2, minutes: minutes2 };
6622
+ }
6623
+ let hours = Number.parseInt(match[1], 10);
6624
+ const minutes = Number.parseInt(match[2], 10);
6625
+ const suffix = match[3]?.toLowerCase();
6626
+ if (suffix === "pm" && hours < 12) hours += 12;
6627
+ if (suffix === "am" && hours === 12) hours = 0;
6628
+ if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
6629
+ if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
6630
+ return { hours, minutes };
6631
+ }
6632
+ function detectMessageType6(content) {
6633
+ for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
6634
+ if (content === pattern || content.startsWith(pattern)) return type;
6635
+ }
6636
+ if (content.startsWith("[null]") && content.includes("maps.google.com")) return 8 /* LOCATION */;
6637
+ if (content.includes(" joined the group") || content.includes("\u5DF2\u52A0\u5165\u8BE5\u7FA4") || content.includes("\u5DF2\u52A0\u5165\u7FA4\u7D44") || content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u306B\u53C2\u52A0\u3057\u307E\u3057\u305F") || content.includes(" added ") || content.includes(" to the group") || content.includes("\u5DF2\u5C06") || content.includes("\u6DFB\u52A0\u81F3\u7FA4") || content.includes("\u6DFB\u52A0\u5230\u7FA4") || content.includes("\u5DF2\u65B0\u589E") || content.includes("\u81F3\u7FA4\u7D44") || content.includes("\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F") || content.includes(" left the group") || content.includes("\u5DF2\u9000\u7FA4") || content.includes("\u5DF2\u96E2\u958B\u7FA4\u7D44") || content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u3092\u9000\u4F1A\u3057\u307E\u3057\u305F") || content.includes("made an announcement") || content.includes("\u53D1\u5E03\u4E86\u901A\u544A") || content.includes("\u5DF2\u8A2D\u5B9A\u516C\u544A") || content.includes("\u304C\u30A2\u30CA\u30A6\u30F3\u30B9\u3057\u307E\u3057\u305F") || content.includes("unsent a message") || content === "Message unsent." || content.includes("\u64A4\u56DE\u4E86\u4E00\u6761\u6D88\u606F") || content.includes("\u5DF2\u6536\u56DE\u8A0A\u606F") || content.includes("\u9001\u4FE1\u3092\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F") || content.startsWith("Auto-reply")) {
6638
+ return 80 /* SYSTEM */;
6639
+ }
6640
+ return /^https?:\/\//.test(content) ? 7 /* LINK */ : 0 /* TEXT */;
6641
+ }
6642
+
6643
+ // ../../packages/parser/src/formats/line-native-txt.ts
6644
+ var feature14 = {
6645
+ id: "line-native-txt",
6646
+ name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
6647
+ platform: KNOWN_PLATFORMS.LINE,
6648
+ priority: 35,
6649
+ extensions: [".txt"],
6650
+ signatures: {
6651
+ head: LINE_HEAD_SIGNATURES,
6652
+ filename: LINE_FILENAME_SIGNATURES
6653
+ }
6654
+ };
6655
+ async function* parseLine2(options2) {
6656
+ const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
6657
+ const totalBytes = getFileSize(filePath);
6658
+ const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
6659
+ yield { type: "progress", data: initialProgress };
6660
+ onProgress?.(initialProgress);
6661
+ onLog?.("info", `Starting LINE TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
6662
+ const result = await parseLineText(fs17.readFileSync(filePath, "utf-8"), filePath, {
6663
+ onProgress: (progress) => onProgress?.(
6664
+ createProgress(
6665
+ "parsing",
6666
+ Math.round(progress.progress * totalBytes),
6667
+ totalBytes,
6668
+ progress.messagesProcessed,
6669
+ `Processed ${progress.messagesProcessed} messages`
6670
+ )
6671
+ )
6672
+ });
6673
+ yield { type: "meta", data: result.meta };
6674
+ yield { type: "members", data: result.members };
6675
+ for (let index = 0; index < result.messages.length; index += batchSize) {
6676
+ yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
6677
+ }
6678
+ const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
6923
6679
  yield { type: "progress", data: doneProgress };
6924
6680
  onProgress?.(doneProgress);
6925
- onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberCount} \u4E2A\u6210\u5458`);
6681
+ onLog?.("info", `LINE TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
6926
6682
  yield {
6927
6683
  type: "done",
6928
- data: { messageCount: messagesProcessed, memberCount }
6684
+ data: { messageCount: result.messages.length, memberCount: result.members.length }
6929
6685
  };
6930
6686
  }
6931
6687
  var parser_13 = {
6932
6688
  feature: feature14,
6933
- parse: parseLINE
6689
+ parse: parseLine2
6934
6690
  };
6935
6691
  var module_14 = {
6936
6692
  feature: feature14,
@@ -6970,6 +6726,398 @@ var formats = [
6970
6726
  // 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
6971
6727
  ];
6972
6728
 
6729
+ // ../../packages/parser/src/chatlab-validator.ts
6730
+ import { createReadStream as createReadStream13 } from "fs";
6731
+ import { readFile, stat } from "fs/promises";
6732
+ import { extname as extname2 } from "path";
6733
+ import { createInterface as createInterface5 } from "readline";
6734
+ var MAX_REPORTED_ISSUES = 100;
6735
+ var MAX_UNIX_SECONDS = 1e11;
6736
+ var KNOWN_MESSAGE_TYPES = new Set(
6737
+ Object.values(MessageType).filter((value) => typeof value === "number")
6738
+ );
6739
+ function createState(format, fileSize) {
6740
+ return {
6741
+ format,
6742
+ fileSize,
6743
+ errorCount: 0,
6744
+ warningCount: 0,
6745
+ issues: [],
6746
+ stats: {
6747
+ headerCount: 0,
6748
+ memberCount: 0,
6749
+ messageCount: 0,
6750
+ uniqueSenderCount: 0,
6751
+ commentLineCount: 0,
6752
+ blankLineCount: 0
6753
+ },
6754
+ memberIds: /* @__PURE__ */ new Set(),
6755
+ ownerIds: /* @__PURE__ */ new Set(),
6756
+ senderIds: /* @__PURE__ */ new Set(),
6757
+ messageIds: /* @__PURE__ */ new Set(),
6758
+ replyTargetIds: /* @__PURE__ */ new Set()
6759
+ };
6760
+ }
6761
+ function addIssue(state, severity, code, message, location = {}) {
6762
+ if (severity === "error") state.errorCount++;
6763
+ else state.warningCount++;
6764
+ if (state.issues.length < MAX_REPORTED_ISSUES) {
6765
+ state.issues.push({ severity, code, message, ...location });
6766
+ }
6767
+ }
6768
+ function isRecord(value) {
6769
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6770
+ }
6771
+ function hasOwn(value, key) {
6772
+ return Object.prototype.hasOwnProperty.call(value, key);
6773
+ }
6774
+ function childLocation(location, path30) {
6775
+ return { ...location, path: path30 };
6776
+ }
6777
+ function validateRequiredString(state, value, path30, location) {
6778
+ if (typeof value === "string" && value.trim().length > 0) return true;
6779
+ addIssue(state, "error", "INVALID_REQUIRED_STRING", "Expected a non-empty string.", childLocation(location, path30));
6780
+ return false;
6781
+ }
6782
+ function validateOptionalString(state, object, key, path30, location) {
6783
+ if (!hasOwn(object, key)) return;
6784
+ if (typeof object[key] !== "string") {
6785
+ addIssue(state, "error", "INVALID_OPTIONAL_STRING", "Expected a string.", childLocation(location, path30));
6786
+ }
6787
+ }
6788
+ function validateUnixSeconds(state, value, path30, location) {
6789
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value < MAX_UNIX_SECONDS) {
6790
+ return true;
6791
+ }
6792
+ addIssue(
6793
+ state,
6794
+ "error",
6795
+ "INVALID_UNIX_SECONDS",
6796
+ "Expected a non-negative integer Unix timestamp in seconds, not milliseconds.",
6797
+ childLocation(location, path30)
6798
+ );
6799
+ return false;
6800
+ }
6801
+ function validateHeader(state, chatlab, meta, location, basePath = "") {
6802
+ state.stats.headerCount++;
6803
+ if (!isRecord(chatlab)) {
6804
+ addIssue(state, "error", "INVALID_CHATLAB_HEADER", "Expected a chatlab object.", {
6805
+ ...location,
6806
+ path: `${basePath}chatlab`
6807
+ });
6808
+ } else {
6809
+ const versionPath = `${basePath}chatlab.version`;
6810
+ if (typeof chatlab.version !== "string") {
6811
+ addIssue(state, "error", "INVALID_VERSION", "Expected a string format version.", {
6812
+ ...location,
6813
+ path: versionPath
6814
+ });
6815
+ } else {
6816
+ state.version = chatlab.version;
6817
+ if (!isSupportedChatLabFormatVersion(chatlab.version)) {
6818
+ addIssue(
6819
+ state,
6820
+ "error",
6821
+ "UNSUPPORTED_VERSION",
6822
+ `Supported ChatLab format versions: ${CHATLAB_SUPPORTED_FORMAT_VERSIONS.join(", ")}.`,
6823
+ {
6824
+ ...location,
6825
+ path: versionPath
6826
+ }
6827
+ );
6828
+ }
6829
+ }
6830
+ validateUnixSeconds(state, chatlab.exportedAt, `${basePath}chatlab.exportedAt`, location);
6831
+ validateOptionalString(state, chatlab, "generator", `${basePath}chatlab.generator`, location);
6832
+ validateOptionalString(state, chatlab, "description", `${basePath}chatlab.description`, location);
6833
+ }
6834
+ if (!isRecord(meta)) {
6835
+ addIssue(state, "error", "INVALID_META", "Expected a meta object.", {
6836
+ ...location,
6837
+ path: `${basePath}meta`
6838
+ });
6839
+ return;
6840
+ }
6841
+ validateRequiredString(state, meta.name, `${basePath}meta.name`, location);
6842
+ validateRequiredString(state, meta.platform, `${basePath}meta.platform`, location);
6843
+ if (meta.type !== "group" && meta.type !== "private") {
6844
+ addIssue(state, "error", "INVALID_CHAT_TYPE", 'Expected chat type "group" or "private".', {
6845
+ ...location,
6846
+ path: `${basePath}meta.type`
6847
+ });
6848
+ }
6849
+ validateOptionalString(state, meta, "groupId", `${basePath}meta.groupId`, location);
6850
+ validateOptionalString(state, meta, "groupAvatar", `${basePath}meta.groupAvatar`, location);
6851
+ if (hasOwn(meta, "ownerId")) {
6852
+ if (validateRequiredString(state, meta.ownerId, `${basePath}meta.ownerId`, location)) {
6853
+ state.ownerIds.add(meta.ownerId);
6854
+ }
6855
+ }
6856
+ validateOptionalString(state, meta, "sourceSessionId", `${basePath}meta.sourceSessionId`, location);
6857
+ }
6858
+ function validateMember(state, value, path30, location) {
6859
+ state.stats.memberCount++;
6860
+ if (!isRecord(value)) {
6861
+ addIssue(state, "error", "INVALID_MEMBER", "Expected a member object.", childLocation(location, path30));
6862
+ return;
6863
+ }
6864
+ if (validateRequiredString(state, value.platformId, `${path30}.platformId`, location)) {
6865
+ if (state.memberIds.has(value.platformId)) {
6866
+ addIssue(state, "error", "DUPLICATE_MEMBER_ID", "Member platformId must be unique.", {
6867
+ ...location,
6868
+ path: `${path30}.platformId`
6869
+ });
6870
+ } else {
6871
+ state.memberIds.add(value.platformId);
6872
+ }
6873
+ }
6874
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6875
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6876
+ validateOptionalString(state, value, "avatar", `${path30}.avatar`, location);
6877
+ if (hasOwn(value, "aliases")) {
6878
+ if (!Array.isArray(value.aliases) || value.aliases.some((alias) => typeof alias !== "string")) {
6879
+ addIssue(state, "error", "INVALID_ALIASES", "Expected aliases to be an array of strings.", {
6880
+ ...location,
6881
+ path: `${path30}.aliases`
6882
+ });
6883
+ }
6884
+ }
6885
+ if (hasOwn(value, "roles")) {
6886
+ if (!Array.isArray(value.roles)) {
6887
+ addIssue(state, "error", "INVALID_ROLES", "Expected roles to be an array.", {
6888
+ ...location,
6889
+ path: `${path30}.roles`
6890
+ });
6891
+ } else {
6892
+ value.roles.forEach((role, index) => {
6893
+ const rolePath = `${path30}.roles[${index}]`;
6894
+ if (!isRecord(role)) {
6895
+ addIssue(state, "error", "INVALID_ROLE", "Expected a role object.", {
6896
+ ...location,
6897
+ path: rolePath
6898
+ });
6899
+ return;
6900
+ }
6901
+ validateRequiredString(state, role.id, `${rolePath}.id`, location);
6902
+ validateOptionalString(state, role, "name", `${rolePath}.name`, location);
6903
+ });
6904
+ }
6905
+ }
6906
+ }
6907
+ function validateMessage(state, value, path30, location) {
6908
+ state.stats.messageCount++;
6909
+ if (!isRecord(value)) {
6910
+ addIssue(state, "error", "INVALID_MESSAGE", "Expected a message object.", childLocation(location, path30));
6911
+ return;
6912
+ }
6913
+ if (validateRequiredString(state, value.sender, `${path30}.sender`, location)) {
6914
+ state.senderIds.add(value.sender);
6915
+ }
6916
+ validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
6917
+ if (validateUnixSeconds(state, value.timestamp, `${path30}.timestamp`, location)) {
6918
+ if (state.lastTimestamp !== void 0 && value.timestamp < state.lastTimestamp) {
6919
+ addIssue(
6920
+ state,
6921
+ "warning",
6922
+ "OUT_OF_ORDER_TIMESTAMP",
6923
+ "Messages should be ordered by timestamp ascending.",
6924
+ childLocation(location, `${path30}.timestamp`)
6925
+ );
6926
+ }
6927
+ state.lastTimestamp = value.timestamp;
6928
+ }
6929
+ if (typeof value.type !== "number" || !Number.isInteger(value.type) || !KNOWN_MESSAGE_TYPES.has(value.type)) {
6930
+ addIssue(state, "error", "INVALID_MESSAGE_TYPE", "Expected a supported numeric ChatLab message type.", {
6931
+ ...location,
6932
+ path: `${path30}.type`
6933
+ });
6934
+ }
6935
+ if (!hasOwn(value, "content") || typeof value.content !== "string" && value.content !== null) {
6936
+ addIssue(state, "error", "INVALID_MESSAGE_CONTENT", "Expected content to be a string or null.", {
6937
+ ...location,
6938
+ path: `${path30}.content`
6939
+ });
6940
+ }
6941
+ validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
6942
+ if (hasOwn(value, "platformMessageId")) {
6943
+ if (validateRequiredString(state, value.platformMessageId, `${path30}.platformMessageId`, location)) {
6944
+ if (state.messageIds.has(value.platformMessageId)) {
6945
+ addIssue(state, "error", "DUPLICATE_MESSAGE_ID", "platformMessageId must be unique.", {
6946
+ ...location,
6947
+ path: `${path30}.platformMessageId`
6948
+ });
6949
+ } else {
6950
+ state.messageIds.add(value.platformMessageId);
6951
+ }
6952
+ }
6953
+ }
6954
+ if (hasOwn(value, "replyToMessageId")) {
6955
+ if (validateRequiredString(state, value.replyToMessageId, `${path30}.replyToMessageId`, location)) {
6956
+ state.replyTargetIds.add(value.replyToMessageId);
6957
+ }
6958
+ }
6959
+ }
6960
+ function finalizeState(state, requireExplicitMembers, enforceRequiredSections = true) {
6961
+ if (enforceRequiredSections) {
6962
+ if (state.stats.headerCount === 0) {
6963
+ addIssue(state, "error", "MISSING_HEADER", "A ChatLab header is required.");
6964
+ }
6965
+ if (state.stats.messageCount === 0) {
6966
+ addIssue(state, "error", "MISSING_MESSAGES", "At least one message is required.");
6967
+ }
6968
+ if (requireExplicitMembers && state.stats.memberCount === 0) {
6969
+ addIssue(state, "error", "MISSING_MEMBERS", "The JSON format requires a members array with sender identities.");
6970
+ }
6971
+ }
6972
+ const ownerMemberIds = state.memberIds.size > 0 ? state.memberIds : requireExplicitMembers ? void 0 : state.senderIds;
6973
+ if (ownerMemberIds) {
6974
+ for (const ownerId of state.ownerIds) {
6975
+ if (!ownerMemberIds.has(ownerId)) {
6976
+ addIssue(state, "error", "UNKNOWN_OWNER", "meta.ownerId must reference a member platformId.");
6977
+ }
6978
+ }
6979
+ }
6980
+ if (state.memberIds.size > 0) {
6981
+ for (const senderId of state.senderIds) {
6982
+ if (!state.memberIds.has(senderId)) {
6983
+ addIssue(
6984
+ state,
6985
+ "error",
6986
+ "UNKNOWN_MESSAGE_SENDER",
6987
+ "Every message sender must reference an explicit member platformId."
6988
+ );
6989
+ }
6990
+ }
6991
+ }
6992
+ for (const replyTargetId of state.replyTargetIds) {
6993
+ if (!state.messageIds.has(replyTargetId)) {
6994
+ addIssue(
6995
+ state,
6996
+ "warning",
6997
+ "UNKNOWN_REPLY_TARGET",
6998
+ "A replyToMessageId does not match any platformMessageId in this file."
6999
+ );
7000
+ }
7001
+ }
7002
+ state.stats.uniqueSenderCount = state.senderIds.size;
7003
+ const totalIssueCount = state.errorCount + state.warningCount;
7004
+ return {
7005
+ valid: state.errorCount === 0,
7006
+ format: state.format,
7007
+ version: state.version,
7008
+ fileSize: state.fileSize,
7009
+ errorCount: state.errorCount,
7010
+ warningCount: state.warningCount,
7011
+ issues: state.issues,
7012
+ truncatedIssueCount: Math.max(0, totalIssueCount - state.issues.length),
7013
+ stats: state.stats
7014
+ };
7015
+ }
7016
+ async function validateJson(filePath, fileSize) {
7017
+ const state = createState("json", fileSize);
7018
+ let root;
7019
+ try {
7020
+ root = JSON.parse(await readFile(filePath, "utf8"));
7021
+ } catch {
7022
+ addIssue(state, "error", "INVALID_JSON", "The file is not valid JSON.");
7023
+ return finalizeState(state, true, false);
7024
+ }
7025
+ if (!isRecord(root)) {
7026
+ addIssue(state, "error", "INVALID_ROOT", "Expected a top-level JSON object.");
7027
+ return finalizeState(state, true, false);
7028
+ }
7029
+ validateHeader(state, root.chatlab, root.meta, {});
7030
+ if (!Array.isArray(root.members)) {
7031
+ addIssue(state, "error", "INVALID_MEMBERS", "Expected members to be an array.", { path: "members" });
7032
+ } else {
7033
+ root.members.forEach((member, index) => validateMember(state, member, `members[${index}]`, {}));
7034
+ }
7035
+ if (!Array.isArray(root.messages)) {
7036
+ addIssue(state, "error", "INVALID_MESSAGES", "Expected messages to be an array.", { path: "messages" });
7037
+ } else {
7038
+ root.messages.forEach((message, index) => validateMessage(state, message, `messages[${index}]`, {}));
7039
+ }
7040
+ return finalizeState(state, true);
7041
+ }
7042
+ async function validateJsonl(filePath, fileSize) {
7043
+ const state = createState("jsonl", fileSize);
7044
+ const input = createReadStream13(filePath, { encoding: "utf8" });
7045
+ const lines = createInterface5({ input, crlfDelay: Infinity });
7046
+ let lineNumber = 0;
7047
+ let dataLineCount = 0;
7048
+ let sawMessage = false;
7049
+ for await (const line of lines) {
7050
+ lineNumber++;
7051
+ const trimmed = line.trim();
7052
+ if (!trimmed) {
7053
+ state.stats.blankLineCount++;
7054
+ continue;
7055
+ }
7056
+ if (trimmed.startsWith("#")) {
7057
+ state.stats.commentLineCount++;
7058
+ continue;
7059
+ }
7060
+ dataLineCount++;
7061
+ let value;
7062
+ try {
7063
+ value = JSON.parse(trimmed);
7064
+ } catch {
7065
+ addIssue(state, "error", "INVALID_JSONL_LINE", "The line is not valid JSON.", { line: lineNumber });
7066
+ continue;
7067
+ }
7068
+ if (!isRecord(value)) {
7069
+ addIssue(state, "error", "INVALID_JSONL_VALUE", "Expected a JSON object on every data line.", {
7070
+ line: lineNumber
7071
+ });
7072
+ continue;
7073
+ }
7074
+ if (dataLineCount === 1 && value._type !== "header") {
7075
+ addIssue(state, "error", "HEADER_NOT_FIRST", "The first data line must be a header.", {
7076
+ line: lineNumber,
7077
+ path: "_type"
7078
+ });
7079
+ }
7080
+ switch (value._type) {
7081
+ case "header":
7082
+ if (state.stats.headerCount > 0) {
7083
+ addIssue(state, "error", "DUPLICATE_HEADER", "Only one header line is allowed.", {
7084
+ line: lineNumber,
7085
+ path: "_type"
7086
+ });
7087
+ }
7088
+ validateHeader(state, value.chatlab, value.meta, { line: lineNumber });
7089
+ break;
7090
+ case "member":
7091
+ if (sawMessage) {
7092
+ addIssue(state, "error", "MEMBER_AFTER_MESSAGE", "Member lines must appear before message lines.", {
7093
+ line: lineNumber
7094
+ });
7095
+ }
7096
+ validateMember(state, value, `line[${lineNumber}]`, { line: lineNumber });
7097
+ break;
7098
+ case "message":
7099
+ sawMessage = true;
7100
+ validateMessage(state, value, `line[${lineNumber}]`, { line: lineNumber });
7101
+ break;
7102
+ default:
7103
+ addIssue(state, "error", "UNKNOWN_LINE_TYPE", "Expected _type to be header, member, or message.", {
7104
+ line: lineNumber,
7105
+ path: "_type"
7106
+ });
7107
+ }
7108
+ }
7109
+ return finalizeState(state, false);
7110
+ }
7111
+ async function validateChatLabFile(filePath) {
7112
+ const fileStat = await stat(filePath);
7113
+ const extension = extname2(filePath).toLowerCase();
7114
+ if (extension === ".json") return validateJson(filePath, fileStat.size);
7115
+ if (extension === ".jsonl") return validateJsonl(filePath, fileStat.size);
7116
+ const state = createState("unknown", fileStat.size);
7117
+ addIssue(state, "error", "UNSUPPORTED_EXTENSION", "Expected a .json or .jsonl file.");
7118
+ return finalizeState(state, false, false);
7119
+ }
7120
+
6973
7121
  // ../../packages/parser/src/index.ts
6974
7122
  var sniffer = createSniffer();
6975
7123
  sniffer.registerAll(formats);
@@ -7031,6 +7179,9 @@ async function* parseFileWithFormat(formatId, options2) {
7031
7179
  yield* parser11.parse(options2);
7032
7180
  }
7033
7181
  function findEntryFileInDirectory(dirPath, maxDepth = 1) {
7182
+ const supportedExtensions = new Set(
7183
+ sniffer.getSupportedFormats().flatMap((feature15) => feature15.extensions).map((extension) => extension.toLowerCase())
7184
+ );
7034
7185
  function walk(current, depth) {
7035
7186
  if (depth > maxDepth) return null;
7036
7187
  let entries;
@@ -7040,15 +7191,15 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
7040
7191
  return null;
7041
7192
  }
7042
7193
  for (const entry of entries) {
7043
- const full = path14.join(current, entry.name);
7044
- if (entry.isFile() && entry.name.endsWith(".json")) {
7194
+ const full = path11.join(current, entry.name);
7195
+ if (entry.isFile() && supportedExtensions.has(path11.extname(entry.name).toLowerCase())) {
7045
7196
  const format = detectFormat(full);
7046
7197
  if (format) return full;
7047
7198
  }
7048
7199
  }
7049
7200
  for (const entry of entries) {
7050
7201
  if (entry.isDirectory()) {
7051
- const found = walk(path14.join(current, entry.name), depth + 1);
7202
+ const found = walk(path11.join(current, entry.name), depth + 1);
7052
7203
  if (found) return found;
7053
7204
  }
7054
7205
  }
@@ -7098,7 +7249,7 @@ function logNativeParserStatus() {
7098
7249
 
7099
7250
  // ../../packages/node-runtime/src/import/perf-logger.ts
7100
7251
  import * as fs19 from "fs";
7101
- import * as path15 from "path";
7252
+ import * as path12 from "path";
7102
7253
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7103
7254
  LogLevel2["ERROR"] = "ERROR";
7104
7255
  LogLevel2["INFO"] = "INFO";
@@ -7113,7 +7264,7 @@ function initPerfLog(sessionId, logDir) {
7113
7264
  if (!fs19.existsSync(logDir)) {
7114
7265
  fs19.mkdirSync(logDir, { recursive: true });
7115
7266
  }
7116
- currentLogFile = path15.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7267
+ currentLogFile = path12.join(logDir, `import_${sessionId}_${Date.now()}.log`);
7117
7268
  fs19.writeFileSync(currentLogFile, `=== Import Log ===
7118
7269
  Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
7119
7270
 
@@ -7290,7 +7441,7 @@ function segmentText(text, locale, minLength) {
7290
7441
 
7291
7442
  // ../../packages/node-runtime/src/nlp/dict-manager.ts
7292
7443
  import * as fs20 from "fs";
7293
- import * as path16 from "path";
7444
+ import * as path13 from "path";
7294
7445
  import { createHash } from "crypto";
7295
7446
  var DICT_DOWNLOAD_URL_BASE = "https://chatlab.fun/assets/nlp";
7296
7447
  var DICT_SHA256 = {
@@ -7302,7 +7453,7 @@ var AVAILABLE_DICTS = [
7302
7453
  { id: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", locale: "zh-TW" }
7303
7454
  ];
7304
7455
  function getDictFilePath(nlpDir, dictId) {
7305
- return path16.join(nlpDir, `${dictId}.dict`);
7456
+ return path13.join(nlpDir, `${dictId}.dict`);
7306
7457
  }
7307
7458
  function isDictDownloaded(nlpDir, dictId) {
7308
7459
  return fs20.existsSync(getDictFilePath(nlpDir, dictId));
@@ -8768,7 +8919,7 @@ async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
8768
8919
 
8769
8920
  // ../../packages/node-runtime/src/import/import-lock.ts
8770
8921
  import * as fs22 from "fs";
8771
- import * as path17 from "path";
8922
+ import * as path14 from "path";
8772
8923
  import { randomUUID } from "crypto";
8773
8924
  var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
8774
8925
  var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
@@ -8837,7 +8988,7 @@ function releaseLock(lockPath, fd, owner) {
8837
8988
  }
8838
8989
  async function withDataDirImportLock(userDataDir, task) {
8839
8990
  fs22.mkdirSync(userDataDir, { recursive: true });
8840
- const lockPath = path17.join(userDataDir, IMPORT_LOCK_FILENAME);
8991
+ const lockPath = path14.join(userDataDir, IMPORT_LOCK_FILENAME);
8841
8992
  let fd = null;
8842
8993
  for (let attempt = 0; attempt < 2; attempt++) {
8843
8994
  try {
@@ -8882,7 +9033,7 @@ var ArchiveImportError = class extends Error {
8882
9033
  // ../../packages/node-runtime/src/import/archive/archive-reader.ts
8883
9034
  var import_yauzl = __toESM(require_yauzl(), 1);
8884
9035
  import * as fs23 from "fs";
8885
- import * as path18 from "path";
9036
+ import * as path15 from "path";
8886
9037
  import { pipeline } from "stream/promises";
8887
9038
  var DEFAULT_MAX_ENTRIES = 1e5;
8888
9039
  var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
@@ -9001,7 +9152,7 @@ var ZipArchiveReader = class {
9001
9152
  await this.visitEntries(async (entry, openStream) => {
9002
9153
  const destination = targets.get(entry.name);
9003
9154
  if (!destination || entry.isDirectory) return;
9004
- fs23.mkdirSync(path18.dirname(destination), { recursive: true });
9155
+ fs23.mkdirSync(path15.dirname(destination), { recursive: true });
9005
9156
  await pipeline(await openStream(), fs23.createWriteStream(destination, { flags: "wx" }));
9006
9157
  remaining.delete(entry.name);
9007
9158
  });
@@ -9015,16 +9166,16 @@ var ZipArchiveReader = class {
9015
9166
  };
9016
9167
 
9017
9168
  // ../../packages/node-runtime/src/import/archive/google-chat-resolver.ts
9018
- var import_stream_chain9 = __toESM(require_stream_chain(), 1);
9169
+ var import_stream_chain8 = __toESM(require_stream_chain(), 1);
9019
9170
  var import_stream_json9 = __toESM(require_stream_json(), 1);
9020
9171
  var import_Pick8 = __toESM(require_Pick(), 1);
9021
- var import_StreamValues9 = __toESM(require_StreamValues(), 1);
9172
+ var import_StreamValues8 = __toESM(require_StreamValues(), 1);
9022
9173
  import * as fs24 from "fs";
9023
- import * as path19 from "path";
9024
- var { chain: chain9 } = import_stream_chain9.default;
9174
+ import * as path16 from "path";
9175
+ var { chain: chain8 } = import_stream_chain8.default;
9025
9176
  var { parser: parser10 } = import_stream_json9.default;
9026
9177
  var { pick: pick8 } = import_Pick8.default;
9027
- var { streamValues: streamValues9 } = import_StreamValues9.default;
9178
+ var { streamValues: streamValues8 } = import_StreamValues8.default;
9028
9179
  var GROUP_FILE = /^Takeout\/Google Chat\/Groups\/((DM|Space) [^/]+)\/(group_info|messages)\.json$/;
9029
9180
  var USER_INFO_FILE = /^Takeout\/Google Chat\/Users\/[^/]+\/user_info\.json$/;
9030
9181
  var TAKEOUT_ROOT = /^Takeout\/([^/]+)\//;
@@ -9047,7 +9198,7 @@ async function readSmallJson(stream, maxBytes = MAX_METADATA_BYTES) {
9047
9198
  }
9048
9199
  }
9049
9200
  async function countMessages(stream) {
9050
- const pipeline2 = chain9([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues9()]);
9201
+ const pipeline2 = chain8([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues8()]);
9051
9202
  let count = 0;
9052
9203
  try {
9053
9204
  for await (const _item of pipeline2) count++;
@@ -9152,12 +9303,12 @@ var GoogleChatTakeoutResolver = class {
9152
9303
  fs24.mkdirSync(targetDir, { recursive: true });
9153
9304
  await reader.pipeEntries(
9154
9305
  /* @__PURE__ */ new Map([
9155
- [userInfoEntry, path19.join(targetDir, "user_info.json")],
9156
- [groupInfoEntry, path19.join(targetDir, "group_info.json")],
9157
- [messagesEntry, path19.join(targetDir, "messages.json")]
9306
+ [userInfoEntry, path16.join(targetDir, "user_info.json")],
9307
+ [groupInfoEntry, path16.join(targetDir, "group_info.json")],
9308
+ [messagesEntry, path16.join(targetDir, "messages.json")]
9158
9309
  ])
9159
9310
  );
9160
- const manifestPath = path19.join(targetDir, "google-chat-import.json");
9311
+ const manifestPath = path16.join(targetDir, "google-chat-import.json");
9161
9312
  fs24.writeFileSync(
9162
9313
  manifestPath,
9163
9314
  JSON.stringify(
@@ -9182,7 +9333,7 @@ var GoogleChatTakeoutResolver = class {
9182
9333
 
9183
9334
  // ../../packages/node-runtime/src/import/archive/source-manager.ts
9184
9335
  import * as fs25 from "fs";
9185
- import * as path20 from "path";
9336
+ import * as path17 from "path";
9186
9337
  import { randomUUID as randomUUID2 } from "crypto";
9187
9338
  var DEFAULT_TTL_MS = 30 * 60 * 1e3;
9188
9339
  var ArchiveImportSourceManager = class {
@@ -9254,7 +9405,7 @@ var ArchiveImportSourceManager = class {
9254
9405
  `Archive conversation was not found: ${chatId}`
9255
9406
  );
9256
9407
  }
9257
- const materializedDir = fs25.mkdtempSync(path20.join(this.tempRoot, `materialized-${sourceId}-`));
9408
+ const materializedDir = fs25.mkdtempSync(path17.join(this.tempRoot, `materialized-${sourceId}-`));
9258
9409
  try {
9259
9410
  const reader = new ZipArchiveReader(record.archivePath);
9260
9411
  const materialized = await record.resolver.materialize(reader, chat, materializedDir);
@@ -9375,12 +9526,12 @@ ${rawErrorLabel}: ${details}`;
9375
9526
  }
9376
9527
 
9377
9528
  // ../../packages/node-runtime/src/ai/assistant-parser.ts
9378
- import * as path21 from "path";
9529
+ import * as path18 from "path";
9379
9530
  import matter from "gray-matter";
9380
9531
  function parseAssistantFile(content, filePath) {
9381
9532
  try {
9382
9533
  const { data: fm, content: body } = matter(content);
9383
- const id = fm.id ?? path21.basename(filePath, ".md");
9534
+ const id = fm.id ?? path18.basename(filePath, ".md");
9384
9535
  const name = fm.name;
9385
9536
  if (!name) return null;
9386
9537
  return {
@@ -9890,12 +10041,12 @@ var AssistantManager = class {
9890
10041
  };
9891
10042
 
9892
10043
  // ../../packages/node-runtime/src/ai/skill-parser.ts
9893
- import * as path22 from "path";
10044
+ import * as path19 from "path";
9894
10045
  import matter2 from "gray-matter";
9895
10046
  function parseSkillFile(content, filePath) {
9896
10047
  try {
9897
10048
  const { data: fm, content: prompt } = matter2(content);
9898
- const id = fm.id ?? path22.basename(filePath, ".md");
10049
+ const id = fm.id ?? path19.basename(filePath, ".md");
9899
10050
  const name = fm.name;
9900
10051
  if (!name) return null;
9901
10052
  return {
@@ -9914,7 +10065,7 @@ function parseSkillFile(content, filePath) {
9914
10065
  function extractSkillId(content, filePath) {
9915
10066
  try {
9916
10067
  const { data: fm } = matter2(content);
9917
- return fm.id ?? path22.basename(filePath, ".md");
10068
+ return fm.id ?? path19.basename(filePath, ".md");
9918
10069
  } catch {
9919
10070
  return null;
9920
10071
  }
@@ -9934,7 +10085,7 @@ function validateChatScope(raw) {
9934
10085
  // ../../packages/node-runtime/src/ai/chats.ts
9935
10086
  import Database from "better-sqlite3";
9936
10087
  import * as fs26 from "fs";
9937
- import * as path23 from "path";
10088
+ import * as path20 from "path";
9938
10089
  var DEFAULT_GENERAL_ID = DEFAULT_GENERAL_ASSISTANT_ID;
9939
10090
  var defaultLogger = {
9940
10091
  warn(_category, message, extra) {
@@ -9960,7 +10111,7 @@ var AIChatManager = class {
9960
10111
  getDb() {
9961
10112
  if (this.db) return this.db;
9962
10113
  this.ensureDir(this.aiDataDir);
9963
- const dbPath = path23.join(this.aiDataDir, "conversations.db");
10114
+ const dbPath = path20.join(this.aiDataDir, "conversations.db");
9964
10115
  this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
9965
10116
  this.db.pragma("journal_mode = WAL");
9966
10117
  this.db.exec(`
@@ -10203,16 +10354,16 @@ var AIChatManager = class {
10203
10354
  if (allRows.length === 0) return [];
10204
10355
  const rowMap = new Map(allRows.map((row) => [row.id, row]));
10205
10356
  let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
10206
- const path33 = [];
10357
+ const path30 = [];
10207
10358
  const seen = /* @__PURE__ */ new Set();
10208
10359
  while (currentId && !seen.has(currentId)) {
10209
10360
  seen.add(currentId);
10210
10361
  const row = rowMap.get(currentId);
10211
10362
  if (!row) break;
10212
- path33.push(row);
10363
+ path30.push(row);
10213
10364
  currentId = row.parentId;
10214
10365
  }
10215
- return path33.length > 0 ? path33.reverse() : allRows;
10366
+ return path30.length > 0 ? path30.reverse() : allRows;
10216
10367
  }
10217
10368
  // ==================== 生命周期 ====================
10218
10369
  close() {
@@ -10949,7 +11100,7 @@ var SkillManagerCore = class {
10949
11100
 
10950
11101
  // ../../packages/node-runtime/src/ai/skill-manager.ts
10951
11102
  import * as fs27 from "fs";
10952
- import * as path24 from "path";
11103
+ import * as path21 from "path";
10953
11104
  var SKILLS_DIR_NAME = "skills";
10954
11105
  var defaultLogger2 = {
10955
11106
  info: () => {
@@ -10978,7 +11129,7 @@ var nodeFs = {
10978
11129
  return fs27.existsSync(filePath);
10979
11130
  },
10980
11131
  joinPath(...parts) {
10981
- return path24.join(...parts);
11132
+ return path21.join(...parts);
10982
11133
  }
10983
11134
  };
10984
11135
  var SkillManager = class {
@@ -10988,7 +11139,7 @@ var SkillManager = class {
10988
11139
  this.logger = logger ?? defaultLogger2;
10989
11140
  this.core = new SkillManagerCore({
10990
11141
  fs: nodeFs,
10991
- skillsDir: path24.join(aiDataDir, SKILLS_DIR_NAME),
11142
+ skillsDir: path21.join(aiDataDir, SKILLS_DIR_NAME),
10992
11143
  builtinRawSkills: [],
10993
11144
  logger: {
10994
11145
  info: () => {
@@ -12850,7 +13001,7 @@ var PLAN_INTENTS = [
12850
13001
  "mixed"
12851
13002
  ];
12852
13003
  var STREAM_MARKER_HOLDBACK_CHARS = Math.max("</draft>".length, "</plan>".length, "<json>".length) - 1;
12853
- function isRecord(value) {
13004
+ function isRecord2(value) {
12854
13005
  return typeof value === "object" && value !== null && !Array.isArray(value);
12855
13006
  }
12856
13007
  function isIntent(value) {
@@ -12872,10 +13023,10 @@ function extractJsonObject4(text) {
12872
13023
  function parsePlanJson(rawText, availableTools) {
12873
13024
  try {
12874
13025
  const parsed = JSON.parse(extractJsonObject4(rawText));
12875
- if (!isRecord(parsed)) return null;
13026
+ if (!isRecord2(parsed)) return null;
12876
13027
  const title = normalizeString(parsed.title);
12877
13028
  if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null;
12878
- const steps = parsed.steps.filter(isRecord).map((step) => {
13029
+ const steps = parsed.steps.filter(isRecord2).map((step) => {
12879
13030
  const goal = normalizeString(step.goal);
12880
13031
  const evidenceNeeded = normalizeString(step.evidenceNeeded);
12881
13032
  if (!goal || !evidenceNeeded) return null;
@@ -14854,9 +15005,9 @@ var localeMap = {
14854
15005
  "ja-JP": ja_JP_default,
14855
15006
  "zh-TW": zh_TW_default
14856
15007
  };
14857
- function getNestedValue(obj, path33) {
15008
+ function getNestedValue(obj, path30) {
14858
15009
  let current = obj;
14859
- for (const segment2 of path33.split(".")) {
15010
+ for (const segment2 of path30.split(".")) {
14860
15011
  if (current == null || typeof current !== "object") return void 0;
14861
15012
  current = current[segment2];
14862
15013
  }
@@ -15371,19 +15522,19 @@ var LLMConfigStore = class {
15371
15522
 
15372
15523
  // ../../packages/node-runtime/src/ai/file-config-storage.ts
15373
15524
  import * as fs28 from "fs";
15374
- import * as path25 from "path";
15525
+ import * as path22 from "path";
15375
15526
  function createFileConfigStorage(baseDir) {
15376
15527
  return {
15377
15528
  readJson(key) {
15378
15529
  try {
15379
- return JSON.parse(fs28.readFileSync(path25.join(baseDir, `${key}.json`), "utf-8"));
15530
+ return JSON.parse(fs28.readFileSync(path22.join(baseDir, `${key}.json`), "utf-8"));
15380
15531
  } catch {
15381
15532
  return null;
15382
15533
  }
15383
15534
  },
15384
15535
  writeJson(key, data) {
15385
15536
  fs28.mkdirSync(baseDir, { recursive: true });
15386
- fs28.writeFileSync(path25.join(baseDir, `${key}.json`), JSON.stringify(data, null, 2), "utf-8");
15537
+ fs28.writeFileSync(path22.join(baseDir, `${key}.json`), JSON.stringify(data, null, 2), "utf-8");
15387
15538
  }
15388
15539
  };
15389
15540
  }
@@ -16122,7 +16273,7 @@ function exportAsJson(db, messages, sessionName, sourceSessionId) {
16122
16273
  ).all();
16123
16274
  const data = {
16124
16275
  chatlab: {
16125
- version: "0.0.2",
16276
+ version: CHATLAB_FORMAT_VERSION,
16126
16277
  exportedAt: Math.floor(Date.now() / 1e3),
16127
16278
  generator: "ChatLab"
16128
16279
  },
@@ -16232,7 +16383,7 @@ function exportWithFormat(params, openDatabase) {
16232
16383
 
16233
16384
  // ../../packages/node-runtime/src/merger/temp-db.ts
16234
16385
  import * as fs29 from "fs";
16235
- import * as path26 from "path";
16386
+ import * as path23 from "path";
16236
16387
  var TEMP_DB_SCHEMA = `
16237
16388
  CREATE TABLE IF NOT EXISTS meta (
16238
16389
  name TEXT NOT NULL,
@@ -16447,7 +16598,7 @@ function exportSessionToJson(db) {
16447
16598
  ).all();
16448
16599
  return {
16449
16600
  chatlab: {
16450
- version: "0.0.1",
16601
+ version: CHATLAB_FORMAT_VERSION,
16451
16602
  exportedAt: Math.floor(Date.now() / 1e3),
16452
16603
  generator: "ChatLab Export",
16453
16604
  description: `Exported from session: ${meta.name}`
@@ -16491,7 +16642,7 @@ function cleanupTempDatabases(tempDir) {
16491
16642
  const files = fs29.readdirSync(tempDir);
16492
16643
  for (const file of files) {
16493
16644
  if (file.startsWith("merge_") && file.endsWith(".db")) {
16494
- deleteTempDatabase(path26.join(tempDir, file));
16645
+ deleteTempDatabase(path23.join(tempDir, file));
16495
16646
  }
16496
16647
  }
16497
16648
  } catch {
@@ -16877,7 +17028,7 @@ function clearSessionOwner(adapter, sessionId) {
16877
17028
 
16878
17029
  // ../../packages/node-runtime/src/services/contacts/overrides.ts
16879
17030
  import fs30 from "fs";
16880
- import path27 from "path";
17031
+ import path24 from "path";
16881
17032
  var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
16882
17033
  var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
16883
17034
  var CONTACT_OVERRIDES_VERSION = 1;
@@ -16888,7 +17039,7 @@ function createEmptyContactOverridesFile() {
16888
17039
  };
16889
17040
  }
16890
17041
  function getContactOverridesPath(snapshotDir) {
16891
- return path27.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
17042
+ return path24.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
16892
17043
  }
16893
17044
  function readContactOverrides(snapshotDir) {
16894
17045
  const filePath = getContactOverridesPath(snapshotDir);
@@ -16902,16 +17053,16 @@ function readContactOverrides(snapshotDir) {
16902
17053
  }
16903
17054
  function writeContactOverrides(snapshotDir, overrides) {
16904
17055
  if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
16905
- const tmpPath = path27.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
17056
+ const tmpPath = path24.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
16906
17057
  fs30.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
16907
17058
  fs30.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
16908
17059
  }
16909
17060
  function normalizeContactOverrides(value) {
16910
- const input = isRecord2(value) ? value : {};
16911
- const manualFriendsInput = isRecord2(input.manualFriends) ? input.manualFriends : {};
17061
+ const input = isRecord3(value) ? value : {};
17062
+ const manualFriendsInput = isRecord3(input.manualFriends) ? input.manualFriends : {};
16912
17063
  const manualFriends = {};
16913
17064
  for (const [key, entry] of Object.entries(manualFriendsInput)) {
16914
- if (!key || !isRecord2(entry)) continue;
17065
+ if (!key || !isRecord3(entry)) continue;
16915
17066
  const createdAt = Number.isFinite(entry.createdAt) ? Number(entry.createdAt) : Date.now();
16916
17067
  const updatedAt = Number.isFinite(entry.updatedAt) ? Number(entry.updatedAt) : createdAt;
16917
17068
  manualFriends[key] = { key, createdAt, updatedAt };
@@ -16921,7 +17072,7 @@ function normalizeContactOverrides(value) {
16921
17072
  manualFriends
16922
17073
  };
16923
17074
  }
16924
- function isRecord2(value) {
17075
+ function isRecord3(value) {
16925
17076
  return typeof value === "object" && value !== null && !Array.isArray(value);
16926
17077
  }
16927
17078
 
@@ -16940,11 +17091,11 @@ function buildContactsSignature(adapter, timeRangePreset) {
16940
17091
 
16941
17092
  // ../../packages/node-runtime/src/services/contacts/snapshot.ts
16942
17093
  import fs31 from "fs";
16943
- import path28 from "path";
17094
+ import path25 from "path";
16944
17095
  var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
16945
17096
  function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
16946
17097
  const preset = normalizeContactsTimeRangePreset(timeRangePreset);
16947
- return path28.join(snapshotDir, `contacts-snapshot-${preset}.json`);
17098
+ return path25.join(snapshotDir, `contacts-snapshot-${preset}.json`);
16948
17099
  }
16949
17100
  function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16950
17101
  const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
@@ -16953,7 +17104,7 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16953
17104
  return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
16954
17105
  } catch (error) {
16955
17106
  const ts = options2.now?.() ?? Date.now();
16956
- const backupPath = path28.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
17107
+ const backupPath = path25.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
16957
17108
  try {
16958
17109
  fs31.renameSync(snapshotPath, backupPath);
16959
17110
  } catch (renameError) {
@@ -16965,7 +17116,7 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
16965
17116
  }
16966
17117
  function writeContactsSnapshot(snapshotDir, snapshot) {
16967
17118
  if (!fs31.existsSync(snapshotDir)) fs31.mkdirSync(snapshotDir, { recursive: true });
16968
- const tmpPath = path28.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17119
+ const tmpPath = path25.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
16969
17120
  fs31.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
16970
17121
  fs31.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
16971
17122
  }
@@ -16974,7 +17125,7 @@ function cleanupContactsSnapshotTempFiles(snapshotDir) {
16974
17125
  for (const name of fs31.readdirSync(snapshotDir)) {
16975
17126
  if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
16976
17127
  try {
16977
- fs31.rmSync(path28.join(snapshotDir, name), { force: true });
17128
+ fs31.rmSync(path25.join(snapshotDir, name), { force: true });
16978
17129
  } catch (error) {
16979
17130
  appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
16980
17131
  }
@@ -17473,11 +17624,11 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
17473
17624
 
17474
17625
  // ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
17475
17626
  import fs32 from "fs";
17476
- import path29 from "path";
17627
+ import path26 from "path";
17477
17628
  var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
17478
17629
  function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
17479
17630
  const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
17480
- return path29.join(snapshotDir, `graph-snapshot-${preset}.json`);
17631
+ return path26.join(snapshotDir, `graph-snapshot-${preset}.json`);
17481
17632
  }
17482
17633
  function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
17483
17634
  const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
@@ -17486,7 +17637,7 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
17486
17637
  return JSON.parse(fs32.readFileSync(snapshotPath, "utf-8"));
17487
17638
  } catch (error) {
17488
17639
  const ts = options2.now?.() ?? Date.now();
17489
- const backupPath = path29.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17640
+ const backupPath = path26.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
17490
17641
  try {
17491
17642
  fs32.renameSync(snapshotPath, backupPath);
17492
17643
  } catch (renameError) {
@@ -17498,7 +17649,7 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
17498
17649
  }
17499
17650
  function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
17500
17651
  if (!fs32.existsSync(snapshotDir)) fs32.mkdirSync(snapshotDir, { recursive: true });
17501
- const tmpPath = path29.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17652
+ const tmpPath = path26.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
17502
17653
  fs32.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
17503
17654
  fs32.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
17504
17655
  }
@@ -17507,7 +17658,7 @@ function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
17507
17658
  for (const name of fs32.readdirSync(snapshotDir)) {
17508
17659
  if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
17509
17660
  try {
17510
- fs32.rmSync(path29.join(snapshotDir, name), { force: true });
17661
+ fs32.rmSync(path26.join(snapshotDir, name), { force: true });
17511
17662
  } catch (error) {
17512
17663
  appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
17513
17664
  }
@@ -18331,7 +18482,7 @@ __export(global_insight_exports, {
18331
18482
 
18332
18483
  // ../../packages/node-runtime/src/semantic-index/config.ts
18333
18484
  import fs34 from "fs";
18334
- import path30 from "path";
18485
+ import path27 from "path";
18335
18486
  var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
18336
18487
  var SEMANTIC_INDEX_CONFIG_VERSION = 1;
18337
18488
  var SEARCH_MAX_RESULTS_DEFAULT = 10;
@@ -18430,7 +18581,7 @@ var SemanticIndexConfigStore = class {
18430
18581
  }
18431
18582
  set(config) {
18432
18583
  const normalized = normalizeConfig(config);
18433
- const dir = path30.dirname(this.filePath);
18584
+ const dir = path27.dirname(this.filePath);
18434
18585
  if (!fs34.existsSync(dir)) fs34.mkdirSync(dir, { recursive: true });
18435
18586
  fs34.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
18436
18587
  return normalized;
@@ -18441,7 +18592,7 @@ var SemanticIndexConfigStore = class {
18441
18592
  };
18442
18593
 
18443
18594
  // ../../packages/node-runtime/src/semantic-index/service.ts
18444
- import path31 from "path";
18595
+ import path28 from "path";
18445
18596
 
18446
18597
  // ../../packages/node-runtime/src/semantic-index/chunker-config.ts
18447
18598
  import { createHash as createHash2 } from "crypto";
@@ -19972,16 +20123,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
19972
20123
  return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
19973
20124
  }
19974
20125
  function sessionIdFromDbPath(dbPath) {
19975
- return path31.basename(dbPath, ".db");
20126
+ return path28.basename(dbPath, ".db");
19976
20127
  }
19977
20128
  var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
19978
20129
  var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
19979
20130
  function createSemanticIndexService(params) {
19980
20131
  const { pathProvider } = params;
19981
20132
  return new SemanticIndexService({
19982
- vectorDbPath: path31.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
19983
- configPath: path31.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
19984
- modelsCacheDir: path31.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
20133
+ vectorDbPath: path28.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
20134
+ configPath: path28.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
20135
+ modelsCacheDir: path28.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
19985
20136
  modelDownloadProxyUrl: params.modelDownloadProxyUrl,
19986
20137
  sessionAdapter: params.sessionAdapter,
19987
20138
  nativeBinding: params.nativeBinding,
@@ -20532,7 +20683,7 @@ ${text}`;
20532
20683
 
20533
20684
  // ../../packages/node-runtime/src/services/merge-cache.ts
20534
20685
  import * as fs35 from "fs";
20535
- import * as path32 from "path";
20686
+ import * as path29 from "path";
20536
20687
  import * as crypto from "crypto";
20537
20688
  var MergeSessionCache = class {
20538
20689
  cache = /* @__PURE__ */ new Map();
@@ -20549,7 +20700,7 @@ var MergeSessionCache = class {
20549
20700
  const ts = Date.now();
20550
20701
  const rand = Math.random().toString(36).slice(2, 8);
20551
20702
  const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
20552
- return path32.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20703
+ return path29.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
20553
20704
  }
20554
20705
  /** Store a parsed file's temp DB and return a handle for subsequent requests. */
20555
20706
  store(filename, tempDbPath) {
@@ -20608,23 +20759,23 @@ var MergeSessionCache = class {
20608
20759
  import * as fs36 from "fs";
20609
20760
  var SYSTEM_SENDER_ID = "SYSTEM";
20610
20761
  var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
20611
- function isRecord3(value) {
20762
+ function isRecord4(value) {
20612
20763
  return typeof value === "object" && value !== null && !Array.isArray(value);
20613
20764
  }
20614
- function validateOptionalString(value, field) {
20765
+ function validateOptionalString2(value, field) {
20615
20766
  return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
20616
20767
  }
20617
20768
  function validatePayload(payload, isNew) {
20618
- if (!isRecord3(payload)) return "payload must be an object";
20769
+ if (!isRecord4(payload)) return "payload must be an object";
20619
20770
  const messages = payload.messages;
20620
20771
  if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
20621
20772
  if (payload.chatlab !== void 0) {
20622
- if (!isRecord3(payload.chatlab)) return "chatlab must be an object";
20773
+ if (!isRecord4(payload.chatlab)) return "chatlab must be an object";
20623
20774
  if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
20624
20775
  return "chatlab.version must be a string";
20625
20776
  if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
20626
20777
  return "chatlab.exportedAt must be a number";
20627
- const generatorError = validateOptionalString(payload.chatlab.generator, "chatlab.generator");
20778
+ const generatorError = validateOptionalString2(payload.chatlab.generator, "chatlab.generator");
20628
20779
  if (generatorError) return generatorError;
20629
20780
  }
20630
20781
  if (isNew) {
@@ -20632,17 +20783,17 @@ function validatePayload(payload, isNew) {
20632
20783
  if (!payload.meta) return "meta is required for new sessions";
20633
20784
  }
20634
20785
  if (payload.meta !== void 0) {
20635
- if (!isRecord3(payload.meta)) return "meta must be an object";
20786
+ if (!isRecord4(payload.meta)) return "meta must be an object";
20636
20787
  const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
20637
20788
  for (const field of metaFields) {
20638
- const error = validateOptionalString(payload.meta[field], `meta.${field}`);
20789
+ const error = validateOptionalString2(payload.meta[field], `meta.${field}`);
20639
20790
  if (error) return error;
20640
20791
  }
20641
20792
  if (isNew && !payload.meta.name) return "meta.name is required";
20642
20793
  if (isNew && !payload.meta.platform) return "meta.platform is required";
20643
20794
  if (isNew && !payload.meta.type) return "meta.type is required";
20644
20795
  }
20645
- if (payload.options !== void 0 && !isRecord3(payload.options)) return "options must be an object";
20796
+ if (payload.options !== void 0 && !isRecord4(payload.options)) return "options must be an object";
20646
20797
  const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
20647
20798
  if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
20648
20799
  return `options.metaUpdateMode must be 'patch' or 'none'`;
@@ -20654,18 +20805,18 @@ function validatePayload(payload, isNew) {
20654
20805
  if (payload.members) {
20655
20806
  for (let i = 0; i < payload.members.length; i++) {
20656
20807
  const member = payload.members[i];
20657
- if (!isRecord3(member)) return `members[${i}] must be an object`;
20808
+ if (!isRecord4(member)) return `members[${i}] must be an object`;
20658
20809
  if (typeof member.platformId !== "string" || member.platformId.length === 0)
20659
20810
  return `members[${i}].platformId must be a string`;
20660
20811
  for (const field of ["accountName", "groupNickname", "avatar"]) {
20661
- const error = validateOptionalString(member[field], `members[${i}].${field}`);
20812
+ const error = validateOptionalString2(member[field], `members[${i}].${field}`);
20662
20813
  if (error) return error;
20663
20814
  }
20664
20815
  if (member.roles !== void 0) {
20665
20816
  if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
20666
20817
  for (let j = 0; j < member.roles.length; j++) {
20667
20818
  const role = member.roles[j];
20668
- if (!isRecord3(role) || typeof role.id !== "string" || role.id.length === 0)
20819
+ if (!isRecord4(role) || typeof role.id !== "string" || role.id.length === 0)
20669
20820
  return `members[${i}].roles[${j}].id must be a string`;
20670
20821
  }
20671
20822
  }
@@ -20673,13 +20824,13 @@ function validatePayload(payload, isNew) {
20673
20824
  }
20674
20825
  for (let i = 0; i < messages.length; i++) {
20675
20826
  const msg = messages[i];
20676
- if (!isRecord3(msg)) return `messages[${i}] must be an object`;
20827
+ if (!isRecord4(msg)) return `messages[${i}] must be an object`;
20677
20828
  if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
20678
20829
  if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
20679
20830
  return `messages[${i}].timestamp must be a positive number`;
20680
20831
  if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
20681
20832
  for (const field of ["accountName", "groupNickname"]) {
20682
- const error = validateOptionalString(msg[field], `messages[${i}].${field}`);
20833
+ const error = validateOptionalString2(msg[field], `messages[${i}].${field}`);
20683
20834
  if (error) return error;
20684
20835
  }
20685
20836
  if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
@@ -21078,7 +21229,7 @@ export {
21078
21229
  getChatLabTempScopeDir,
21079
21230
  createChatLabTempDir,
21080
21231
  removeChatLabTempDir,
21081
- writeParseResultToDb,
21232
+ validateChatLabFile,
21082
21233
  detectFormat,
21083
21234
  detectAllFormats,
21084
21235
  getSupportedFormats,
@@ -21298,4 +21449,4 @@ export {
21298
21449
  hashImportBody,
21299
21450
  ImportIdempotencyCache
21300
21451
  };
21301
- //# sourceMappingURL=chunk-75FZ26HM.mjs.map
21452
+ //# sourceMappingURL=chunk-KWJ5FZ74.mjs.map