chatlab-cli 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{chat-command-3KUWKS53.mjs → chat-command-XPY3WURC.mjs} +9 -9
- package/dist/{chunk-LNU4RW5G.mjs → chunk-AQPDZAWB.mjs} +1604 -1327
- package/dist/chunk-AQPDZAWB.mjs.map +1 -0
- package/dist/{chunk-UVCH2ICD.mjs → chunk-BQ3QN4QT.mjs} +3 -3
- package/dist/{chunk-DV4U34MR.mjs → chunk-DGF3S3TZ.mjs} +2 -2
- package/dist/{chunk-3J7WTUEE.mjs → chunk-IGEHMLHM.mjs} +2 -2
- package/dist/{chunk-CANPJOIE.mjs → chunk-IGK6UMN2.mjs} +112 -16
- package/dist/chunk-IGK6UMN2.mjs.map +1 -0
- package/dist/{chunk-BLOBEOCG.mjs → chunk-ISRLGLUL.mjs} +2 -2
- package/dist/{chunk-27DWKA3L.mjs → chunk-JCSPDTZL.mjs} +15 -62
- package/dist/chunk-JCSPDTZL.mjs.map +1 -0
- package/dist/{chunk-23AIEWCD.mjs → chunk-JTOHAMIW.mjs} +2 -2
- package/dist/{chunk-YVNZ73O5.mjs → chunk-OLE2USEN.mjs} +4492 -4038
- package/dist/chunk-OLE2USEN.mjs.map +1 -0
- package/dist/{chunk-YAQLYL52.mjs → chunk-QZCNVAH6.mjs} +2 -2
- package/dist/{chunk-YAQLYL52.mjs.map → chunk-QZCNVAH6.mjs.map} +1 -1
- package/dist/{chunk-3IGBPOJM.mjs → chunk-S5NCEESV.mjs} +5 -4
- package/dist/chunk-S5NCEESV.mjs.map +1 -0
- package/dist/{chunk-PZM3FHVZ.mjs → chunk-UO2XVFY6.mjs} +37 -26
- package/dist/chunk-UO2XVFY6.mjs.map +1 -0
- package/dist/cli.mjs +9 -9
- package/dist/contacts-worker.mjs +2 -2
- package/dist/global-insight-worker.mjs +2 -2
- package/dist/{http-YR26GF7X.mjs → http-HV6XWU6X.mjs} +12 -12
- package/dist/{import-Q3DUACU4.mjs → import-DP2XLZ2H.mjs} +8 -8
- package/dist/index.mjs +12 -12
- package/dist/{mcp-HTVZSGTI.mjs → mcp-QDPFBPGW.mjs} +9 -9
- package/dist/people-relationships-worker.mjs +2 -2
- package/dist/semantic-index-worker.mjs +1 -1
- package/dist/{src-HJL7YZZJ.mjs → src-B3A2QHP5.mjs} +12 -10
- package/dist/{stream-import-MXETSEPW.mjs → stream-import-KEGNP3QK.mjs} +8 -8
- package/dist/{worker-runtime-DZQDL2E2.mjs → worker-runtime-MDYKX3BW.mjs} +6 -6
- package/dist-cli-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-JxPR2O6b.js +110 -0
- package/{dist-web/assets/BufferResource-BXfwfGam.js → dist-cli-web/assets/BufferResource--dSbJb0k.js} +1 -1
- package/dist-cli-web/assets/CanvasRenderer-Cm_dmJXZ.js +1 -0
- package/dist-cli-web/assets/DataDirCleanupNotice-B0s5txX3.js +1 -0
- package/dist-cli-web/assets/EChartWordcloud-CwMVJLBO.js +1 -0
- package/{dist-web/assets/Filter-Irxx0f6j.js → dist-cli-web/assets/Filter-BjYScRpK.js} +1 -1
- package/dist-cli-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-BshQybD_.js +1 -0
- package/dist-cli-web/assets/MessageExportModal-DAJW2mFj.js +1 -0
- package/dist-cli-web/assets/PageHeader-BKAjCuqs.js +1 -0
- package/{dist-web/assets/RelationshipGalaxyCanvas-ByDU6ZS_.js → dist-cli-web/assets/RelationshipGalaxyCanvas-mlZs7k4L.js} +14 -14
- package/{dist-web/assets/RelationshipGalaxyThreeCanvas-BluPHjCR.css → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-CeYJABxI.css} +1 -1
- package/{dist-web/assets/RelationshipGalaxyThreeCanvas-DgAlyKjc.js → dist-cli-web/assets/RelationshipGalaxyThreeCanvas-DgMG3jdU.js} +14 -14
- package/{dist-web/assets/RenderTargetSystem-Cx5U5HAM.js → dist-cli-web/assets/RenderTargetSystem-D00Ex44y.js} +1 -1
- package/dist-cli-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-Dsb6lzs-.js +1 -0
- package/dist-cli-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-BfR3tlkz.js +1 -0
- package/{dist-web/assets/WebGLRenderer-B3Fnp1WX.js → dist-cli-web/assets/WebGLRenderer-CTTusm2n.js} +2 -2
- package/{dist-web/assets/WebGPURenderer-DZ0X9N86.js → dist-cli-web/assets/WebGPURenderer-pyv3EXor.js} +2 -2
- package/dist-cli-web/assets/annual-summary-time-range-C_d1oZrj.js +1 -0
- package/dist-cli-web/assets/base-D91aVUwM.js +1 -0
- package/{dist-web/assets/browserAll-BqC9ELNO.js → dist-cli-web/assets/browserAll-C9fW936Y.js} +1 -1
- package/dist-cli-web/assets/cli-web-C7uGKz_E.js +1 -0
- package/{dist-web/assets/dateFormat-CX9atL2g.js → dist-cli-web/assets/dateFormat-rY43adcv.js} +1 -1
- package/{dist-web/assets/fetch-CUT9JdXT.js → dist-cli-web/assets/fetch-C6po4XYB.js} +3 -3
- package/dist-cli-web/assets/fetch-CNy9OQ2g.js +1 -0
- package/dist-cli-web/assets/fetch-CS3T-v-1.js +1 -0
- package/{dist-web/assets/fetch-C3KuMfIU.js → dist-cli-web/assets/fetch-CbnX5VoM.js} +3 -3
- package/dist-cli-web/assets/fetch-CrSz5KJy.js +1 -0
- package/dist-cli-web/assets/fetch-DG3AeEjW.js +1 -0
- package/dist-cli-web/assets/fetch-DYNgtIcU.js +1 -0
- package/{dist-web/assets/fetch-th_egarZ.js → dist-cli-web/assets/fetch-DZPC3D8R.js} +2 -2
- package/dist-cli-web/assets/fetch-Kk2knkEo.js +1 -0
- package/dist-cli-web/assets/fetch-PLrI3V67.js +1 -0
- package/dist-cli-web/assets/index-2-OaYdzt.js +1 -0
- package/dist-cli-web/assets/index-2S80bo1s.js +2 -0
- package/dist-cli-web/assets/index-3YO6RI5e.js +2 -0
- package/dist-cli-web/assets/index-BKo68Iu5.js +1 -0
- package/{dist-web/assets/index-Bm_U0hSr.js → dist-cli-web/assets/index-BrkDgcJ6.js} +4 -4
- package/dist-cli-web/assets/index-CGI3Y8pq.js +1 -0
- package/dist-cli-web/assets/index-CW9H_fBd.js +53 -0
- package/dist-cli-web/assets/index-DA3Q0TGT.js +1 -0
- package/dist-cli-web/assets/index-DFHYr_-u.js +1 -0
- package/dist-cli-web/assets/index-DmrrdYXH.css +1 -0
- package/dist-cli-web/assets/index-DmwA85vG.js +173 -0
- package/dist-cli-web/assets/index-DrjoMv9s.css +1 -0
- package/dist-cli-web/assets/index-DsdMsKYT.js +1 -0
- package/dist-cli-web/assets/index-JYfjMRb-.js +1 -0
- package/dist-cli-web/assets/index-Jhwca9kg.css +1 -0
- package/dist-cli-web/assets/index-ZTgfCZvY.js +64 -0
- package/dist-cli-web/assets/index-vtF05fh7.css +1 -0
- package/dist-cli-web/assets/people-page-header-D-LWaEqn.js +1 -0
- package/{dist-web/assets/webworkerAll-a_fveCLR.js → dist-cli-web/assets/webworkerAll-CPOVECup.js} +1 -1
- package/{dist-web → dist-cli-web}/index.html +2 -4
- package/package.json +4 -4
- package/dist/chunk-27DWKA3L.mjs.map +0 -1
- package/dist/chunk-3IGBPOJM.mjs.map +0 -1
- package/dist/chunk-CANPJOIE.mjs.map +0 -1
- package/dist/chunk-LNU4RW5G.mjs.map +0 -1
- package/dist/chunk-PZM3FHVZ.mjs.map +0 -1
- package/dist/chunk-YVNZ73O5.mjs.map +0 -1
- package/dist-web/assets/ActionToolsPanel.vue_vue_type_script_setup_true_lang-DfQa9E2W.js +0 -110
- package/dist-web/assets/CanvasRenderer-Bvpsbxds.js +0 -1
- package/dist-web/assets/DataDirCleanupNotice-C0sCmNL2.js +0 -1
- package/dist-web/assets/EChartWordcloud-C5hLT1vS.js +0 -1
- package/dist-web/assets/LoadingState.vue_vue_type_script_setup_true_lang-8s6XRB3S.js +0 -1
- package/dist-web/assets/MessageExportModal-tTI358HU.js +0 -1
- package/dist-web/assets/PageHeader-D5QQI9io.js +0 -1
- package/dist-web/assets/ThemeCard.vue_vue_type_script_setup_true_lang-CpNjFhpm.js +0 -1
- package/dist-web/assets/TimeSelect.vue_vue_type_script_setup_true_lang-DtCKfbaz.js +0 -1
- package/dist-web/assets/annual-summary-time-range-CvG74N2l.js +0 -1
- package/dist-web/assets/base-DdQRTliS.js +0 -1
- package/dist-web/assets/electron-ChQkD5ht.js +0 -1
- package/dist-web/assets/electron-DGxVMsvC.js +0 -1
- package/dist-web/assets/electron-hcWiB7Gg.js +0 -1
- package/dist-web/assets/fetch-3Ifr5YJN.js +0 -1
- package/dist-web/assets/fetch-74o_kRZf.js +0 -1
- package/dist-web/assets/fetch-BKFznq4l.js +0 -1
- package/dist-web/assets/fetch-BPj6IlT-.js +0 -1
- package/dist-web/assets/fetch-CoYaXr5K.js +0 -1
- package/dist-web/assets/fetch-Dcg6Q6Ei.js +0 -1
- package/dist-web/assets/fetch-DxWfYzkB.js +0 -1
- package/dist-web/assets/index-B3riRaJA.js +0 -1
- package/dist-web/assets/index-BKFGuwJu.css +0 -1
- package/dist-web/assets/index-BRDdozcm.js +0 -173
- package/dist-web/assets/index-Bg-OiHMa.js +0 -1
- package/dist-web/assets/index-BhewGKey.js +0 -2
- package/dist-web/assets/index-BpeJ40uK.js +0 -1
- package/dist-web/assets/index-C6bwt1H4.js +0 -1
- package/dist-web/assets/index-CEJqRkis.js +0 -2
- package/dist-web/assets/index-CKLCaqdT.js +0 -1
- package/dist-web/assets/index-D8Uq3_Dc.css +0 -1
- package/dist-web/assets/index-DBb03wVF.css +0 -1
- package/dist-web/assets/index-Ds5PiVyB.js +0 -64
- package/dist-web/assets/index-GXMxhYc9.js +0 -1
- package/dist-web/assets/index-JyB6Cr8r.js +0 -25
- package/dist-web/assets/index-UJl03IYE.css +0 -1
- package/dist-web/assets/index-tx-Oal36.js +0 -1
- package/dist-web/assets/people-page-header-DGkK0AMO.js +0 -1
- package/dist-web/assets/types-CZnACqaB.js +0 -1
- package/dist-web/assets/vendor-nuxt-ui-DMdbdSFy.js +0 -15
- package/dist-web/assets/vendor-reka-ui-8_x7iKfi.js +0 -15
- package/dist-web/assets/web-aZcioccr.js +0 -1
- /package/dist/{chat-command-3KUWKS53.mjs.map → chat-command-XPY3WURC.mjs.map} +0 -0
- /package/dist/{chunk-UVCH2ICD.mjs.map → chunk-BQ3QN4QT.mjs.map} +0 -0
- /package/dist/{chunk-DV4U34MR.mjs.map → chunk-DGF3S3TZ.mjs.map} +0 -0
- /package/dist/{chunk-3J7WTUEE.mjs.map → chunk-IGEHMLHM.mjs.map} +0 -0
- /package/dist/{chunk-BLOBEOCG.mjs.map → chunk-ISRLGLUL.mjs.map} +0 -0
- /package/dist/{chunk-23AIEWCD.mjs.map → chunk-JTOHAMIW.mjs.map} +0 -0
- /package/dist/{http-YR26GF7X.mjs.map → http-HV6XWU6X.mjs.map} +0 -0
- /package/dist/{import-Q3DUACU4.mjs.map → import-DP2XLZ2H.mjs.map} +0 -0
- /package/dist/{mcp-HTVZSGTI.mjs.map → mcp-QDPFBPGW.mjs.map} +0 -0
- /package/dist/{src-HJL7YZZJ.mjs.map → src-B3A2QHP5.mjs.map} +0 -0
- /package/dist/{stream-import-MXETSEPW.mjs.map → stream-import-KEGNP3QK.mjs.map} +0 -0
- /package/dist/{worker-runtime-DZQDL2E2.mjs.map → worker-runtime-MDYKX3BW.mjs.map} +0 -0
- /package/{dist-web → dist-cli-web}/assets/ActionToolsPanel-Dp-ojXjO.css +0 -0
- /package/{dist-web → dist-cli-web}/assets/PageHeader-XIXMTomn.css +0 -0
- /package/{dist-web → dist-cli-web}/assets/index-BfO_LpDv.css +0 -0
- /package/{dist-web → dist-cli-web}/assets/index-ByUaiVQK.css +0 -0
- /package/{dist-web → dist-cli-web}/assets/index-ZsZwPLWy.css +0 -0
- /package/{dist-web → dist-cli-web}/assets/logo-DXIuG_WE.svg +0 -0
- /package/{dist-web → dist-cli-web}/assets/relationship-galaxy-viewport-Cwfa51ER.js +0 -0
- /package/{dist-web → dist-cli-web}/assets/vendor-echarts-B-i_56Av.js +0 -0
- /package/{dist-web → dist-cli-web}/assets/vendor-echarts-wordcloud-Ca8n6qS6.js +0 -0
- /package/{dist-web → dist-cli-web}/assets/vendor-snapdom--nZvo8Sg.js +0 -0
- /package/{dist-web → dist-cli-web}/assets/vendor-zrender-5DcYjSZk.js +0 -0
- /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-
|
|
12
|
+
} from "./chunk-IGEHMLHM.mjs";
|
|
13
13
|
import {
|
|
14
14
|
PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
|
|
15
15
|
buildPeopleRelationshipsNeighborhoodGraph,
|
|
@@ -18,15 +18,17 @@ import {
|
|
|
18
18
|
computePeopleRelationshipsSnapshot,
|
|
19
19
|
normalizePeopleRelationshipsTimeRangePreset,
|
|
20
20
|
resolvePeopleRelationshipsTimeRange
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-JTOHAMIW.mjs";
|
|
22
22
|
import {
|
|
23
23
|
ANNUAL_SUMMARY_ALGORITHM_VERSION
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-QZCNVAH6.mjs";
|
|
25
25
|
import {
|
|
26
26
|
BUILTIN_MODELS,
|
|
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,
|
|
@@ -48,11 +51,14 @@ import {
|
|
|
48
51
|
deleteAuthProfile,
|
|
49
52
|
deleteMember,
|
|
50
53
|
deleteMembers,
|
|
54
|
+
deriveAuthProfileName,
|
|
51
55
|
generateIncrementalSessionIndex,
|
|
52
56
|
generateMessageKey,
|
|
53
57
|
generateSessionIndex,
|
|
54
58
|
getAnnualSummarySnapshotPath,
|
|
59
|
+
getBuiltinModelById,
|
|
55
60
|
getBuiltinModelsByProvider,
|
|
61
|
+
getBuiltinProviderById,
|
|
56
62
|
getChatSessionList,
|
|
57
63
|
getContactsDir,
|
|
58
64
|
getContactsFactsCacheDir,
|
|
@@ -76,6 +82,8 @@ import {
|
|
|
76
82
|
isChatSessionDb,
|
|
77
83
|
isNameMatchPlatform,
|
|
78
84
|
isReasoningModel,
|
|
85
|
+
isSupportedChatLabFormatVersion,
|
|
86
|
+
loadAuthProfiles,
|
|
79
87
|
matchOwnerProfile,
|
|
80
88
|
mergeConfirmedNames,
|
|
81
89
|
mergeMembers,
|
|
@@ -84,6 +92,7 @@ import {
|
|
|
84
92
|
normalizeBuiltinToolNames,
|
|
85
93
|
openBetterSqliteDatabase,
|
|
86
94
|
readAnnualSummarySnapshot,
|
|
95
|
+
readJsonFile,
|
|
87
96
|
renameSession,
|
|
88
97
|
resolveApiKey,
|
|
89
98
|
saveSegmentSummary,
|
|
@@ -96,9 +105,12 @@ import {
|
|
|
96
105
|
truncateToolResultText,
|
|
97
106
|
updateMemberAliases,
|
|
98
107
|
updateSessionOwnerId,
|
|
108
|
+
withFileLock,
|
|
99
109
|
writeAnnualSummarySnapshot,
|
|
100
|
-
writeAuthProfile
|
|
101
|
-
|
|
110
|
+
writeAuthProfile,
|
|
111
|
+
writeJsonFileAtomically,
|
|
112
|
+
writeParseResultToDb
|
|
113
|
+
} from "./chunk-OLE2USEN.mjs";
|
|
102
114
|
import {
|
|
103
115
|
__commonJS,
|
|
104
116
|
__export,
|
|
@@ -714,8 +726,8 @@ var require_FilterBase = __commonJS({
|
|
|
714
726
|
var FilterBase = class _FilterBase extends Transform {
|
|
715
727
|
static stringFilter(string, separator) {
|
|
716
728
|
return (stack) => {
|
|
717
|
-
const
|
|
718
|
-
return
|
|
729
|
+
const path30 = stack.join(separator);
|
|
730
|
+
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
731
|
};
|
|
720
732
|
}
|
|
721
733
|
static regExpFilter(regExp, separator) {
|
|
@@ -1057,9 +1069,9 @@ var require_stream_chain = __commonJS({
|
|
|
1057
1069
|
var require_withParser = __commonJS({
|
|
1058
1070
|
"../../node_modules/stream-json/utils/withParser.js"(exports, module) {
|
|
1059
1071
|
"use strict";
|
|
1060
|
-
var { chain:
|
|
1072
|
+
var { chain: chain9 } = require_stream_chain();
|
|
1061
1073
|
var Parser = require_Parser();
|
|
1062
|
-
var withParser = (fn, options2) =>
|
|
1074
|
+
var withParser = (fn, options2) => chain9([new Parser(options2), fn(options2)], Object.assign({}, options2, { writableObjectMode: false, readableObjectMode: true }));
|
|
1063
1075
|
module.exports = withParser;
|
|
1064
1076
|
}
|
|
1065
1077
|
});
|
|
@@ -1137,7 +1149,7 @@ var require_Assembler = __commonJS({
|
|
|
1137
1149
|
this.current = new Ctr();
|
|
1138
1150
|
this.key = null;
|
|
1139
1151
|
};
|
|
1140
|
-
var
|
|
1152
|
+
var Assembler2 = class _Assembler extends EventEmitter {
|
|
1141
1153
|
static connectTo(stream, options2) {
|
|
1142
1154
|
return new _Assembler(options2).connectTo(stream);
|
|
1143
1155
|
}
|
|
@@ -1169,12 +1181,12 @@ var require_Assembler = __commonJS({
|
|
|
1169
1181
|
return (this.stack.length >> 1) + (this.done ? 0 : 1);
|
|
1170
1182
|
}
|
|
1171
1183
|
get path() {
|
|
1172
|
-
const
|
|
1184
|
+
const path30 = [];
|
|
1173
1185
|
for (let i = 0; i < this.stack.length; i += 2) {
|
|
1174
1186
|
const key = this.stack[i + 1];
|
|
1175
|
-
|
|
1187
|
+
path30.push(key === null ? this.stack[i].length : key);
|
|
1176
1188
|
}
|
|
1177
|
-
return
|
|
1189
|
+
return path30;
|
|
1178
1190
|
}
|
|
1179
1191
|
dropToLevel(level) {
|
|
1180
1192
|
if (level < this.depth) {
|
|
@@ -1256,11 +1268,11 @@ var require_Assembler = __commonJS({
|
|
|
1256
1268
|
}
|
|
1257
1269
|
}
|
|
1258
1270
|
};
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
module.exports =
|
|
1271
|
+
Assembler2.prototype.stringValue = Assembler2.prototype._saveValue;
|
|
1272
|
+
Assembler2.prototype.startObject = startObject(Object);
|
|
1273
|
+
Assembler2.prototype.startArray = startObject(Array);
|
|
1274
|
+
Assembler2.prototype.endArray = Assembler2.prototype.endObject;
|
|
1275
|
+
module.exports = Assembler2;
|
|
1264
1276
|
}
|
|
1265
1277
|
});
|
|
1266
1278
|
|
|
@@ -1269,7 +1281,7 @@ var require_StreamBase = __commonJS({
|
|
|
1269
1281
|
"../../node_modules/stream-json/streamers/StreamBase.js"(exports, module) {
|
|
1270
1282
|
"use strict";
|
|
1271
1283
|
var { Transform } = __require("stream");
|
|
1272
|
-
var
|
|
1284
|
+
var Assembler2 = require_Assembler();
|
|
1273
1285
|
var Counter = class {
|
|
1274
1286
|
constructor(initialDepth) {
|
|
1275
1287
|
this.depth = initialDepth;
|
|
@@ -1298,7 +1310,7 @@ var require_StreamBase = __commonJS({
|
|
|
1298
1310
|
this._filter = this._transform;
|
|
1299
1311
|
}
|
|
1300
1312
|
this._transform = this._wait || this._filter;
|
|
1301
|
-
this._assembler = new
|
|
1313
|
+
this._assembler = new Assembler2(options2);
|
|
1302
1314
|
}
|
|
1303
1315
|
_transform(chunk, encoding, callback) {
|
|
1304
1316
|
if (this._assembler[chunk.name]) {
|
|
@@ -1456,7 +1468,7 @@ var require_pend = __commonJS({
|
|
|
1456
1468
|
var require_fd_slicer = __commonJS({
|
|
1457
1469
|
"../../packages/node-runtime/node_modules/yauzl/fd-slicer.js"(exports) {
|
|
1458
1470
|
"use strict";
|
|
1459
|
-
var
|
|
1471
|
+
var fs36 = __require("fs");
|
|
1460
1472
|
var util = __require("util");
|
|
1461
1473
|
var stream = __require("stream");
|
|
1462
1474
|
var Readable = stream.Readable;
|
|
@@ -1476,7 +1488,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1476
1488
|
FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
|
|
1477
1489
|
var self = this;
|
|
1478
1490
|
self.pend.go(function(cb) {
|
|
1479
|
-
|
|
1491
|
+
fs36.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
|
|
1480
1492
|
cb();
|
|
1481
1493
|
callback(err, bytesRead, buffer2);
|
|
1482
1494
|
});
|
|
@@ -1493,7 +1505,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1493
1505
|
self.refCount -= 1;
|
|
1494
1506
|
if (self.refCount < 0) throw new Error("invalid unref");
|
|
1495
1507
|
if (self.refCount > 0) return;
|
|
1496
|
-
|
|
1508
|
+
fs36.close(self.fd, onCloseDone);
|
|
1497
1509
|
function onCloseDone(err) {
|
|
1498
1510
|
if (err) {
|
|
1499
1511
|
self.emit("error", err);
|
|
@@ -1525,7 +1537,7 @@ var require_fd_slicer = __commonJS({
|
|
|
1525
1537
|
}
|
|
1526
1538
|
self.context.pend.go(function(cb) {
|
|
1527
1539
|
var buffer = Buffer.allocUnsafe(toRead);
|
|
1528
|
-
|
|
1540
|
+
fs36.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
|
|
1529
1541
|
if (err) {
|
|
1530
1542
|
self.destroy(err);
|
|
1531
1543
|
} else if (bytesRead === 0) {
|
|
@@ -1887,7 +1899,7 @@ var require_crc32 = __commonJS({
|
|
|
1887
1899
|
var require_yauzl = __commonJS({
|
|
1888
1900
|
"../../packages/node-runtime/node_modules/yauzl/index.js"(exports) {
|
|
1889
1901
|
"use strict";
|
|
1890
|
-
var
|
|
1902
|
+
var fs36 = __require("fs");
|
|
1891
1903
|
var zlib = __require("zlib");
|
|
1892
1904
|
var fd_slicer = require_fd_slicer();
|
|
1893
1905
|
var util = __require("util");
|
|
@@ -1912,9 +1924,9 @@ var require_yauzl = __commonJS({
|
|
|
1912
1924
|
exports.Entry = Entry;
|
|
1913
1925
|
exports.LocalFileHeader = LocalFileHeader;
|
|
1914
1926
|
exports.RandomAccessReader = RandomAccessReader;
|
|
1915
|
-
function openPromise2(
|
|
1927
|
+
function openPromise2(path30, options2) {
|
|
1916
1928
|
return new Promise((resolve4, reject) => {
|
|
1917
|
-
open(
|
|
1929
|
+
open(path30, { ...options2, lazyEntries: true }, function(err, zipfile) {
|
|
1918
1930
|
if (err) return reject(err);
|
|
1919
1931
|
resolve4(zipfile);
|
|
1920
1932
|
});
|
|
@@ -1944,7 +1956,7 @@ var require_yauzl = __commonJS({
|
|
|
1944
1956
|
});
|
|
1945
1957
|
});
|
|
1946
1958
|
}
|
|
1947
|
-
function open(
|
|
1959
|
+
function open(path30, options2, callback) {
|
|
1948
1960
|
if (typeof options2 === "function") {
|
|
1949
1961
|
callback = options2;
|
|
1950
1962
|
options2 = null;
|
|
@@ -1956,10 +1968,10 @@ var require_yauzl = __commonJS({
|
|
|
1956
1968
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
1957
1969
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
1958
1970
|
if (callback == null) callback = defaultCallback;
|
|
1959
|
-
|
|
1971
|
+
fs36.open(path30, "r", function(err, fd) {
|
|
1960
1972
|
if (err) return callback(err);
|
|
1961
1973
|
fromFd(fd, options2, function(err2, zipfile) {
|
|
1962
|
-
if (err2)
|
|
1974
|
+
if (err2) fs36.close(fd, defaultCallback);
|
|
1963
1975
|
callback(err2, zipfile);
|
|
1964
1976
|
});
|
|
1965
1977
|
});
|
|
@@ -1976,7 +1988,7 @@ var require_yauzl = __commonJS({
|
|
|
1976
1988
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
1977
1989
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
1978
1990
|
if (callback == null) callback = defaultCallback;
|
|
1979
|
-
|
|
1991
|
+
fs36.fstat(fd, function(err, stats) {
|
|
1980
1992
|
if (err) return callback(err);
|
|
1981
1993
|
var reader = new fd_slicer.FdSlicer(fd);
|
|
1982
1994
|
fromRandomAccessReader(reader, stats.size, options2, callback);
|
|
@@ -2810,122 +2822,9 @@ function removeChatLabTempDir(dirPath, scope, options2 = {}) {
|
|
|
2810
2822
|
fs.rmSync(resolvedDir, { recursive: true, force: true });
|
|
2811
2823
|
}
|
|
2812
2824
|
|
|
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
2825
|
// ../../packages/parser/src/index.ts
|
|
2927
2826
|
import * as fs18 from "fs";
|
|
2928
|
-
import * as
|
|
2827
|
+
import * as path11 from "path";
|
|
2929
2828
|
|
|
2930
2829
|
// ../../packages/parser/src/sniffer.ts
|
|
2931
2830
|
import * as fs2 from "fs";
|
|
@@ -3315,8 +3214,8 @@ var { pick } = import_Pick.default;
|
|
|
3315
3214
|
var { streamValues } = import_StreamValues.default;
|
|
3316
3215
|
var { chain } = import_stream_chain.default;
|
|
3317
3216
|
function extractNameFromFilePath(filePath) {
|
|
3318
|
-
const
|
|
3319
|
-
const name =
|
|
3217
|
+
const basename10 = path3.basename(filePath);
|
|
3218
|
+
const name = basename10.replace(/\.json$/i, "");
|
|
3320
3219
|
return name || "\u672A\u77E5\u7FA4\u804A";
|
|
3321
3220
|
}
|
|
3322
3221
|
var feature = {
|
|
@@ -3486,8 +3385,8 @@ import * as fs5 from "fs";
|
|
|
3486
3385
|
import * as readline from "readline";
|
|
3487
3386
|
import * as path4 from "path";
|
|
3488
3387
|
function extractNameFromFilePath2(filePath) {
|
|
3489
|
-
const
|
|
3490
|
-
const name =
|
|
3388
|
+
const basename10 = path4.basename(filePath);
|
|
3389
|
+
const name = basename10.replace(/\.jsonl$/i, "");
|
|
3491
3390
|
return name || "\u672A\u77E5\u7FA4\u804A";
|
|
3492
3391
|
}
|
|
3493
3392
|
function parseLine(line) {
|
|
@@ -3509,8 +3408,10 @@ var feature2 = {
|
|
|
3509
3408
|
// 低优先级,让其他格式先匹配
|
|
3510
3409
|
extensions: [".jsonl"],
|
|
3511
3410
|
signatures: {
|
|
3512
|
-
//
|
|
3513
|
-
head: [
|
|
3411
|
+
// 跳过格式允许的空行和注释后,分别匹配首条数据中的 header 类型和 chatlab 对象。
|
|
3412
|
+
head: [
|
|
3413
|
+
/^\uFEFF?(?:[ \t]*(?:#[^\r\n]*)?\r?\n)*[ \t]*\{(?=[^\r\n]*"_type"\s*:\s*"header")(?=[^\r\n]*"chatlab"\s*:)[^\r\n]*\}/
|
|
3414
|
+
]
|
|
3514
3415
|
}
|
|
3515
3416
|
};
|
|
3516
3417
|
async function* parseChatLabJsonl(options2) {
|
|
@@ -3561,7 +3462,9 @@ async function* parseChatLabJsonl(options2) {
|
|
|
3561
3462
|
platformId: parsed.platformId,
|
|
3562
3463
|
accountName: parsed.accountName,
|
|
3563
3464
|
groupNickname: parsed.groupNickname,
|
|
3564
|
-
|
|
3465
|
+
aliases: parsed.aliases,
|
|
3466
|
+
avatar: parsed.avatar,
|
|
3467
|
+
roles: parsed.roles
|
|
3565
3468
|
};
|
|
3566
3469
|
members.push(member);
|
|
3567
3470
|
memberMap.set(parsed.platformId, member);
|
|
@@ -3960,8 +3863,8 @@ function extractJsonObject2(content, key) {
|
|
|
3960
3863
|
return null;
|
|
3961
3864
|
}
|
|
3962
3865
|
function extractNameFromFilePath3(filePath) {
|
|
3963
|
-
const
|
|
3964
|
-
return
|
|
3866
|
+
const basename10 = path6.basename(filePath);
|
|
3867
|
+
return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u7FA4\u804A";
|
|
3965
3868
|
}
|
|
3966
3869
|
function isPlaceholderSenderId(id) {
|
|
3967
3870
|
return id === "0" || id === "\u672A\u77E5";
|
|
@@ -4561,8 +4464,8 @@ var { pick: pick4 } = import_Pick4.default;
|
|
|
4561
4464
|
var { streamValues: streamValues4 } = import_StreamValues4.default;
|
|
4562
4465
|
var { chain: chain4 } = import_stream_chain4.default;
|
|
4563
4466
|
function extractNameFromFilePath4(filePath) {
|
|
4564
|
-
const
|
|
4565
|
-
const name =
|
|
4467
|
+
const basename10 = path8.basename(filePath);
|
|
4468
|
+
const name = basename10.replace(/\.json$/i, "");
|
|
4566
4469
|
return name || "\u672A\u77E5\u804A\u5929";
|
|
4567
4470
|
}
|
|
4568
4471
|
var feature5 = {
|
|
@@ -5473,13 +5376,16 @@ var module_8 = {
|
|
|
5473
5376
|
var telegram_native_default = module_8;
|
|
5474
5377
|
|
|
5475
5378
|
// ../../packages/parser/src/formats/telegram-native-single.ts
|
|
5476
|
-
var
|
|
5379
|
+
var import_Assembler = __toESM(require_Assembler(), 1);
|
|
5477
5380
|
var import_stream_json7 = __toESM(require_stream_json(), 1);
|
|
5478
|
-
var import_StreamValues7 = __toESM(require_StreamValues(), 1);
|
|
5479
5381
|
import * as fs12 from "fs";
|
|
5480
|
-
|
|
5382
|
+
|
|
5383
|
+
// ../../packages/parser/src/browser/telegram.ts
|
|
5384
|
+
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)"/;
|
|
5385
|
+
var TELEGRAM_SINGLE_HEAD_SIGNATURES = [/^\s*\{\s*(?:\r?\n\s*)?"name"\s*:/];
|
|
5386
|
+
|
|
5387
|
+
// ../../packages/parser/src/formats/telegram-native-single.ts
|
|
5481
5388
|
var { parser: parser8 } = import_stream_json7.default;
|
|
5482
|
-
var { streamValues: streamValues7 } = import_StreamValues7.default;
|
|
5483
5389
|
var feature9 = {
|
|
5484
5390
|
id: "telegram-native-single",
|
|
5485
5391
|
name: "Telegram \u5355\u804A\u5929\u5BFC\u51FA (JSON)",
|
|
@@ -5487,98 +5393,97 @@ var feature9 = {
|
|
|
5487
5393
|
priority: 23,
|
|
5488
5394
|
extensions: [".json"],
|
|
5489
5395
|
signatures: {
|
|
5490
|
-
|
|
5491
|
-
// 这与全量导出(以 "about" 开头)有效区分,避免嵌套字段误匹配
|
|
5492
|
-
head: [/^\s*\{\s*\r?\n\s*"name"\s*:/],
|
|
5396
|
+
head: TELEGRAM_SINGLE_HEAD_SIGNATURES,
|
|
5493
5397
|
requiredFields: ["messages"],
|
|
5494
|
-
fieldPatterns: {
|
|
5495
|
-
// Telegram 特有的聊天类型值,精确区分
|
|
5496
|
-
telegramChatType: /"type"\s*:\s*"(personal_chat|bot_chat|private_group|private_supergroup|public_group|public_supergroup|public_channel|private_channel|saved_messages)"/
|
|
5497
|
-
}
|
|
5398
|
+
fieldPatterns: { telegramChatType: TELEGRAM_SINGLE_CHAT_TYPE_PATTERN }
|
|
5498
5399
|
}
|
|
5499
5400
|
};
|
|
5500
5401
|
async function* parseTelegramSingle(options2) {
|
|
5501
5402
|
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
5502
5403
|
const totalBytes = getFileSize(filePath);
|
|
5503
|
-
let messagesProcessed = 0;
|
|
5504
5404
|
const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
|
|
5505
5405
|
yield { type: "progress", data: initialProgress };
|
|
5506
5406
|
onProgress?.(initialProgress);
|
|
5507
|
-
onLog?.("info",
|
|
5508
|
-
|
|
5509
|
-
const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
|
|
5510
|
-
const pipeline2 = chain7([readStream, parser8(), streamValues7()]);
|
|
5511
|
-
let found = false;
|
|
5512
|
-
pipeline2.on("data", ({ value }) => {
|
|
5513
|
-
if (!found) {
|
|
5514
|
-
found = true;
|
|
5515
|
-
resolve4(value);
|
|
5516
|
-
}
|
|
5517
|
-
});
|
|
5518
|
-
pipeline2.on("end", () => {
|
|
5519
|
-
if (!found) resolve4(null);
|
|
5520
|
-
});
|
|
5521
|
-
pipeline2.on("error", reject);
|
|
5522
|
-
});
|
|
5523
|
-
if (!chatData) {
|
|
5524
|
-
onLog?.("error", "\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6");
|
|
5525
|
-
yield { type: "error", data: new Error("\u65E0\u6CD5\u89E3\u6790 Telegram \u5355\u804A\u5929\u6587\u4EF6") };
|
|
5526
|
-
return;
|
|
5527
|
-
}
|
|
5528
|
-
onLog?.("info", `\u804A\u5929: "${chatData.name}", \u7C7B\u578B: ${chatData.type}, \u6D88\u606F\u6570: ${chatData.messages?.length || 0}`);
|
|
5529
|
-
const chatType = mapChatType(chatData.type);
|
|
5530
|
-
const meta = {
|
|
5531
|
-
name: chatData.name || `Telegram Chat ${chatData.id}`,
|
|
5532
|
-
platform: KNOWN_PLATFORMS.TELEGRAM,
|
|
5533
|
-
type: chatType,
|
|
5534
|
-
groupId: chatType === "group" /* GROUP */ ? String(chatData.id) : void 0
|
|
5535
|
-
};
|
|
5536
|
-
yield { type: "meta", data: meta };
|
|
5407
|
+
onLog?.("info", `Starting Telegram single-chat JSON parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
|
|
5408
|
+
let messagesProcessed = 0;
|
|
5537
5409
|
const memberMap = /* @__PURE__ */ new Map();
|
|
5538
5410
|
const messageBatch = [];
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
let
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5411
|
+
try {
|
|
5412
|
+
const readStream = fs12.createReadStream(filePath, { encoding: "utf-8" });
|
|
5413
|
+
const tokenStream = readStream.pipe(parser8());
|
|
5414
|
+
let depth = 0;
|
|
5415
|
+
let rootKey = null;
|
|
5416
|
+
let chatName = null;
|
|
5417
|
+
let telegramType = null;
|
|
5418
|
+
let chatId = null;
|
|
5419
|
+
let messagesArrayDepth = null;
|
|
5420
|
+
let messageAssembler = null;
|
|
5421
|
+
let meta = null;
|
|
5422
|
+
for await (const rawToken of tokenStream) {
|
|
5423
|
+
const token = rawToken;
|
|
5424
|
+
if (messageAssembler) {
|
|
5425
|
+
messageAssembler.consume(token);
|
|
5426
|
+
if (messageAssembler.done) {
|
|
5427
|
+
const message = messageAssembler.current;
|
|
5428
|
+
messageAssembler = null;
|
|
5429
|
+
const parsedMessage = parseTelegramMessage(message, memberMap);
|
|
5430
|
+
if (parsedMessage) {
|
|
5431
|
+
messageBatch.push(parsedMessage);
|
|
5432
|
+
messagesProcessed++;
|
|
5433
|
+
}
|
|
5434
|
+
if (messageBatch.length >= batchSize) {
|
|
5435
|
+
yield { type: "messages", data: messageBatch.splice(0) };
|
|
5436
|
+
const progress = createProgress(
|
|
5437
|
+
"parsing",
|
|
5438
|
+
readStream.bytesRead,
|
|
5439
|
+
totalBytes,
|
|
5440
|
+
messagesProcessed,
|
|
5441
|
+
`Processed ${messagesProcessed} messages`
|
|
5442
|
+
);
|
|
5443
|
+
yield { type: "progress", data: progress };
|
|
5444
|
+
onProgress?.(progress);
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
} else if (token.name === "keyValue" && depth === 1) {
|
|
5448
|
+
rootKey = token.value ?? null;
|
|
5449
|
+
} else if (token.name === "stringValue" && depth === 1) {
|
|
5450
|
+
if (rootKey === "name") chatName = token.value ?? "";
|
|
5451
|
+
if (rootKey === "type") telegramType = token.value ?? "";
|
|
5452
|
+
} else if (token.name === "numberValue" && depth === 1 && rootKey === "id") {
|
|
5453
|
+
const parsedId = Number(token.value);
|
|
5454
|
+
if (Number.isFinite(parsedId)) chatId = parsedId;
|
|
5455
|
+
}
|
|
5456
|
+
if (token.name === "startArray" && depth === 1 && rootKey === "messages") {
|
|
5457
|
+
if (chatName === null || telegramType === null || chatId === null) {
|
|
5458
|
+
throw new Error("Invalid Telegram single-chat JSON export");
|
|
5459
|
+
}
|
|
5460
|
+
const chatType = mapChatType(telegramType);
|
|
5461
|
+
meta = {
|
|
5462
|
+
name: chatName || `Telegram Chat ${chatId}`,
|
|
5463
|
+
platform: KNOWN_PLATFORMS.TELEGRAM,
|
|
5464
|
+
type: chatType,
|
|
5465
|
+
groupId: chatType === "group" /* GROUP */ ? String(chatId) : void 0
|
|
5466
|
+
};
|
|
5467
|
+
messagesArrayDepth = depth + 1;
|
|
5468
|
+
yield { type: "meta", data: meta };
|
|
5469
|
+
} else if (token.name === "startObject" && messagesArrayDepth !== null && depth === messagesArrayDepth) {
|
|
5470
|
+
messageAssembler = new import_Assembler.default();
|
|
5471
|
+
messageAssembler.consume(token);
|
|
5472
|
+
}
|
|
5473
|
+
if (token.name === "startObject" || token.name === "startArray") {
|
|
5474
|
+
depth++;
|
|
5475
|
+
} else if (token.name === "endObject" || token.name === "endArray") {
|
|
5476
|
+
depth--;
|
|
5477
|
+
}
|
|
5555
5478
|
}
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
const parsedMsg = {
|
|
5559
|
-
platformMessageId: String(msg.id),
|
|
5560
|
-
senderPlatformId,
|
|
5561
|
-
senderAccountName: senderName,
|
|
5562
|
-
timestamp,
|
|
5563
|
-
type: detectMessageType(msg),
|
|
5564
|
-
content: buildContent(msg),
|
|
5565
|
-
replyToMessageId: msg.reply_to_message_id ? String(msg.reply_to_message_id) : void 0
|
|
5566
|
-
};
|
|
5567
|
-
messageBatch.push(parsedMsg);
|
|
5568
|
-
messagesProcessed++;
|
|
5569
|
-
if (messageBatch.length >= batchSize) {
|
|
5570
|
-
yield { type: "messages", data: [...messageBatch] };
|
|
5571
|
-
messageBatch.length = 0;
|
|
5572
|
-
const progress = createProgress(
|
|
5573
|
-
"parsing",
|
|
5574
|
-
0,
|
|
5575
|
-
totalBytes,
|
|
5576
|
-
messagesProcessed,
|
|
5577
|
-
`\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
|
|
5578
|
-
);
|
|
5579
|
-
yield { type: "progress", data: progress };
|
|
5580
|
-
onProgress?.(progress);
|
|
5479
|
+
if (!meta || messagesArrayDepth === null || messageAssembler) {
|
|
5480
|
+
throw new Error("Invalid Telegram single-chat JSON export");
|
|
5581
5481
|
}
|
|
5482
|
+
} catch (error) {
|
|
5483
|
+
const parseError = error instanceof Error ? error : new Error(String(error));
|
|
5484
|
+
onLog?.("error", "Telegram single-chat JSON parsing failed");
|
|
5485
|
+
yield { type: "error", data: parseError };
|
|
5486
|
+
return;
|
|
5582
5487
|
}
|
|
5583
5488
|
yield { type: "members", data: Array.from(memberMap.values()) };
|
|
5584
5489
|
if (messageBatch.length > 0) {
|
|
@@ -5587,77 +5492,63 @@ async function* parseTelegramSingle(options2) {
|
|
|
5587
5492
|
const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
|
|
5588
5493
|
yield { type: "progress", data: doneProgress };
|
|
5589
5494
|
onProgress?.(doneProgress);
|
|
5590
|
-
onLog?.(
|
|
5495
|
+
onLog?.(
|
|
5496
|
+
"info",
|
|
5497
|
+
`Telegram single-chat JSON parsing completed: ${messagesProcessed} messages, ${memberMap.size} members`
|
|
5498
|
+
);
|
|
5591
5499
|
yield {
|
|
5592
5500
|
type: "done",
|
|
5593
5501
|
data: { messageCount: messagesProcessed, memberCount: memberMap.size }
|
|
5594
5502
|
};
|
|
5595
5503
|
}
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5504
|
+
function parseTelegramMessage(message, memberMap) {
|
|
5505
|
+
const isService = message.type === "service";
|
|
5506
|
+
const senderPlatformId = extractPlatformId(isService ? message.actor_id : message.from_id);
|
|
5507
|
+
const senderName = isService ? message.actor || "System" : message.from || senderPlatformId;
|
|
5508
|
+
if (!memberMap.has(senderPlatformId) && senderPlatformId !== "unknown") {
|
|
5509
|
+
memberMap.set(senderPlatformId, {
|
|
5510
|
+
platformId: senderPlatformId,
|
|
5511
|
+
accountName: senderName
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5514
|
+
const timestamp = Number.parseInt(message.date_unixtime, 10);
|
|
5515
|
+
if (Number.isNaN(timestamp)) return null;
|
|
5516
|
+
return {
|
|
5517
|
+
platformMessageId: String(message.id),
|
|
5518
|
+
senderPlatformId,
|
|
5519
|
+
senderAccountName: senderName,
|
|
5520
|
+
timestamp,
|
|
5521
|
+
type: detectMessageType(message),
|
|
5522
|
+
content: buildContent(message),
|
|
5523
|
+
replyToMessageId: message.reply_to_message_id ? String(message.reply_to_message_id) : void 0
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
var parser_8 = { feature: feature9, parse: parseTelegramSingle };
|
|
5527
|
+
var module_9 = { feature: feature9, parser: parser_8 };
|
|
5604
5528
|
var telegram_native_single_default = module_9;
|
|
5605
5529
|
|
|
5606
5530
|
// ../../packages/parser/src/formats/whatsapp-native-txt.ts
|
|
5607
5531
|
import * as fs13 from "fs";
|
|
5608
|
-
import * as path9 from "path";
|
|
5609
5532
|
import * as readline3 from "readline";
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
}
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
}
|
|
5626
|
-
var feature10 = {
|
|
5627
|
-
id: "whatsapp-native-txt",
|
|
5628
|
-
name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
5629
|
-
platform: KNOWN_PLATFORMS.WHATSAPP,
|
|
5630
|
-
priority: 25,
|
|
5631
|
-
extensions: [".txt"],
|
|
5632
|
-
signatures: {
|
|
5633
|
-
// WhatsApp 导出文件的特征(中文/英文)
|
|
5634
|
-
// 注意:仅保留 WhatsApp 独有的特征,避免误匹配其他 TXT 格式(如 LINE)
|
|
5635
|
-
head: [
|
|
5636
|
-
/消息和通话已进行端到端加密/,
|
|
5637
|
-
// 简体中文加密提示(WhatsApp 独有)
|
|
5638
|
-
/訊息與通話已受端對端加密保護/,
|
|
5639
|
-
// 繁体中文加密提示(WhatsApp 独有)
|
|
5640
|
-
/Messages and calls are end-to-end encrypted/i,
|
|
5641
|
-
// 英文加密提示(WhatsApp 独有)
|
|
5642
|
-
/你发送给自己的消息已进行端到端加密/,
|
|
5643
|
-
// 简体中文自己对话提示(WhatsApp 独有)
|
|
5644
|
-
/\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,
|
|
5645
|
-
// 消息行格式特征(无方括号,含 " - " 分隔符,WhatsApp 独有)
|
|
5646
|
-
/\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
|
|
5647
|
-
// 消息行格式特征(方括号 + 含日期和时间的时间戳,兼容各地区变体)
|
|
5648
|
-
],
|
|
5649
|
-
// 文件名特征:与xxx的 WhatsApp 聊天.txt
|
|
5650
|
-
filename: [/^与.+的\s*WhatsApp\s*聊天\.txt$/i, /^與.+的\s*WhatsApp\s*對話\.txt$/i, /WhatsApp/i]
|
|
5651
|
-
}
|
|
5652
|
-
};
|
|
5653
|
-
function cleanLine(line) {
|
|
5654
|
-
return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
|
|
5655
|
-
}
|
|
5656
|
-
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;
|
|
5533
|
+
|
|
5534
|
+
// ../../packages/parser/src/browser/whatsapp.ts
|
|
5535
|
+
var WHATSAPP_HEAD_SIGNATURES = [
|
|
5536
|
+
/消息和通话已进行端到端加密/,
|
|
5537
|
+
/訊息與通話已受端對端加密保護/,
|
|
5538
|
+
/Messages and calls are end-to-end encrypted/i,
|
|
5539
|
+
/你发送给自己的消息已进行端到端加密/,
|
|
5540
|
+
/\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,
|
|
5541
|
+
/\[\d{1,4}\/\d{1,2}\/\d{1,4}[\s,].*\d{1,2}:\d{2}(?::\d{2})?.*\] /
|
|
5542
|
+
];
|
|
5543
|
+
var WHATSAPP_FILENAME_SIGNATURES = [
|
|
5544
|
+
/^与.+的\s*WhatsApp\s*聊天\.txt$/i,
|
|
5545
|
+
/^與.+的\s*WhatsApp\s*對話\.txt$/i,
|
|
5546
|
+
/WhatsApp/i
|
|
5547
|
+
];
|
|
5548
|
+
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;
|
|
5657
5549
|
var MESSAGE_LINE_REGEX_V2 = /^\[([^\]]+)\] (.+)$/;
|
|
5658
5550
|
var SENDER_CONTENT_REGEX = /^(.+?):[\s\u200E]*(.*)$/;
|
|
5659
5551
|
var SYSTEM_MESSAGE_PATTERNS = [
|
|
5660
|
-
// 简体中文系统消息
|
|
5661
5552
|
/消息和通话已进行端到端加密/,
|
|
5662
5553
|
/创建了此群组/,
|
|
5663
5554
|
/加入群组/,
|
|
@@ -5669,7 +5560,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
|
|
|
5669
5560
|
/这条消息已删除/,
|
|
5670
5561
|
/限时消息功能/,
|
|
5671
5562
|
/正在等待此消息/,
|
|
5672
|
-
// 繁体中文系统消息
|
|
5673
5563
|
/訊息與通話已受端對端加密保護/,
|
|
5674
5564
|
/建立了此群組/,
|
|
5675
5565
|
/加入了群組/,
|
|
@@ -5679,7 +5569,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
|
|
|
5679
5569
|
/已變更本群組/,
|
|
5680
5570
|
/此訊息已刪除/,
|
|
5681
5571
|
/限時訊息/,
|
|
5682
|
-
// 英文系统消息
|
|
5683
5572
|
/Messages and calls are end-to-end encrypted/i,
|
|
5684
5573
|
/created this group/i,
|
|
5685
5574
|
/joined the group/i,
|
|
@@ -5690,21 +5579,6 @@ var SYSTEM_MESSAGE_PATTERNS = [
|
|
|
5690
5579
|
/This message was deleted/i,
|
|
5691
5580
|
/disappearing messages/i
|
|
5692
5581
|
];
|
|
5693
|
-
function isSystemMessage(content) {
|
|
5694
|
-
return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
|
|
5695
|
-
}
|
|
5696
|
-
function detectMessageType2(content) {
|
|
5697
|
-
const trimmed = content.trim();
|
|
5698
|
-
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")
|
|
5699
|
-
return 1 /* IMAGE */;
|
|
5700
|
-
if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A"))
|
|
5701
|
-
return 4 /* FILE */;
|
|
5702
|
-
if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
|
|
5703
|
-
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"))
|
|
5704
|
-
return 81 /* RECALL */;
|
|
5705
|
-
if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
|
|
5706
|
-
return 0 /* TEXT */;
|
|
5707
|
-
}
|
|
5708
5582
|
var AMPM_MARKERS = [
|
|
5709
5583
|
[/\bPM\b/i, true],
|
|
5710
5584
|
[/\bP\.M\.(?!\w)/i, true],
|
|
@@ -5717,191 +5591,226 @@ var AMPM_MARKERS = [
|
|
|
5717
5591
|
[/午前/, false],
|
|
5718
5592
|
[/오전/, false]
|
|
5719
5593
|
];
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5594
|
+
var WhatsAppTextAccumulator = class {
|
|
5595
|
+
constructor(fileName) {
|
|
5596
|
+
this.fileName = fileName;
|
|
5597
|
+
this.fileChatName = extractNameFromFileName(fileName);
|
|
5598
|
+
}
|
|
5599
|
+
fileName;
|
|
5600
|
+
fileChatName;
|
|
5601
|
+
memberMap = /* @__PURE__ */ new Map();
|
|
5602
|
+
messages = [];
|
|
5603
|
+
currentMessage = null;
|
|
5604
|
+
skippedLines = 0;
|
|
5605
|
+
get messageCount() {
|
|
5606
|
+
return this.messages.length + (this.currentMessage ? 1 : 0);
|
|
5607
|
+
}
|
|
5608
|
+
pushLine(line) {
|
|
5609
|
+
const cleanedLine = cleanLine(line);
|
|
5610
|
+
const v1Match = cleanedLine.match(MESSAGE_LINE_REGEX_V1);
|
|
5611
|
+
if (v1Match) {
|
|
5612
|
+
this.saveCurrentMessage();
|
|
5613
|
+
this.currentMessage = createPendingMessage(parseFlexibleTimestamp(v1Match[1]) ?? 0, v1Match[2]);
|
|
5614
|
+
return;
|
|
5615
|
+
}
|
|
5616
|
+
const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
|
|
5617
|
+
if (v2Match) {
|
|
5618
|
+
const timestamp = parseFlexibleTimestamp(v2Match[1]);
|
|
5619
|
+
if (timestamp !== null) {
|
|
5620
|
+
this.saveCurrentMessage();
|
|
5621
|
+
this.currentMessage = createPendingMessage(timestamp, v2Match[2]);
|
|
5622
|
+
return;
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
if (this.currentMessage && cleanedLine) {
|
|
5626
|
+
this.currentMessage.contentLines.push(cleanedLine);
|
|
5627
|
+
} else if (cleanedLine) {
|
|
5628
|
+
this.skippedLines += 1;
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
finish() {
|
|
5632
|
+
this.saveCurrentMessage();
|
|
5633
|
+
const chatType = this.memberMap.size > 2 ? "group" /* GROUP */ : "private" /* PRIVATE */;
|
|
5634
|
+
const chatName = chatType === "private" /* PRIVATE */ && shouldInferPrivateChatName(this.fileName, this.fileChatName) ? inferPrivateChatName(this.memberMap, this.fileChatName) : this.fileChatName;
|
|
5635
|
+
return {
|
|
5636
|
+
meta: { name: chatName, platform: KNOWN_PLATFORMS.WHATSAPP, type: chatType },
|
|
5637
|
+
members: Array.from(this.memberMap.values(), (member) => ({
|
|
5638
|
+
platformId: member.platformId,
|
|
5639
|
+
accountName: member.nickname
|
|
5640
|
+
})),
|
|
5641
|
+
messages: this.messages,
|
|
5642
|
+
skippedLines: this.skippedLines
|
|
5643
|
+
};
|
|
5644
|
+
}
|
|
5645
|
+
saveCurrentMessage() {
|
|
5646
|
+
if (!this.currentMessage) return;
|
|
5647
|
+
const content = this.currentMessage.contentLines.join("\n").trim();
|
|
5648
|
+
const senderPlatformId = this.currentMessage.sender ?? "system";
|
|
5649
|
+
const senderName = this.currentMessage.sender ?? "\u7CFB\u7EDF\u6D88\u606F";
|
|
5650
|
+
this.messages.push({
|
|
5651
|
+
senderPlatformId,
|
|
5652
|
+
senderAccountName: senderName,
|
|
5653
|
+
timestamp: this.currentMessage.timestamp,
|
|
5654
|
+
type: detectMessageType2(content),
|
|
5655
|
+
content: content || null
|
|
5656
|
+
});
|
|
5657
|
+
if (this.currentMessage.sender) {
|
|
5658
|
+
this.memberMap.set(senderPlatformId, { platformId: senderPlatformId, nickname: senderName });
|
|
5728
5659
|
}
|
|
5660
|
+
this.currentMessage = null;
|
|
5661
|
+
}
|
|
5662
|
+
};
|
|
5663
|
+
function createPendingMessage(timestamp, restContent) {
|
|
5664
|
+
const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
|
|
5665
|
+
if (senderMatch && !isSystemMessage(restContent)) {
|
|
5666
|
+
return { timestamp, sender: senderMatch[1].trim(), contentLines: [senderMatch[2]] };
|
|
5667
|
+
}
|
|
5668
|
+
return { timestamp, sender: null, contentLines: [restContent] };
|
|
5669
|
+
}
|
|
5670
|
+
function extractNameFromFileName(fileName) {
|
|
5671
|
+
const basename10 = getBasename(fileName);
|
|
5672
|
+
const simplifiedChinese = basename10.match(/^与(.+?)的\s*WhatsApp\s*聊天\.txt$/i);
|
|
5673
|
+
if (simplifiedChinese) return simplifiedChinese[1].trim();
|
|
5674
|
+
const traditionalChinese = basename10.match(/^與(.+?)的\s*WhatsApp\s*對話\.txt$/i);
|
|
5675
|
+
if (traditionalChinese) return traditionalChinese[1].trim();
|
|
5676
|
+
return basename10.replace(/\.txt$/i, "") || "\u672A\u77E5\u804A\u5929";
|
|
5677
|
+
}
|
|
5678
|
+
function shouldInferPrivateChatName(fileName, fallbackName) {
|
|
5679
|
+
const basename10 = getBasename(fileName);
|
|
5680
|
+
return /^_chat\.txt$/i.test(basename10) || /^whatsapp(?:[-_\s].*)?$/i.test(fallbackName);
|
|
5681
|
+
}
|
|
5682
|
+
function inferPrivateChatName(memberMap, fallbackName) {
|
|
5683
|
+
return Array.from(memberMap.values()).find((member) => member.platformId !== "system")?.nickname ?? fallbackName;
|
|
5684
|
+
}
|
|
5685
|
+
function getBasename(fileName) {
|
|
5686
|
+
return fileName.split(/[\\/]/).pop() ?? fileName;
|
|
5687
|
+
}
|
|
5688
|
+
function cleanLine(line) {
|
|
5689
|
+
return line.replace(/^(?:\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)+/, "").trim();
|
|
5690
|
+
}
|
|
5691
|
+
function isSystemMessage(content) {
|
|
5692
|
+
return SYSTEM_MESSAGE_PATTERNS.some((pattern) => pattern.test(content));
|
|
5693
|
+
}
|
|
5694
|
+
function detectMessageType2(content) {
|
|
5695
|
+
const trimmed = content.trim();
|
|
5696
|
+
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") {
|
|
5697
|
+
return 1 /* IMAGE */;
|
|
5698
|
+
}
|
|
5699
|
+
if (trimmed.includes("<\u5DF2\u9644\u52A0:") || trimmed.includes("<\u9644\u4EF6:") || trimmed.includes("<\u5DF2\u9644\u52A0\uFF1A")) {
|
|
5700
|
+
return 4 /* FILE */;
|
|
5701
|
+
}
|
|
5702
|
+
if (trimmed === "\u8CBC\u5716\u5DF2\u5FFD\u7565" || trimmed === "\u8CBC\u5716\u5DF2\u7565\u53BB") return 5 /* EMOJI */;
|
|
5703
|
+
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")) {
|
|
5704
|
+
return 81 /* RECALL */;
|
|
5705
|
+
}
|
|
5706
|
+
if (isSystemMessage(trimmed)) return 80 /* SYSTEM */;
|
|
5707
|
+
return 0 /* TEXT */;
|
|
5708
|
+
}
|
|
5709
|
+
function parseFlexibleTimestamp(raw) {
|
|
5710
|
+
let value = raw.replace(/(?:\u2009|\u202F|\uFEFF|\u200E|\u200F|\u200B|\u200C|\u200D|\u2060)/g, " ").trim();
|
|
5711
|
+
let isPm = null;
|
|
5712
|
+
for (const [pattern, pm] of AMPM_MARKERS) {
|
|
5713
|
+
if (!pattern.test(value)) continue;
|
|
5714
|
+
isPm = pm;
|
|
5715
|
+
value = value.replace(pattern, "").trim();
|
|
5716
|
+
break;
|
|
5729
5717
|
}
|
|
5730
|
-
|
|
5731
|
-
const match =
|
|
5718
|
+
value = value.replace(/,/g, " ").replace(/\s+/g, " ").trim();
|
|
5719
|
+
const match = value.match(/^(\d{1,4}\/\d{1,2}\/\d{1,4})\s+(\d{1,2}:\d{2}(?::\d{2})?)$/);
|
|
5732
5720
|
if (!match) return null;
|
|
5733
|
-
const
|
|
5734
|
-
const
|
|
5735
|
-
|
|
5736
|
-
let
|
|
5721
|
+
const dateParts = match[1].split("/").map((part) => Number.parseInt(part, 10));
|
|
5722
|
+
const timeParts = match[2].split(":").map((part) => Number.parseInt(part, 10));
|
|
5723
|
+
let year;
|
|
5724
|
+
let month;
|
|
5725
|
+
let day;
|
|
5737
5726
|
if (dateParts[0] > 31) {
|
|
5738
|
-
|
|
5739
|
-
month = dateParts
|
|
5740
|
-
day = dateParts[2];
|
|
5727
|
+
;
|
|
5728
|
+
[year, month, day] = dateParts;
|
|
5741
5729
|
} else if (dateParts[2] > 31) {
|
|
5742
|
-
|
|
5743
|
-
month = dateParts
|
|
5744
|
-
year = dateParts[2];
|
|
5730
|
+
;
|
|
5731
|
+
[day, month, year] = dateParts;
|
|
5745
5732
|
} else {
|
|
5746
|
-
|
|
5747
|
-
day = dateParts
|
|
5733
|
+
;
|
|
5734
|
+
[month, day] = dateParts;
|
|
5748
5735
|
year = 2e3 + dateParts[2];
|
|
5749
5736
|
}
|
|
5750
5737
|
let hour = timeParts[0];
|
|
5751
5738
|
const minute = timeParts[1];
|
|
5752
5739
|
const second = timeParts[2] ?? 0;
|
|
5753
|
-
if (
|
|
5754
|
-
if (
|
|
5740
|
+
if (isPm === true && hour !== 12) hour += 12;
|
|
5741
|
+
if (isPm === false && hour === 12) hour = 0;
|
|
5755
5742
|
const date = new Date(year, month - 1, day, hour, minute, second);
|
|
5756
|
-
const
|
|
5757
|
-
return isNaN(
|
|
5758
|
-
}
|
|
5759
|
-
function parseV1Timestamp(timeStr) {
|
|
5760
|
-
return parseFlexibleV2Timestamp(timeStr) ?? 0;
|
|
5743
|
+
const timestamp = Math.floor(date.getTime() / 1e3);
|
|
5744
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
5761
5745
|
}
|
|
5746
|
+
|
|
5747
|
+
// ../../packages/parser/src/formats/whatsapp-native-txt.ts
|
|
5748
|
+
var feature10 = {
|
|
5749
|
+
id: "whatsapp-native-txt",
|
|
5750
|
+
name: "WhatsApp \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
5751
|
+
platform: KNOWN_PLATFORMS.WHATSAPP,
|
|
5752
|
+
priority: 25,
|
|
5753
|
+
extensions: [".txt"],
|
|
5754
|
+
signatures: {
|
|
5755
|
+
head: WHATSAPP_HEAD_SIGNATURES,
|
|
5756
|
+
filename: WHATSAPP_FILENAME_SIGNATURES
|
|
5757
|
+
}
|
|
5758
|
+
};
|
|
5762
5759
|
async function* parseWhatsApp(options2) {
|
|
5763
5760
|
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
5764
5761
|
const totalBytes = getFileSize(filePath);
|
|
5762
|
+
const accumulator = new WhatsAppTextAccumulator(filePath);
|
|
5765
5763
|
let bytesRead = 0;
|
|
5766
|
-
let
|
|
5767
|
-
let skippedLines = 0;
|
|
5764
|
+
let lastReportedMessages = 0;
|
|
5768
5765
|
const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
|
|
5769
5766
|
yield { type: "progress", data: initialProgress };
|
|
5770
5767
|
onProgress?.(initialProgress);
|
|
5771
|
-
onLog?.("info",
|
|
5772
|
-
const fileChatName = extractNameFromFilePath5(filePath);
|
|
5773
|
-
const memberMap = /* @__PURE__ */ new Map();
|
|
5774
|
-
const messages = [];
|
|
5775
|
-
let currentMessage = null;
|
|
5776
|
-
const saveCurrentMessage = () => {
|
|
5777
|
-
if (currentMessage) {
|
|
5778
|
-
const content = currentMessage.contentLines.join("\n").trim();
|
|
5779
|
-
const type = detectMessageType2(content);
|
|
5780
|
-
const senderPlatformId = currentMessage.sender || "system";
|
|
5781
|
-
const senderName = currentMessage.sender || "\u7CFB\u7EDF\u6D88\u606F";
|
|
5782
|
-
messages.push({
|
|
5783
|
-
senderPlatformId,
|
|
5784
|
-
senderAccountName: senderName,
|
|
5785
|
-
timestamp: currentMessage.timestamp,
|
|
5786
|
-
type,
|
|
5787
|
-
content: content || null
|
|
5788
|
-
});
|
|
5789
|
-
if (currentMessage.sender) {
|
|
5790
|
-
memberMap.set(senderPlatformId, {
|
|
5791
|
-
platformId: senderPlatformId,
|
|
5792
|
-
nickname: senderName
|
|
5793
|
-
});
|
|
5794
|
-
}
|
|
5795
|
-
messagesProcessed++;
|
|
5796
|
-
}
|
|
5797
|
-
};
|
|
5768
|
+
onLog?.("info", `Starting WhatsApp TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
|
|
5798
5769
|
const fileStream = fs13.createReadStream(filePath, { encoding: "utf-8" });
|
|
5799
|
-
const
|
|
5800
|
-
input: fileStream,
|
|
5801
|
-
crlfDelay: Infinity
|
|
5802
|
-
});
|
|
5770
|
+
const lines = readline3.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
5803
5771
|
fileStream.on("data", (chunk) => {
|
|
5804
5772
|
bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
5805
5773
|
});
|
|
5806
|
-
for await (const line of
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
} else {
|
|
5820
|
-
currentMessage = {
|
|
5821
|
-
timestamp: parseV1Timestamp(v1Match[1]),
|
|
5822
|
-
sender: null,
|
|
5823
|
-
contentLines: [restContent]
|
|
5824
|
-
};
|
|
5825
|
-
}
|
|
5826
|
-
if (messagesProcessed % 500 === 0) {
|
|
5827
|
-
onProgress?.(
|
|
5828
|
-
createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
|
|
5829
|
-
);
|
|
5830
|
-
}
|
|
5831
|
-
continue;
|
|
5832
|
-
}
|
|
5833
|
-
const v2Match = cleanedLine.match(MESSAGE_LINE_REGEX_V2);
|
|
5834
|
-
if (v2Match) {
|
|
5835
|
-
const timestamp = parseFlexibleV2Timestamp(v2Match[1]);
|
|
5836
|
-
if (timestamp !== null) {
|
|
5837
|
-
saveCurrentMessage();
|
|
5838
|
-
const restContent = v2Match[2];
|
|
5839
|
-
const senderMatch = restContent.match(SENDER_CONTENT_REGEX);
|
|
5840
|
-
if (senderMatch && !isSystemMessage(restContent)) {
|
|
5841
|
-
currentMessage = {
|
|
5842
|
-
timestamp,
|
|
5843
|
-
sender: senderMatch[1].trim(),
|
|
5844
|
-
contentLines: [senderMatch[2]]
|
|
5845
|
-
};
|
|
5846
|
-
} else {
|
|
5847
|
-
currentMessage = {
|
|
5848
|
-
timestamp,
|
|
5849
|
-
sender: null,
|
|
5850
|
-
contentLines: [restContent]
|
|
5851
|
-
};
|
|
5852
|
-
}
|
|
5853
|
-
if (messagesProcessed % 500 === 0) {
|
|
5854
|
-
onProgress?.(
|
|
5855
|
-
createProgress("parsing", bytesRead, totalBytes, messagesProcessed, `\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`)
|
|
5856
|
-
);
|
|
5857
|
-
}
|
|
5858
|
-
continue;
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
if (currentMessage && cleanedLine) {
|
|
5862
|
-
currentMessage.contentLines.push(cleanedLine);
|
|
5863
|
-
} else if (cleanedLine) {
|
|
5864
|
-
skippedLines++;
|
|
5774
|
+
for await (const line of lines) {
|
|
5775
|
+
accumulator.pushLine(line);
|
|
5776
|
+
if (accumulator.messageCount >= lastReportedMessages + 500) {
|
|
5777
|
+
lastReportedMessages = accumulator.messageCount;
|
|
5778
|
+
onProgress?.(
|
|
5779
|
+
createProgress(
|
|
5780
|
+
"parsing",
|
|
5781
|
+
bytesRead,
|
|
5782
|
+
totalBytes,
|
|
5783
|
+
accumulator.messageCount,
|
|
5784
|
+
`Processed ${accumulator.messageCount} messages`
|
|
5785
|
+
)
|
|
5786
|
+
);
|
|
5865
5787
|
}
|
|
5866
5788
|
}
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
const meta = {
|
|
5873
|
-
name: chatName,
|
|
5874
|
-
platform: KNOWN_PLATFORMS.WHATSAPP,
|
|
5875
|
-
type: chatType
|
|
5876
|
-
};
|
|
5877
|
-
yield { type: "meta", data: meta };
|
|
5878
|
-
const members = Array.from(memberMap.values()).map((m) => ({
|
|
5879
|
-
platformId: m.platformId,
|
|
5880
|
-
accountName: m.nickname
|
|
5881
|
-
}));
|
|
5882
|
-
yield { type: "members", data: members };
|
|
5883
|
-
for (let i = 0; i < messages.length; i += batchSize) {
|
|
5884
|
-
const batch = messages.slice(i, i + batchSize);
|
|
5885
|
-
yield { type: "messages", data: batch };
|
|
5789
|
+
const result = accumulator.finish();
|
|
5790
|
+
yield { type: "meta", data: result.meta };
|
|
5791
|
+
yield { type: "members", data: result.members };
|
|
5792
|
+
for (let index = 0; index < result.messages.length; index += batchSize) {
|
|
5793
|
+
yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
|
|
5886
5794
|
}
|
|
5887
|
-
const doneProgress = createProgress("done", totalBytes, totalBytes,
|
|
5795
|
+
const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
|
|
5888
5796
|
yield { type: "progress", data: doneProgress };
|
|
5889
5797
|
onProgress?.(doneProgress);
|
|
5890
5798
|
const typeCounts = /* @__PURE__ */ new Map();
|
|
5891
|
-
for (const
|
|
5892
|
-
typeCounts.set(
|
|
5799
|
+
for (const message of result.messages) {
|
|
5800
|
+
typeCounts.set(message.type, (typeCounts.get(message.type) ?? 0) + 1);
|
|
5893
5801
|
}
|
|
5894
|
-
onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberMap.size} \u4E2A\u6210\u5458, \u7C7B\u578B: ${chatType}`);
|
|
5895
5802
|
onLog?.(
|
|
5896
5803
|
"info",
|
|
5897
|
-
|
|
5804
|
+
`WhatsApp TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members, type ${result.meta.type}`
|
|
5898
5805
|
);
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5806
|
+
onLog?.(
|
|
5807
|
+
"info",
|
|
5808
|
+
`Message types: ${Array.from(typeCounts.entries(), ([type, count]) => `${type}=${count}`).join(", ")}`
|
|
5809
|
+
);
|
|
5810
|
+
if (result.skippedLines > 0) onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines`);
|
|
5902
5811
|
yield {
|
|
5903
5812
|
type: "done",
|
|
5904
|
-
data: { messageCount:
|
|
5813
|
+
data: { messageCount: result.messages.length, memberCount: result.members.length }
|
|
5905
5814
|
};
|
|
5906
5815
|
}
|
|
5907
5816
|
var parser_9 = {
|
|
@@ -5911,32 +5820,110 @@ var parser_9 = {
|
|
|
5911
5820
|
var module_10 = {
|
|
5912
5821
|
feature: feature10,
|
|
5913
5822
|
parser: parser_9
|
|
5914
|
-
// TXT 格式不需要预处理器
|
|
5915
5823
|
};
|
|
5916
5824
|
var whatsapp_native_txt_default = module_10;
|
|
5917
5825
|
|
|
5918
5826
|
// ../../packages/parser/src/formats/qq-native-txt.ts
|
|
5919
5827
|
import * as fs14 from "fs";
|
|
5920
|
-
import * as path10 from "path";
|
|
5921
5828
|
import * as readline4 from "readline";
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
return name || "\u672A\u77E5\u7FA4\u804A";
|
|
5926
|
-
}
|
|
5927
|
-
var feature11 = {
|
|
5928
|
-
id: "qq-native-txt",
|
|
5929
|
-
name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
5930
|
-
platform: KNOWN_PLATFORMS.QQ,
|
|
5931
|
-
priority: 30,
|
|
5932
|
-
extensions: [".txt"],
|
|
5933
|
-
signatures: {
|
|
5934
|
-
// 支持群聊导出和多人聊天(讨论组)导出
|
|
5935
|
-
head: [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/]
|
|
5936
|
-
}
|
|
5937
|
-
};
|
|
5829
|
+
|
|
5830
|
+
// ../../packages/parser/src/browser/qq.ts
|
|
5831
|
+
var QQ_HEAD_SIGNATURES = [/消息记录(此消息记录为文本格式/, /消息对象:/, /多人聊天/];
|
|
5938
5832
|
var MESSAGE_HEADER_REGEX = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (.+?)(?:\(([^)]+)\)|<([^>]+)>)?$/;
|
|
5939
5833
|
var GROUP_NAME_REGEX = /^消息对象:(.+)$/;
|
|
5834
|
+
var QqTextAccumulator = class {
|
|
5835
|
+
fallbackName;
|
|
5836
|
+
memberMap = /* @__PURE__ */ new Map();
|
|
5837
|
+
lastValidNickname = /* @__PURE__ */ new Map();
|
|
5838
|
+
messages = [];
|
|
5839
|
+
groupName = "\u672A\u77E5\u7FA4\u804A";
|
|
5840
|
+
currentMessage = null;
|
|
5841
|
+
skippedLines = 0;
|
|
5842
|
+
constructor(fileName) {
|
|
5843
|
+
this.fallbackName = extractNameFromFileName2(fileName);
|
|
5844
|
+
}
|
|
5845
|
+
get messageCount() {
|
|
5846
|
+
return this.messages.length + (this.currentMessage ? 1 : 0);
|
|
5847
|
+
}
|
|
5848
|
+
pushLine(line) {
|
|
5849
|
+
const groupMatch = line.match(GROUP_NAME_REGEX);
|
|
5850
|
+
if (groupMatch) {
|
|
5851
|
+
this.groupName = groupMatch[1].trim();
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
const headerMatch = line.match(MESSAGE_HEADER_REGEX);
|
|
5855
|
+
if (headerMatch) {
|
|
5856
|
+
this.saveCurrentMessage();
|
|
5857
|
+
const platformId = headerMatch[3] || headerMatch[4] || cleanNickname(headerMatch[2].trim());
|
|
5858
|
+
let nickname = cleanNickname(headerMatch[2].trim());
|
|
5859
|
+
if (nickname === platformId && headerMatch[3]) {
|
|
5860
|
+
nickname = this.lastValidNickname.get(platformId) ?? nickname;
|
|
5861
|
+
} else if (headerMatch[3] || headerMatch[4]) {
|
|
5862
|
+
this.lastValidNickname.set(platformId, nickname);
|
|
5863
|
+
}
|
|
5864
|
+
this.currentMessage = {
|
|
5865
|
+
timestamp: parseLocalTime(headerMatch[1]),
|
|
5866
|
+
platformId,
|
|
5867
|
+
nickname,
|
|
5868
|
+
contentLines: []
|
|
5869
|
+
};
|
|
5870
|
+
return;
|
|
5871
|
+
}
|
|
5872
|
+
if (this.currentMessage) {
|
|
5873
|
+
if (line.startsWith("=====") || line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) return;
|
|
5874
|
+
this.currentMessage.contentLines.push(line);
|
|
5875
|
+
return;
|
|
5876
|
+
}
|
|
5877
|
+
const trimmed = line.trim();
|
|
5878
|
+
if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
|
|
5879
|
+
this.skippedLines += 1;
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
finish() {
|
|
5883
|
+
this.saveCurrentMessage();
|
|
5884
|
+
return {
|
|
5885
|
+
meta: {
|
|
5886
|
+
name: this.groupName === "\u672A\u77E5\u7FA4\u804A" ? this.fallbackName : this.groupName,
|
|
5887
|
+
platform: KNOWN_PLATFORMS.QQ,
|
|
5888
|
+
type: "group" /* GROUP */
|
|
5889
|
+
},
|
|
5890
|
+
members: Array.from(this.memberMap.values(), (member) => ({
|
|
5891
|
+
platformId: member.platformId,
|
|
5892
|
+
accountName: member.nickname
|
|
5893
|
+
})),
|
|
5894
|
+
messages: this.messages,
|
|
5895
|
+
skippedLines: this.skippedLines
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5898
|
+
saveCurrentMessage() {
|
|
5899
|
+
if (!this.currentMessage) return;
|
|
5900
|
+
const content = this.currentMessage.contentLines.join("\n").trim();
|
|
5901
|
+
this.messages.push({
|
|
5902
|
+
senderPlatformId: this.currentMessage.platformId,
|
|
5903
|
+
senderAccountName: this.currentMessage.nickname,
|
|
5904
|
+
timestamp: this.currentMessage.timestamp,
|
|
5905
|
+
type: detectMessageType3(content),
|
|
5906
|
+
content: content || null
|
|
5907
|
+
});
|
|
5908
|
+
this.memberMap.set(this.currentMessage.platformId, {
|
|
5909
|
+
platformId: this.currentMessage.platformId,
|
|
5910
|
+
nickname: this.currentMessage.nickname
|
|
5911
|
+
});
|
|
5912
|
+
this.currentMessage = null;
|
|
5913
|
+
}
|
|
5914
|
+
};
|
|
5915
|
+
function extractNameFromFileName2(fileName) {
|
|
5916
|
+
return getBasename2(fileName).replace(/\.txt$/i, "") || "\u672A\u77E5\u7FA4\u804A";
|
|
5917
|
+
}
|
|
5918
|
+
function getBasename2(fileName) {
|
|
5919
|
+
return fileName.split(/[\\/]/).pop() ?? fileName;
|
|
5920
|
+
}
|
|
5921
|
+
function cleanNickname(nickname) {
|
|
5922
|
+
return nickname.replace(/^(【[^】]*】\s*)+/, "").trim();
|
|
5923
|
+
}
|
|
5924
|
+
function parseLocalTime(timeText) {
|
|
5925
|
+
return Math.floor(new Date(timeText.replace(" ", "T")).getTime() / 1e3);
|
|
5926
|
+
}
|
|
5940
5927
|
function detectMessageType3(content) {
|
|
5941
5928
|
const trimmed = content.trim();
|
|
5942
5929
|
if (trimmed === "[\u56FE\u7247]") return 1 /* IMAGE */;
|
|
@@ -5949,7 +5936,9 @@ function detectMessageType3(content) {
|
|
|
5949
5936
|
if (trimmed === "[\u7EA2\u5305]" || trimmed.includes("\u53D1\u51FA\u4E86\u7EA2\u5305")) return 20 /* RED_PACKET */;
|
|
5950
5937
|
if (trimmed === "[\u8F6C\u8D26]" || trimmed.includes("\u5411\u4F60\u8F6C\u8D26")) return 21 /* TRANSFER */;
|
|
5951
5938
|
if (trimmed.includes("\u62CD\u4E86\u62CD") || trimmed === "[\u62CD\u4E00\u62CD]") return 22 /* POKE */;
|
|
5952
|
-
if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F"))
|
|
5939
|
+
if (trimmed === "[\u8BED\u97F3\u901A\u8BDD]" || trimmed === "[\u89C6\u9891\u901A\u8BDD]" || trimmed.includes("\u901A\u8BDD\u65F6\u957F")) {
|
|
5940
|
+
return 23 /* CALL */;
|
|
5941
|
+
}
|
|
5953
5942
|
if (trimmed === "[\u5206\u4EAB]" || trimmed === "[\u97F3\u4E50]" || trimmed === "[\u5C0F\u7A0B\u5E8F]") return 24 /* SHARE */;
|
|
5954
5943
|
if (trimmed.startsWith("[\u56DE\u590D]")) return 25 /* REPLY */;
|
|
5955
5944
|
if (trimmed === "[\u8F6C\u53D1]" || trimmed === "[\u804A\u5929\u8BB0\u5F55]") return 26 /* FORWARD */;
|
|
@@ -5960,150 +5949,64 @@ function detectMessageType3(content) {
|
|
|
5960
5949
|
if (trimmed.startsWith("[") && trimmed.endsWith("]")) return 99 /* OTHER */;
|
|
5961
5950
|
return 0 /* TEXT */;
|
|
5962
5951
|
}
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5952
|
+
|
|
5953
|
+
// ../../packages/parser/src/formats/qq-native-txt.ts
|
|
5954
|
+
var feature11 = {
|
|
5955
|
+
id: "qq-native-txt",
|
|
5956
|
+
name: "QQ \u5B98\u65B9\u5BFC\u51FA (TXT)",
|
|
5957
|
+
platform: KNOWN_PLATFORMS.QQ,
|
|
5958
|
+
priority: 30,
|
|
5959
|
+
extensions: [".txt"],
|
|
5960
|
+
signatures: { head: QQ_HEAD_SIGNATURES }
|
|
5961
|
+
};
|
|
5962
|
+
async function* parseQq(options2) {
|
|
5972
5963
|
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
5973
5964
|
const totalBytes = getFileSize(filePath);
|
|
5974
5965
|
let bytesRead = 0;
|
|
5975
|
-
let messagesProcessed = 0;
|
|
5976
|
-
let skippedLines = 0;
|
|
5977
5966
|
const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
|
|
5978
5967
|
yield { type: "progress", data: initialProgress };
|
|
5979
5968
|
onProgress?.(initialProgress);
|
|
5980
|
-
onLog?.("info",
|
|
5981
|
-
|
|
5982
|
-
const memberMap = /* @__PURE__ */ new Map();
|
|
5983
|
-
const messages = [];
|
|
5984
|
-
let currentMessage = null;
|
|
5985
|
-
const saveCurrentMessage = () => {
|
|
5986
|
-
if (currentMessage) {
|
|
5987
|
-
const content = currentMessage.contentLines.join("\n").trim();
|
|
5988
|
-
const type = detectMessageType3(content);
|
|
5989
|
-
messages.push({
|
|
5990
|
-
senderPlatformId: currentMessage.platformId,
|
|
5991
|
-
senderAccountName: currentMessage.nickname,
|
|
5992
|
-
// QQ TXT 格式只有一个昵称,作为账号名称追踪历史
|
|
5993
|
-
// 不设置 senderGroupNickname,避免同一昵称被重复追踪
|
|
5994
|
-
timestamp: currentMessage.timestamp,
|
|
5995
|
-
type,
|
|
5996
|
-
content: content || null
|
|
5997
|
-
});
|
|
5998
|
-
memberMap.set(currentMessage.platformId, {
|
|
5999
|
-
platformId: currentMessage.platformId,
|
|
6000
|
-
nickname: currentMessage.nickname
|
|
6001
|
-
});
|
|
6002
|
-
messagesProcessed++;
|
|
6003
|
-
}
|
|
6004
|
-
};
|
|
5969
|
+
onLog?.("info", `Starting QQ TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
|
|
5970
|
+
const accumulator = new QqTextAccumulator(filePath);
|
|
6005
5971
|
const fileStream = fs14.createReadStream(filePath, { encoding: "utf-8" });
|
|
6006
|
-
const
|
|
6007
|
-
input: fileStream,
|
|
6008
|
-
crlfDelay: Infinity
|
|
6009
|
-
});
|
|
5972
|
+
const lines = readline4.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
6010
5973
|
fileStream.on("data", (chunk) => {
|
|
6011
5974
|
bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
6012
5975
|
});
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
nickname = previousNickname;
|
|
6030
|
-
}
|
|
6031
|
-
} else if (headerMatch[3] || headerMatch[4]) {
|
|
6032
|
-
lastValidNickname.set(platformId, nickname);
|
|
6033
|
-
}
|
|
6034
|
-
currentMessage = {
|
|
6035
|
-
timestamp: parseLocalTime(timeStr),
|
|
6036
|
-
platformId,
|
|
6037
|
-
nickname,
|
|
6038
|
-
contentLines: []
|
|
6039
|
-
};
|
|
6040
|
-
if (messagesProcessed % 1e3 === 0) {
|
|
6041
|
-
const progress = createProgress(
|
|
6042
|
-
"parsing",
|
|
6043
|
-
bytesRead,
|
|
6044
|
-
totalBytes,
|
|
6045
|
-
messagesProcessed,
|
|
6046
|
-
`\u5DF2\u5904\u7406 ${messagesProcessed} \u6761\u6D88\u606F...`
|
|
6047
|
-
);
|
|
6048
|
-
onProgress?.(progress);
|
|
6049
|
-
}
|
|
6050
|
-
continue;
|
|
6051
|
-
}
|
|
6052
|
-
if (currentMessage) {
|
|
6053
|
-
if (line.startsWith("=====")) continue;
|
|
6054
|
-
if (line.startsWith("\u6D88\u606F\u8BB0\u5F55") || line.startsWith("\u6D88\u606F\u5206\u7EC4")) continue;
|
|
6055
|
-
currentMessage.contentLines.push(line);
|
|
6056
|
-
} else {
|
|
6057
|
-
const trimmed = line.trim();
|
|
6058
|
-
if (trimmed && !trimmed.startsWith("=====") && !trimmed.startsWith("\u6D88\u606F\u8BB0\u5F55") && !trimmed.startsWith("\u6D88\u606F\u5206\u7EC4")) {
|
|
6059
|
-
skippedLines++;
|
|
6060
|
-
}
|
|
6061
|
-
}
|
|
6062
|
-
}
|
|
6063
|
-
saveCurrentMessage();
|
|
6064
|
-
const meta = {
|
|
6065
|
-
name: groupName === "\u672A\u77E5\u7FA4\u804A" ? extractNameFromFilePath6(filePath) : groupName,
|
|
6066
|
-
platform: KNOWN_PLATFORMS.QQ,
|
|
6067
|
-
type: "group" /* GROUP */
|
|
6068
|
-
};
|
|
6069
|
-
yield { type: "meta", data: meta };
|
|
6070
|
-
const members = Array.from(memberMap.values()).map((m) => ({
|
|
6071
|
-
platformId: m.platformId,
|
|
6072
|
-
accountName: m.nickname
|
|
6073
|
-
// QQ TXT 格式只有昵称,作为账号名称
|
|
6074
|
-
// 不设置 groupNickname,避免同一昵称被重复追踪
|
|
6075
|
-
}));
|
|
6076
|
-
yield { type: "members", data: members };
|
|
6077
|
-
for (let i = 0; i < messages.length; i += batchSize) {
|
|
6078
|
-
const batch = messages.slice(i, i + batchSize);
|
|
6079
|
-
yield { type: "messages", data: batch };
|
|
6080
|
-
}
|
|
6081
|
-
const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
|
|
5976
|
+
let lastReportedCount = -1;
|
|
5977
|
+
for await (const line of lines) {
|
|
5978
|
+
accumulator.pushLine(line);
|
|
5979
|
+
const messageCount = accumulator.messageCount;
|
|
5980
|
+
if (messageCount > 0 && messageCount % 1e3 === 0 && messageCount !== lastReportedCount) {
|
|
5981
|
+
lastReportedCount = messageCount;
|
|
5982
|
+
onProgress?.(createProgress("parsing", bytesRead, totalBytes, messageCount, `Processed ${messageCount} messages`));
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
const result = accumulator.finish();
|
|
5986
|
+
yield { type: "meta", data: result.meta };
|
|
5987
|
+
yield { type: "members", data: result.members };
|
|
5988
|
+
for (let index = 0; index < result.messages.length; index += batchSize) {
|
|
5989
|
+
yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
|
|
5990
|
+
}
|
|
5991
|
+
const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
|
|
6082
5992
|
yield { type: "progress", data: doneProgress };
|
|
6083
5993
|
onProgress?.(doneProgress);
|
|
6084
|
-
onLog?.("info",
|
|
6085
|
-
if (skippedLines > 0) {
|
|
6086
|
-
onLog?.("info",
|
|
5994
|
+
onLog?.("info", `QQ TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
|
|
5995
|
+
if (result.skippedLines > 0) {
|
|
5996
|
+
onLog?.("info", `Skipped ${result.skippedLines} unrecognized lines while parsing QQ TXT`);
|
|
6087
5997
|
}
|
|
6088
5998
|
yield {
|
|
6089
5999
|
type: "done",
|
|
6090
|
-
data: { messageCount:
|
|
6000
|
+
data: { messageCount: result.messages.length, memberCount: result.members.length }
|
|
6091
6001
|
};
|
|
6092
6002
|
}
|
|
6093
|
-
var parser_10 = {
|
|
6094
|
-
|
|
6095
|
-
parse: parseTxt
|
|
6096
|
-
};
|
|
6097
|
-
var module_11 = {
|
|
6098
|
-
feature: feature11,
|
|
6099
|
-
parser: parser_10
|
|
6100
|
-
// TXT 格式不需要预处理器
|
|
6101
|
-
};
|
|
6003
|
+
var parser_10 = { feature: feature11, parse: parseQq };
|
|
6004
|
+
var module_11 = { feature: feature11, parser: parser_10 };
|
|
6102
6005
|
var qq_native_txt_default = module_11;
|
|
6103
6006
|
|
|
6104
6007
|
// ../../packages/parser/src/formats/instagram-native.ts
|
|
6105
6008
|
import * as fs15 from "fs";
|
|
6106
|
-
import * as
|
|
6009
|
+
import * as path9 from "path";
|
|
6107
6010
|
var feature12 = {
|
|
6108
6011
|
id: "instagram-native",
|
|
6109
6012
|
name: "Instagram \u5B98\u65B9\u5BFC\u51FA",
|
|
@@ -6128,9 +6031,9 @@ function decodeInstagramText(text) {
|
|
|
6128
6031
|
return text;
|
|
6129
6032
|
}
|
|
6130
6033
|
}
|
|
6131
|
-
function
|
|
6132
|
-
const
|
|
6133
|
-
return
|
|
6034
|
+
function extractNameFromFilePath5(filePath) {
|
|
6035
|
+
const basename10 = path9.basename(filePath);
|
|
6036
|
+
return basename10.replace(/\.json$/i, "") || "\u672A\u77E5\u5BF9\u8BDD";
|
|
6134
6037
|
}
|
|
6135
6038
|
function isSystemMessage2(content) {
|
|
6136
6039
|
const systemPatterns = [
|
|
@@ -6216,7 +6119,7 @@ async function* parseInstagram(options2) {
|
|
|
6216
6119
|
ownerId = createMsg ? decodeInstagramText(createMsg.sender_name) : void 0;
|
|
6217
6120
|
}
|
|
6218
6121
|
const meta = {
|
|
6219
|
-
name: decodeInstagramText(data.title) ||
|
|
6122
|
+
name: decodeInstagramText(data.title) || extractNameFromFilePath5(filePath),
|
|
6220
6123
|
platform: KNOWN_PLATFORMS.INSTAGRAM,
|
|
6221
6124
|
type: chatType,
|
|
6222
6125
|
ownerId
|
|
@@ -6289,16 +6192,16 @@ var module_12 = {
|
|
|
6289
6192
|
var instagram_native_default = module_12;
|
|
6290
6193
|
|
|
6291
6194
|
// ../../packages/parser/src/formats/google-chat-takeout.ts
|
|
6292
|
-
var
|
|
6195
|
+
var import_stream_chain7 = __toESM(require_stream_chain(), 1);
|
|
6293
6196
|
var import_stream_json8 = __toESM(require_stream_json(), 1);
|
|
6294
6197
|
var import_Pick7 = __toESM(require_Pick(), 1);
|
|
6295
|
-
var
|
|
6198
|
+
var import_StreamValues7 = __toESM(require_StreamValues(), 1);
|
|
6296
6199
|
import * as fs16 from "fs";
|
|
6297
|
-
import * as
|
|
6298
|
-
var { chain:
|
|
6200
|
+
import * as path10 from "path";
|
|
6201
|
+
var { chain: chain7 } = import_stream_chain7.default;
|
|
6299
6202
|
var { parser: parser9 } = import_stream_json8.default;
|
|
6300
6203
|
var { pick: pick7 } = import_Pick7.default;
|
|
6301
|
-
var { streamValues:
|
|
6204
|
+
var { streamValues: streamValues7 } = import_StreamValues7.default;
|
|
6302
6205
|
var ENGLISH_MONTHS = /* @__PURE__ */ new Map([
|
|
6303
6206
|
["january", 0],
|
|
6304
6207
|
["february", 1],
|
|
@@ -6380,9 +6283,9 @@ function readJson(filePath) {
|
|
|
6380
6283
|
return JSON.parse(fs16.readFileSync(filePath, "utf8"));
|
|
6381
6284
|
}
|
|
6382
6285
|
function resolveManifestFile(manifestDir, relativePath) {
|
|
6383
|
-
const resolved =
|
|
6384
|
-
const relative2 =
|
|
6385
|
-
if (relative2.startsWith("..") ||
|
|
6286
|
+
const resolved = path10.resolve(manifestDir, relativePath);
|
|
6287
|
+
const relative2 = path10.relative(manifestDir, resolved);
|
|
6288
|
+
if (relative2.startsWith("..") || path10.isAbsolute(relative2)) {
|
|
6386
6289
|
throw new Error(`Google Chat manifest path escapes its directory: ${relativePath}`);
|
|
6387
6290
|
}
|
|
6388
6291
|
return resolved;
|
|
@@ -6395,7 +6298,7 @@ function readManifest2(filePath) {
|
|
|
6395
6298
|
if (manifest.chatType !== "private" && manifest.chatType !== "group") {
|
|
6396
6299
|
throw new Error("Invalid Google Chat chat type");
|
|
6397
6300
|
}
|
|
6398
|
-
const manifestDir =
|
|
6301
|
+
const manifestDir = path10.dirname(filePath);
|
|
6399
6302
|
return {
|
|
6400
6303
|
manifest,
|
|
6401
6304
|
userInfoPath: resolveManifestFile(manifestDir, manifest.userInfoFile),
|
|
@@ -6479,7 +6382,7 @@ async function* parseGoogleChat(options2) {
|
|
|
6479
6382
|
readStream.on("data", (chunk) => {
|
|
6480
6383
|
bytesRead += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
|
|
6481
6384
|
});
|
|
6482
|
-
const pipeline2 =
|
|
6385
|
+
const pipeline2 = chain7([readStream, parser9(), pick7({ filter: /^messages\.\d+$/ }), streamValues7()]);
|
|
6483
6386
|
const messageBatch = [];
|
|
6484
6387
|
try {
|
|
6485
6388
|
for await (const item of pipeline2) {
|
|
@@ -6527,456 +6430,700 @@ var google_chat_takeout_default = module_13;
|
|
|
6527
6430
|
|
|
6528
6431
|
// ../../packages/parser/src/formats/line-native-txt.ts
|
|
6529
6432
|
import * as fs17 from "fs";
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
// Tab 分隔的消息格式(支持多种时间格式)
|
|
6543
|
-
/^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
|
|
6544
|
-
// 空格分隔的消息格式(部分 LINE 导出)
|
|
6545
|
-
/^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
|
|
6546
|
-
// LINE 独有的日期行格式:YYYY.MM.DD DayOfWeek(英文星期全称)
|
|
6547
|
-
/^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
|
|
6548
|
-
// LINE 日文/中文日期行格式:YYYY/M/D(曜日)
|
|
6549
|
-
/^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
|
|
6550
|
-
// LINE 中文日期行格式:YYYY/M/D周X
|
|
6551
|
-
/^\d{4}\/\d{1,2}\/\d{1,2}周/m
|
|
6552
|
-
],
|
|
6553
|
-
// 文件名特征:[LINE] 出现在文件名中
|
|
6554
|
-
filename: [/\[LINE\]/i]
|
|
6555
|
-
}
|
|
6556
|
-
};
|
|
6557
|
-
function extractNameFromFilePath8(filePath) {
|
|
6558
|
-
const basename13 = path13.basename(filePath, ".txt");
|
|
6559
|
-
const name = basename13.replace(/^\[LINE\]\s*/i, "").trim();
|
|
6560
|
-
return name || "\u672A\u77E5\u804A\u5929";
|
|
6561
|
-
}
|
|
6562
|
-
function extractNameFromHeader(header) {
|
|
6563
|
-
const enPrivateMatch = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
|
|
6564
|
-
if (enPrivateMatch) return { name: enPrivateMatch[1].trim(), isGroup: false };
|
|
6565
|
-
const enGroupMatch = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
|
|
6566
|
-
if (enGroupMatch) return { name: enGroupMatch[1].trim(), isGroup: true };
|
|
6567
|
-
const jaPrivateMatch = header.match(/^\[LINE\] (.+)とのトーク履歴/);
|
|
6568
|
-
if (jaPrivateMatch) return { name: jaPrivateMatch[1].trim(), isGroup: false };
|
|
6569
|
-
const jaGroupMatch = header.match(/^\[LINE\] (.+)のトーク履歴/);
|
|
6570
|
-
if (jaGroupMatch) return { name: jaGroupMatch[1].trim(), isGroup: true };
|
|
6571
|
-
const zhCnPrivateMatch = header.match(/^\[LINE\] 与(.+)的聊天记录/);
|
|
6572
|
-
if (zhCnPrivateMatch) return { name: zhCnPrivateMatch[1].trim(), isGroup: false };
|
|
6573
|
-
const zhCnGroupMatch = header.match(/^\[LINE\] (.+)的聊天记录/);
|
|
6574
|
-
if (zhCnGroupMatch) return { name: zhCnGroupMatch[1].trim(), isGroup: true };
|
|
6575
|
-
const zhTwPrivateMatch = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
|
|
6576
|
-
if (zhTwPrivateMatch) return { name: zhTwPrivateMatch[1].trim(), isGroup: false };
|
|
6577
|
-
const zhTwGroupMatch = header.match(/^\[LINE\] (.+)的聊天記錄/);
|
|
6578
|
-
if (zhTwGroupMatch) return { name: zhTwGroupMatch[1].trim(), isGroup: true };
|
|
6579
|
-
return null;
|
|
6580
|
-
}
|
|
6433
|
+
|
|
6434
|
+
// ../../packages/parser/src/browser/line.ts
|
|
6435
|
+
var LINE_HEAD_SIGNATURES = [
|
|
6436
|
+
/^\[LINE\] /m,
|
|
6437
|
+
/^(?:\[LINE\] )?Chat history (?:with|in) /m,
|
|
6438
|
+
/^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t[^\t\n]+\t/m,
|
|
6439
|
+
/^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) [^\s]+ /m,
|
|
6440
|
+
/^\d{4}\.\d{2}\.\d{2}\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/m,
|
|
6441
|
+
/^\d{4}\/\d{1,2}\/\d{1,2}[((][月火水木金土日]/m,
|
|
6442
|
+
/^\d{4}\/\d{1,2}\/\d{1,2}周/m
|
|
6443
|
+
];
|
|
6444
|
+
var LINE_FILENAME_SIGNATURES = [/\[LINE\]/i];
|
|
6581
6445
|
var DATE_PATTERNS = [
|
|
6582
|
-
// 2025.12.10 Wednesday
|
|
6583
6446
|
/^(\d{4})\.(\d{2})\.(\d{2})\s+(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)?/,
|
|
6584
|
-
// 2026/1/30周五 or 2026/1/30(金)
|
|
6585
6447
|
/^(\d{4})\/(\d{1,2})\/(\d{1,2})/,
|
|
6586
|
-
// Fri, 1/30/2026
|
|
6587
6448
|
/^[A-Za-z]+,\s*(\d{1,2})\/(\d{1,2})\/(\d{4})/
|
|
6588
6449
|
];
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
if (match) {
|
|
6593
|
-
if (pattern === DATE_PATTERNS[0] || pattern === DATE_PATTERNS[1]) {
|
|
6594
|
-
const year = parseInt(match[1]);
|
|
6595
|
-
const month = parseInt(match[2]) - 1;
|
|
6596
|
-
const day = parseInt(match[3]);
|
|
6597
|
-
return new Date(year, month, day);
|
|
6598
|
-
} else if (pattern === DATE_PATTERNS[2]) {
|
|
6599
|
-
const month = parseInt(match[1]) - 1;
|
|
6600
|
-
const day = parseInt(match[2]);
|
|
6601
|
-
const year = parseInt(match[3]);
|
|
6602
|
-
return new Date(year, month, day);
|
|
6603
|
-
}
|
|
6604
|
-
}
|
|
6605
|
-
}
|
|
6606
|
-
return null;
|
|
6607
|
-
}
|
|
6608
|
-
var PRIVATE_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
|
|
6609
|
-
var GROUP_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
|
|
6610
|
-
var SYSTEM_MSG_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
|
|
6450
|
+
var TAB_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t([^\t]+)\t(.*)$/;
|
|
6451
|
+
var SPACE_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?) ([^\s]+) (.*)$/;
|
|
6452
|
+
var SYSTEM_MESSAGE_PATTERN = /^((?:上午|下午|午前|午後)?\d{1,2}:\d{2}(?:[AaPp][Mm])?)\t\t(.+)$/;
|
|
6611
6453
|
var SPECIAL_MESSAGE_TYPES = {
|
|
6612
|
-
// 图片 / Photo
|
|
6613
6454
|
"[Photo]": 1 /* IMAGE */,
|
|
6614
|
-
// EN
|
|
6615
6455
|
"[\u7167\u7247]": 1 /* IMAGE */,
|
|
6616
|
-
// ZH-CN / ZH-TW
|
|
6617
6456
|
"[\u5199\u771F]": 1 /* IMAGE */,
|
|
6618
|
-
// JA
|
|
6619
6457
|
Photos: 1 /* IMAGE */,
|
|
6620
|
-
// EN (fallback)
|
|
6621
|
-
// 语音 / Voice
|
|
6622
6458
|
"[Voice message]": 2 /* VOICE */,
|
|
6623
|
-
// EN
|
|
6624
6459
|
"[\u8BED\u97F3\u4FE1\u606F]": 2 /* VOICE */,
|
|
6625
|
-
// ZH-CN
|
|
6626
6460
|
"[\u8A9E\u97F3\u8A0A\u606F]": 2 /* VOICE */,
|
|
6627
|
-
// ZH-TW
|
|
6628
6461
|
"[\u30DC\u30A4\u30B9\u30E1\u30C3\u30BB\u30FC\u30B8]": 2 /* VOICE */,
|
|
6629
|
-
// JA
|
|
6630
6462
|
Audio: 2 /* VOICE */,
|
|
6631
|
-
// EN (fallback)
|
|
6632
|
-
// 视频 / Video
|
|
6633
6463
|
"[Video]": 3 /* VIDEO */,
|
|
6634
|
-
// EN
|
|
6635
6464
|
"[\u89C6\u9891]": 3 /* VIDEO */,
|
|
6636
|
-
// ZH-CN
|
|
6637
6465
|
"[\u5F71\u7247]": 3 /* VIDEO */,
|
|
6638
|
-
// ZH-TW
|
|
6639
6466
|
"[\u52D5\u753B]": 3 /* VIDEO */,
|
|
6640
|
-
// JA
|
|
6641
6467
|
Videos: 3 /* VIDEO */,
|
|
6642
|
-
// EN (fallback)
|
|
6643
|
-
// 文件 / File
|
|
6644
6468
|
"[File]": 4 /* FILE */,
|
|
6645
|
-
// EN
|
|
6646
6469
|
"[\u6587\u4EF6]": 4 /* FILE */,
|
|
6647
|
-
// ZH-CN
|
|
6648
6470
|
"[\u6A94\u6848]": 4 /* FILE */,
|
|
6649
|
-
// ZH-TW
|
|
6650
6471
|
"[\u30D5\u30A1\u30A4\u30EB]": 4 /* FILE */,
|
|
6651
|
-
// JA
|
|
6652
|
-
// 贴纸 / Sticker
|
|
6653
6472
|
"[Sticker]": 5 /* EMOJI */,
|
|
6654
|
-
// EN
|
|
6655
6473
|
"[\u8D34\u56FE]": 5 /* EMOJI */,
|
|
6656
|
-
// ZH-CN
|
|
6657
6474
|
"[\u8CBC\u5716]": 5 /* EMOJI */,
|
|
6658
|
-
// ZH-TW
|
|
6659
6475
|
"[\u30B9\u30BF\u30F3\u30D7]": 5 /* EMOJI */,
|
|
6660
|
-
// JA
|
|
6661
6476
|
Stickers: 5 /* EMOJI */,
|
|
6662
|
-
// EN (fallback)
|
|
6663
|
-
// 位置 / Location
|
|
6664
6477
|
"[Location]": 8 /* LOCATION */,
|
|
6665
|
-
// EN
|
|
6666
6478
|
"[\u4F4D\u7F6E]": 8 /* LOCATION */,
|
|
6667
|
-
// ZH-CN / ZH-TW
|
|
6668
6479
|
"[\u4F4D\u7F6E\u60C5\u5831]": 8 /* LOCATION */,
|
|
6669
|
-
// JA
|
|
6670
|
-
// 记事本 / Notes
|
|
6671
6480
|
"[Notes]": 0 /* TEXT */,
|
|
6672
|
-
// EN
|
|
6673
6481
|
"[\u8BB0\u4E8B\u672C]": 0 /* TEXT */,
|
|
6674
|
-
// ZH-CN
|
|
6675
6482
|
"[\u8A18\u4E8B\u672C]": 0 /* TEXT */,
|
|
6676
|
-
// ZH-TW
|
|
6677
6483
|
"[\u30CE\u30FC\u30C8]": 0 /* TEXT */
|
|
6678
|
-
// JA
|
|
6679
6484
|
};
|
|
6680
|
-
function
|
|
6681
|
-
for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
|
|
6682
|
-
if (content === pattern || content.startsWith(pattern)) {
|
|
6683
|
-
return type;
|
|
6684
|
-
}
|
|
6685
|
-
}
|
|
6686
|
-
if (content.startsWith("[null]") && content.includes("maps.google.com")) {
|
|
6687
|
-
return 8 /* LOCATION */;
|
|
6688
|
-
}
|
|
6689
|
-
if (
|
|
6690
|
-
// --- 加入群组 / Join group ---
|
|
6691
|
-
content.includes(" joined the group") || // EN
|
|
6692
|
-
content.includes("\u5DF2\u52A0\u5165\u8BE5\u7FA4") || // ZH-CN
|
|
6693
|
-
content.includes("\u5DF2\u52A0\u5165\u7FA4\u7D44") || // ZH-TW
|
|
6694
|
-
content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u306B\u53C2\u52A0\u3057\u307E\u3057\u305F") || // JA
|
|
6695
|
-
// --- 拉人进群 / Added to group ---
|
|
6696
|
-
content.includes(" added ") || // EN
|
|
6697
|
-
content.includes(" to the group") || // EN
|
|
6698
|
-
content.includes("\u5DF2\u5C06") || // ZH-CN
|
|
6699
|
-
content.includes("\u6DFB\u52A0\u81F3\u7FA4") || // ZH-CN
|
|
6700
|
-
content.includes("\u6DFB\u52A0\u5230\u7FA4") || // ZH-CN (另一格式)
|
|
6701
|
-
content.includes("\u5DF2\u65B0\u589E") || // ZH-TW
|
|
6702
|
-
content.includes("\u81F3\u7FA4\u7D44") || // ZH-TW
|
|
6703
|
-
content.includes("\u3092\u30B0\u30EB\u30FC\u30D7\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F") || // JA
|
|
6704
|
-
// --- 退出群组 / Left group ---
|
|
6705
|
-
content.includes(" left the group") || // EN
|
|
6706
|
-
content.includes("\u5DF2\u9000\u7FA4") || // ZH-CN
|
|
6707
|
-
content.includes("\u5DF2\u96E2\u958B\u7FA4\u7D44") || // ZH-TW
|
|
6708
|
-
content.includes("\u304C\u30B0\u30EB\u30FC\u30D7\u3092\u9000\u4F1A\u3057\u307E\u3057\u305F") || // JA
|
|
6709
|
-
// --- 设定公告 / Announcement ---
|
|
6710
|
-
content.includes("made an announcement") || // EN
|
|
6711
|
-
content.includes("\u53D1\u5E03\u4E86\u901A\u544A") || // ZH-CN
|
|
6712
|
-
content.includes("\u5DF2\u8A2D\u5B9A\u516C\u544A") || // ZH-TW
|
|
6713
|
-
content.includes("\u304C\u30A2\u30CA\u30A6\u30F3\u30B9\u3057\u307E\u3057\u305F") || // JA
|
|
6714
|
-
// --- 收回讯息 / Unsent message ---
|
|
6715
|
-
content.includes("unsent a message") || // EN
|
|
6716
|
-
content === "Message unsent." || // EN
|
|
6717
|
-
content.includes("\u64A4\u56DE\u4E86\u4E00\u6761\u6D88\u606F") || // ZH-CN
|
|
6718
|
-
content.includes("\u5DF2\u6536\u56DE\u8A0A\u606F") || // ZH-TW
|
|
6719
|
-
content.includes("\u9001\u4FE1\u3092\u53D6\u308A\u6D88\u3057\u307E\u3057\u305F") || // JA
|
|
6720
|
-
// --- 其他 / Others ---
|
|
6721
|
-
content.startsWith("Auto-reply")
|
|
6722
|
-
) {
|
|
6723
|
-
return 80 /* SYSTEM */;
|
|
6724
|
-
}
|
|
6725
|
-
if (content.match(/^https?:\/\//)) {
|
|
6726
|
-
return 7 /* LINK */;
|
|
6727
|
-
}
|
|
6728
|
-
return 0 /* TEXT */;
|
|
6729
|
-
}
|
|
6730
|
-
async function* parseLINE(options2) {
|
|
6731
|
-
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
6732
|
-
const totalBytes = getFileSize(filePath);
|
|
6733
|
-
let messagesProcessed = 0;
|
|
6734
|
-
const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
|
|
6735
|
-
yield { type: "progress", data: initialProgress };
|
|
6736
|
-
onProgress?.(initialProgress);
|
|
6737
|
-
onLog?.("info", `\u5F00\u59CB\u89E3\u6790 LINE \u5BFC\u51FA\u6587\u4EF6\uFF0C\u5927\u5C0F: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`);
|
|
6738
|
-
const content = fs17.readFileSync(filePath, "utf-8");
|
|
6485
|
+
async function parseLineText(content, fileName, options2 = {}) {
|
|
6739
6486
|
const lines = content.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
6740
|
-
let currentDate = null;
|
|
6741
|
-
let chatName = extractNameFromFilePath8(filePath);
|
|
6742
|
-
let isPrivateChat = false;
|
|
6743
|
-
let useTabSeparator = false;
|
|
6744
6487
|
const memberMap = /* @__PURE__ */ new Map();
|
|
6745
6488
|
const messages = [];
|
|
6489
|
+
const header = lines.length > 0 ? extractNameFromHeader(lines[0].trim()) : null;
|
|
6490
|
+
const chatName = header?.name ?? extractNameFromFileName3(fileName);
|
|
6491
|
+
let currentDate = null;
|
|
6746
6492
|
let lastMessage = null;
|
|
6747
|
-
let
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
if (headerResult) {
|
|
6753
|
-
chatName = headerResult.name;
|
|
6754
|
-
isPrivateChat = !headerResult.isGroup;
|
|
6755
|
-
useTabSeparator = true;
|
|
6756
|
-
lineIndex = 3;
|
|
6757
|
-
onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u5934\u90E8\uFF0C\u540D\u79F0: ${headerResult.name}, \u7FA4\u804A: ${headerResult.isGroup}`);
|
|
6758
|
-
}
|
|
6759
|
-
}
|
|
6760
|
-
if (!isPrivateChat && lines.length > 0) {
|
|
6493
|
+
let quotedMultiline = false;
|
|
6494
|
+
let lineIndex = header ? 3 : 0;
|
|
6495
|
+
let useTabSeparator = Boolean(header);
|
|
6496
|
+
const yieldEvery = Math.max(1, options2.yieldEvery ?? 1e3);
|
|
6497
|
+
if (!header) {
|
|
6761
6498
|
for (const line of lines) {
|
|
6762
|
-
if (
|
|
6499
|
+
if (TAB_MESSAGE_PATTERN.test(line)) {
|
|
6763
6500
|
useTabSeparator = true;
|
|
6764
|
-
onLog?.("debug", `LINE \u68C0\u6D4B\u5230 Tab \u5206\u9694\u683C\u5F0F`);
|
|
6765
6501
|
break;
|
|
6766
6502
|
}
|
|
6767
|
-
if (
|
|
6503
|
+
if (SPACE_MESSAGE_PATTERN.test(line)) {
|
|
6768
6504
|
useTabSeparator = false;
|
|
6769
|
-
onLog?.("debug", `LINE \u68C0\u6D4B\u5230\u7A7A\u683C\u5206\u9694\u683C\u5F0F`);
|
|
6770
6505
|
break;
|
|
6771
6506
|
}
|
|
6772
6507
|
}
|
|
6773
6508
|
}
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
const
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
}
|
|
6792
|
-
if (msgMatch) {
|
|
6793
|
-
const [, timeStr, sender, contentRaw] = msgMatch;
|
|
6794
|
-
let content2 = contentRaw.trim();
|
|
6795
|
-
let isQuotedMultiline = false;
|
|
6796
|
-
if (content2.startsWith('"')) {
|
|
6797
|
-
content2 = content2.substring(1);
|
|
6798
|
-
isQuotedMultiline = !content2.endsWith('"');
|
|
6799
|
-
if (content2.endsWith('"')) {
|
|
6800
|
-
content2 = content2.substring(0, content2.length - 1);
|
|
6509
|
+
for (; lineIndex < lines.length; lineIndex += 1) {
|
|
6510
|
+
options2.checkCancelled?.();
|
|
6511
|
+
const line = lines[lineIndex];
|
|
6512
|
+
const date = parseDateLine(line);
|
|
6513
|
+
if (date) {
|
|
6514
|
+
currentDate = date;
|
|
6515
|
+
} else {
|
|
6516
|
+
const messagePattern = useTabSeparator ? TAB_MESSAGE_PATTERN : SPACE_MESSAGE_PATTERN;
|
|
6517
|
+
const messageMatch = line.match(messagePattern);
|
|
6518
|
+
if (messageMatch) {
|
|
6519
|
+
const [, timeText, sender, rawContent] = messageMatch;
|
|
6520
|
+
let messageContent = rawContent.trim();
|
|
6521
|
+
quotedMultiline = false;
|
|
6522
|
+
if (messageContent.startsWith('"')) {
|
|
6523
|
+
messageContent = messageContent.slice(1);
|
|
6524
|
+
quotedMultiline = !messageContent.endsWith('"');
|
|
6525
|
+
if (!quotedMultiline) messageContent = messageContent.slice(0, -1);
|
|
6801
6526
|
}
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
if (suffix === "am" && hours === 12) hours = 0;
|
|
6814
|
-
if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
|
|
6815
|
-
if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
|
|
6816
|
-
} else {
|
|
6817
|
-
const parts = timeStr.split(":").map(Number);
|
|
6818
|
-
hours = parts[0];
|
|
6819
|
-
minutes = parts[1];
|
|
6820
|
-
}
|
|
6821
|
-
let timestamp;
|
|
6822
|
-
if (currentDate) {
|
|
6823
|
-
const msgDate = new Date(currentDate);
|
|
6824
|
-
msgDate.setHours(hours, minutes, 0, 0);
|
|
6825
|
-
timestamp = Math.floor(msgDate.getTime() / 1e3);
|
|
6527
|
+
if (!memberMap.has(sender)) {
|
|
6528
|
+
memberMap.set(sender, { platformId: sender, accountName: sender });
|
|
6529
|
+
}
|
|
6530
|
+
lastMessage = {
|
|
6531
|
+
senderPlatformId: sender,
|
|
6532
|
+
senderAccountName: sender,
|
|
6533
|
+
timestamp: buildTimestamp(currentDate, timeText, options2.now),
|
|
6534
|
+
type: detectMessageType6(messageContent),
|
|
6535
|
+
content: messageContent || null
|
|
6536
|
+
};
|
|
6537
|
+
messages.push(lastMessage);
|
|
6826
6538
|
} else {
|
|
6827
|
-
const
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6539
|
+
const systemMatch = line.match(SYSTEM_MESSAGE_PATTERN);
|
|
6540
|
+
if (systemMatch) {
|
|
6541
|
+
const [, timeText, rawContent] = systemMatch;
|
|
6542
|
+
const messageContent = rawContent.trim();
|
|
6543
|
+
quotedMultiline = false;
|
|
6544
|
+
lastMessage = {
|
|
6545
|
+
senderPlatformId: "system",
|
|
6546
|
+
senderAccountName: "\u7CFB\u7D71",
|
|
6547
|
+
timestamp: buildTimestamp(currentDate, timeText, options2.now),
|
|
6548
|
+
type: 80 /* SYSTEM */,
|
|
6549
|
+
content: messageContent || null
|
|
6550
|
+
};
|
|
6551
|
+
messages.push(lastMessage);
|
|
6552
|
+
} else if (line.trim() && lastMessage) {
|
|
6553
|
+
let continuation = line;
|
|
6554
|
+
if (quotedMultiline && continuation.endsWith('"')) {
|
|
6555
|
+
continuation = continuation.slice(0, -1);
|
|
6556
|
+
quotedMultiline = false;
|
|
6557
|
+
}
|
|
6558
|
+
lastMessage.content = lastMessage.content ? `${lastMessage.content}
|
|
6559
|
+
${continuation}` : continuation;
|
|
6560
|
+
}
|
|
6837
6561
|
}
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6562
|
+
}
|
|
6563
|
+
if (lineIndex > 0 && lineIndex % yieldEvery === 0) {
|
|
6564
|
+
options2.onProgress?.({ progress: lineIndex / lines.length, messagesProcessed: messages.length });
|
|
6565
|
+
await new Promise((resolve4) => setTimeout(resolve4, 0));
|
|
6566
|
+
options2.checkCancelled?.();
|
|
6567
|
+
}
|
|
6568
|
+
}
|
|
6569
|
+
options2.checkCancelled?.();
|
|
6570
|
+
const memberCount = memberMap.size;
|
|
6571
|
+
const chatType = header ? header.isGroup ? "group" /* GROUP */ : "private" /* PRIVATE */ : memberCount <= 2 ? "private" /* PRIVATE */ : "group" /* GROUP */;
|
|
6572
|
+
const result = {
|
|
6573
|
+
meta: { name: chatName, platform: KNOWN_PLATFORMS.LINE, type: chatType },
|
|
6574
|
+
members: Array.from(memberMap.values()),
|
|
6575
|
+
messages
|
|
6576
|
+
};
|
|
6577
|
+
options2.onProgress?.({ progress: 1, messagesProcessed: messages.length });
|
|
6578
|
+
return result;
|
|
6579
|
+
}
|
|
6580
|
+
function extractNameFromFileName3(fileName) {
|
|
6581
|
+
const basename10 = getBasename3(fileName).replace(/\.txt$/, "");
|
|
6582
|
+
return basename10.replace(/^\[LINE\]\s*/i, "").trim() || "\u672A\u77E5\u804A\u5929";
|
|
6583
|
+
}
|
|
6584
|
+
function extractNameFromHeader(header) {
|
|
6585
|
+
const englishPrivate = header.match(/^(?:\[LINE\] )?Chat history with (.+)$/m);
|
|
6586
|
+
if (englishPrivate) return { name: englishPrivate[1].trim(), isGroup: false };
|
|
6587
|
+
const englishGroup = header.match(/^(?:\[LINE\] )?Chat history in (.+)$/m);
|
|
6588
|
+
if (englishGroup) return { name: englishGroup[1].trim(), isGroup: true };
|
|
6589
|
+
const japanesePrivate = header.match(/^\[LINE\] (.+)とのトーク履歴/);
|
|
6590
|
+
if (japanesePrivate) return { name: japanesePrivate[1].trim(), isGroup: false };
|
|
6591
|
+
const japaneseGroup = header.match(/^\[LINE\] (.+)のトーク履歴/);
|
|
6592
|
+
if (japaneseGroup) return { name: japaneseGroup[1].trim(), isGroup: true };
|
|
6593
|
+
const simplifiedPrivate = header.match(/^\[LINE\] 与(.+)的聊天记录/);
|
|
6594
|
+
if (simplifiedPrivate) return { name: simplifiedPrivate[1].trim(), isGroup: false };
|
|
6595
|
+
const simplifiedGroup = header.match(/^\[LINE\] (.+)的聊天记录/);
|
|
6596
|
+
if (simplifiedGroup) return { name: simplifiedGroup[1].trim(), isGroup: true };
|
|
6597
|
+
const traditionalPrivate = header.match(/^\[LINE\] 與(.+)的聊天記錄/);
|
|
6598
|
+
if (traditionalPrivate) return { name: traditionalPrivate[1].trim(), isGroup: false };
|
|
6599
|
+
const traditionalGroup = header.match(/^\[LINE\] (.+)的聊天記錄/);
|
|
6600
|
+
return traditionalGroup ? { name: traditionalGroup[1].trim(), isGroup: true } : null;
|
|
6601
|
+
}
|
|
6602
|
+
function getBasename3(fileName) {
|
|
6603
|
+
return fileName.split(/[\\/]/).pop() ?? fileName;
|
|
6604
|
+
}
|
|
6605
|
+
function parseDateLine(line) {
|
|
6606
|
+
for (let index = 0; index < DATE_PATTERNS.length; index += 1) {
|
|
6607
|
+
const match = line.match(DATE_PATTERNS[index]);
|
|
6608
|
+
if (!match) continue;
|
|
6609
|
+
if (index < 2) {
|
|
6610
|
+
return new Date(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10) - 1, Number.parseInt(match[3], 10));
|
|
6611
|
+
}
|
|
6612
|
+
return new Date(Number.parseInt(match[3], 10), Number.parseInt(match[1], 10) - 1, Number.parseInt(match[2], 10));
|
|
6613
|
+
}
|
|
6614
|
+
return null;
|
|
6615
|
+
}
|
|
6616
|
+
function buildTimestamp(currentDate, timeText, now) {
|
|
6617
|
+
const { hours, minutes } = parseTime(timeText);
|
|
6618
|
+
const value = currentDate ? new Date(currentDate) : now?.() ?? /* @__PURE__ */ new Date();
|
|
6619
|
+
value.setHours(hours, minutes, 0, 0);
|
|
6620
|
+
return Math.floor(value.getTime() / 1e3);
|
|
6621
|
+
}
|
|
6622
|
+
function parseTime(timeText) {
|
|
6623
|
+
const prefix = timeText.match(/^(上午|下午|午前|午後)/)?.[1];
|
|
6624
|
+
const cleanTime = timeText.replace(/^(上午|下午|午前|午後)/, "");
|
|
6625
|
+
const match = cleanTime.match(/^(\d{1,2}):(\d{2})([AaPp][Mm])?$/i);
|
|
6626
|
+
if (!match) {
|
|
6627
|
+
const [hours2, minutes2] = timeText.split(":").map(Number);
|
|
6628
|
+
return { hours: hours2, minutes: minutes2 };
|
|
6629
|
+
}
|
|
6630
|
+
let hours = Number.parseInt(match[1], 10);
|
|
6631
|
+
const minutes = Number.parseInt(match[2], 10);
|
|
6632
|
+
const suffix = match[3]?.toLowerCase();
|
|
6633
|
+
if (suffix === "pm" && hours < 12) hours += 12;
|
|
6634
|
+
if (suffix === "am" && hours === 12) hours = 0;
|
|
6635
|
+
if ((prefix === "\u4E0B\u5348" || prefix === "\u5348\u5F8C") && hours < 12) hours += 12;
|
|
6636
|
+
if ((prefix === "\u4E0A\u5348" || prefix === "\u5348\u524D") && hours === 12) hours = 0;
|
|
6637
|
+
return { hours, minutes };
|
|
6638
|
+
}
|
|
6639
|
+
function detectMessageType6(content) {
|
|
6640
|
+
for (const [pattern, type] of Object.entries(SPECIAL_MESSAGE_TYPES)) {
|
|
6641
|
+
if (content === pattern || content.startsWith(pattern)) return type;
|
|
6642
|
+
}
|
|
6643
|
+
if (content.startsWith("[null]") && content.includes("maps.google.com")) return 8 /* LOCATION */;
|
|
6644
|
+
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")) {
|
|
6645
|
+
return 80 /* SYSTEM */;
|
|
6646
|
+
}
|
|
6647
|
+
return /^https?:\/\//.test(content) ? 7 /* LINK */ : 0 /* TEXT */;
|
|
6648
|
+
}
|
|
6649
|
+
|
|
6650
|
+
// ../../packages/parser/src/formats/line-native-txt.ts
|
|
6651
|
+
var feature14 = {
|
|
6652
|
+
id: "line-native-txt",
|
|
6653
|
+
name: "LINE \u5B98\u65B9\u5BFC\u51FA TXT",
|
|
6654
|
+
platform: KNOWN_PLATFORMS.LINE,
|
|
6655
|
+
priority: 35,
|
|
6656
|
+
extensions: [".txt"],
|
|
6657
|
+
signatures: {
|
|
6658
|
+
head: LINE_HEAD_SIGNATURES,
|
|
6659
|
+
filename: LINE_FILENAME_SIGNATURES
|
|
6660
|
+
}
|
|
6661
|
+
};
|
|
6662
|
+
async function* parseLine2(options2) {
|
|
6663
|
+
const { filePath, batchSize = 5e3, onProgress, onLog } = options2;
|
|
6664
|
+
const totalBytes = getFileSize(filePath);
|
|
6665
|
+
const initialProgress = createProgress("parsing", 0, totalBytes, 0, "");
|
|
6666
|
+
yield { type: "progress", data: initialProgress };
|
|
6667
|
+
onProgress?.(initialProgress);
|
|
6668
|
+
onLog?.("info", `Starting LINE TXT parsing (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
|
|
6669
|
+
const result = await parseLineText(fs17.readFileSync(filePath, "utf-8"), filePath, {
|
|
6670
|
+
onProgress: (progress) => onProgress?.(
|
|
6671
|
+
createProgress(
|
|
6672
|
+
"parsing",
|
|
6673
|
+
Math.round(progress.progress * totalBytes),
|
|
6674
|
+
totalBytes,
|
|
6675
|
+
progress.messagesProcessed,
|
|
6676
|
+
`Processed ${progress.messagesProcessed} messages`
|
|
6677
|
+
)
|
|
6678
|
+
)
|
|
6679
|
+
});
|
|
6680
|
+
yield { type: "meta", data: result.meta };
|
|
6681
|
+
yield { type: "members", data: result.members };
|
|
6682
|
+
for (let index = 0; index < result.messages.length; index += batchSize) {
|
|
6683
|
+
yield { type: "messages", data: result.messages.slice(index, index + batchSize) };
|
|
6684
|
+
}
|
|
6685
|
+
const doneProgress = createProgress("done", totalBytes, totalBytes, result.messages.length, "");
|
|
6686
|
+
yield { type: "progress", data: doneProgress };
|
|
6687
|
+
onProgress?.(doneProgress);
|
|
6688
|
+
onLog?.("info", `LINE TXT parsing completed: ${result.messages.length} messages, ${result.members.length} members`);
|
|
6689
|
+
yield {
|
|
6690
|
+
type: "done",
|
|
6691
|
+
data: { messageCount: result.messages.length, memberCount: result.members.length }
|
|
6692
|
+
};
|
|
6693
|
+
}
|
|
6694
|
+
var parser_13 = {
|
|
6695
|
+
feature: feature14,
|
|
6696
|
+
parse: parseLine2
|
|
6697
|
+
};
|
|
6698
|
+
var module_14 = {
|
|
6699
|
+
feature: feature14,
|
|
6700
|
+
parser: parser_13
|
|
6701
|
+
};
|
|
6702
|
+
var line_native_txt_default = module_14;
|
|
6703
|
+
|
|
6704
|
+
// ../../packages/parser/src/formats/index.ts
|
|
6705
|
+
var formats = [
|
|
6706
|
+
shuakami_qq_exporter_chunked_default,
|
|
6707
|
+
// 优先级 5 - shuakami/qq-chat-exporter chunked-jsonl
|
|
6708
|
+
shuakami_qq_exporter_default,
|
|
6709
|
+
// 优先级 10 - shuakami/qq-chat-exporter
|
|
6710
|
+
weflow_default,
|
|
6711
|
+
// 优先级 15 - WeFlow 微信导出
|
|
6712
|
+
ycccccccy_echotrace_default,
|
|
6713
|
+
// 优先级 16 - ycccccccy/echotrace 微信导出
|
|
6714
|
+
tyrrrz_discord_exporter_default,
|
|
6715
|
+
// 优先级 20 - Tyrrrz/DiscordChatExporter
|
|
6716
|
+
telegram_native_default,
|
|
6717
|
+
// 优先级 22 - Telegram 官方全量导出 JSON
|
|
6718
|
+
telegram_native_single_default,
|
|
6719
|
+
// 优先级 23 - Telegram 单聊天导出 JSON
|
|
6720
|
+
google_chat_takeout_default,
|
|
6721
|
+
// 优先级 24 - Google Chat Takeout 内部 manifest
|
|
6722
|
+
instagram_native_default,
|
|
6723
|
+
// 优先级 25 - Instagram 官方导出
|
|
6724
|
+
whatsapp_native_txt_default,
|
|
6725
|
+
// 优先级 26 - WhatsApp 官方导出 TXT
|
|
6726
|
+
qq_native_txt_default,
|
|
6727
|
+
// 优先级 30 - QQ 官方导出 TXT
|
|
6728
|
+
line_native_txt_default,
|
|
6729
|
+
// 优先级 35 - LINE 官方导出 TXT
|
|
6730
|
+
chatlab_default,
|
|
6731
|
+
// 优先级 50 - ChatLab JSON
|
|
6732
|
+
chatlab_jsonl_default
|
|
6733
|
+
// 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
|
|
6734
|
+
];
|
|
6735
|
+
|
|
6736
|
+
// ../../packages/parser/src/chatlab-validator.ts
|
|
6737
|
+
import { createReadStream as createReadStream13 } from "fs";
|
|
6738
|
+
import { readFile, stat } from "fs/promises";
|
|
6739
|
+
import { extname as extname2 } from "path";
|
|
6740
|
+
import { createInterface as createInterface5 } from "readline";
|
|
6741
|
+
var MAX_REPORTED_ISSUES = 100;
|
|
6742
|
+
var MAX_UNIX_SECONDS = 1e11;
|
|
6743
|
+
var KNOWN_MESSAGE_TYPES = new Set(
|
|
6744
|
+
Object.values(MessageType).filter((value) => typeof value === "number")
|
|
6745
|
+
);
|
|
6746
|
+
function createState(format, fileSize) {
|
|
6747
|
+
return {
|
|
6748
|
+
format,
|
|
6749
|
+
fileSize,
|
|
6750
|
+
errorCount: 0,
|
|
6751
|
+
warningCount: 0,
|
|
6752
|
+
issues: [],
|
|
6753
|
+
stats: {
|
|
6754
|
+
headerCount: 0,
|
|
6755
|
+
memberCount: 0,
|
|
6756
|
+
messageCount: 0,
|
|
6757
|
+
uniqueSenderCount: 0,
|
|
6758
|
+
commentLineCount: 0,
|
|
6759
|
+
blankLineCount: 0
|
|
6760
|
+
},
|
|
6761
|
+
memberIds: /* @__PURE__ */ new Set(),
|
|
6762
|
+
ownerIds: /* @__PURE__ */ new Set(),
|
|
6763
|
+
senderIds: /* @__PURE__ */ new Set(),
|
|
6764
|
+
messageIds: /* @__PURE__ */ new Set(),
|
|
6765
|
+
replyTargetIds: /* @__PURE__ */ new Set()
|
|
6766
|
+
};
|
|
6767
|
+
}
|
|
6768
|
+
function addIssue(state, severity, code, message, location = {}) {
|
|
6769
|
+
if (severity === "error") state.errorCount++;
|
|
6770
|
+
else state.warningCount++;
|
|
6771
|
+
if (state.issues.length < MAX_REPORTED_ISSUES) {
|
|
6772
|
+
state.issues.push({ severity, code, message, ...location });
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6775
|
+
function isRecord(value) {
|
|
6776
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6777
|
+
}
|
|
6778
|
+
function hasOwn(value, key) {
|
|
6779
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
6780
|
+
}
|
|
6781
|
+
function childLocation(location, path30) {
|
|
6782
|
+
return { ...location, path: path30 };
|
|
6783
|
+
}
|
|
6784
|
+
function validateRequiredString(state, value, path30, location) {
|
|
6785
|
+
if (typeof value === "string" && value.trim().length > 0) return true;
|
|
6786
|
+
addIssue(state, "error", "INVALID_REQUIRED_STRING", "Expected a non-empty string.", childLocation(location, path30));
|
|
6787
|
+
return false;
|
|
6788
|
+
}
|
|
6789
|
+
function validateOptionalString(state, object, key, path30, location) {
|
|
6790
|
+
if (!hasOwn(object, key)) return;
|
|
6791
|
+
if (typeof object[key] !== "string") {
|
|
6792
|
+
addIssue(state, "error", "INVALID_OPTIONAL_STRING", "Expected a string.", childLocation(location, path30));
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
function validateUnixSeconds(state, value, path30, location) {
|
|
6796
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value < MAX_UNIX_SECONDS) {
|
|
6797
|
+
return true;
|
|
6798
|
+
}
|
|
6799
|
+
addIssue(
|
|
6800
|
+
state,
|
|
6801
|
+
"error",
|
|
6802
|
+
"INVALID_UNIX_SECONDS",
|
|
6803
|
+
"Expected a non-negative integer Unix timestamp in seconds, not milliseconds.",
|
|
6804
|
+
childLocation(location, path30)
|
|
6805
|
+
);
|
|
6806
|
+
return false;
|
|
6807
|
+
}
|
|
6808
|
+
function validateHeader(state, chatlab, meta, location, basePath = "") {
|
|
6809
|
+
state.stats.headerCount++;
|
|
6810
|
+
if (!isRecord(chatlab)) {
|
|
6811
|
+
addIssue(state, "error", "INVALID_CHATLAB_HEADER", "Expected a chatlab object.", {
|
|
6812
|
+
...location,
|
|
6813
|
+
path: `${basePath}chatlab`
|
|
6814
|
+
});
|
|
6815
|
+
} else {
|
|
6816
|
+
const versionPath = `${basePath}chatlab.version`;
|
|
6817
|
+
if (typeof chatlab.version !== "string") {
|
|
6818
|
+
addIssue(state, "error", "INVALID_VERSION", "Expected a string format version.", {
|
|
6819
|
+
...location,
|
|
6820
|
+
path: versionPath
|
|
6821
|
+
});
|
|
6822
|
+
} else {
|
|
6823
|
+
state.version = chatlab.version;
|
|
6824
|
+
if (!isSupportedChatLabFormatVersion(chatlab.version)) {
|
|
6825
|
+
addIssue(
|
|
6826
|
+
state,
|
|
6827
|
+
"error",
|
|
6828
|
+
"UNSUPPORTED_VERSION",
|
|
6829
|
+
`Supported ChatLab format versions: ${CHATLAB_SUPPORTED_FORMAT_VERSIONS.join(", ")}.`,
|
|
6830
|
+
{
|
|
6831
|
+
...location,
|
|
6832
|
+
path: versionPath
|
|
6833
|
+
}
|
|
6856
6834
|
);
|
|
6857
|
-
onProgress?.(progress);
|
|
6858
6835
|
}
|
|
6836
|
+
}
|
|
6837
|
+
validateUnixSeconds(state, chatlab.exportedAt, `${basePath}chatlab.exportedAt`, location);
|
|
6838
|
+
validateOptionalString(state, chatlab, "generator", `${basePath}chatlab.generator`, location);
|
|
6839
|
+
validateOptionalString(state, chatlab, "description", `${basePath}chatlab.description`, location);
|
|
6840
|
+
}
|
|
6841
|
+
if (!isRecord(meta)) {
|
|
6842
|
+
addIssue(state, "error", "INVALID_META", "Expected a meta object.", {
|
|
6843
|
+
...location,
|
|
6844
|
+
path: `${basePath}meta`
|
|
6845
|
+
});
|
|
6846
|
+
return;
|
|
6847
|
+
}
|
|
6848
|
+
validateRequiredString(state, meta.name, `${basePath}meta.name`, location);
|
|
6849
|
+
validateRequiredString(state, meta.platform, `${basePath}meta.platform`, location);
|
|
6850
|
+
if (meta.type !== "group" && meta.type !== "private") {
|
|
6851
|
+
addIssue(state, "error", "INVALID_CHAT_TYPE", 'Expected chat type "group" or "private".', {
|
|
6852
|
+
...location,
|
|
6853
|
+
path: `${basePath}meta.type`
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
validateOptionalString(state, meta, "groupId", `${basePath}meta.groupId`, location);
|
|
6857
|
+
validateOptionalString(state, meta, "groupAvatar", `${basePath}meta.groupAvatar`, location);
|
|
6858
|
+
if (hasOwn(meta, "ownerId")) {
|
|
6859
|
+
if (validateRequiredString(state, meta.ownerId, `${basePath}meta.ownerId`, location)) {
|
|
6860
|
+
state.ownerIds.add(meta.ownerId);
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
validateOptionalString(state, meta, "sourceSessionId", `${basePath}meta.sourceSessionId`, location);
|
|
6864
|
+
}
|
|
6865
|
+
function validateMember(state, value, path30, location) {
|
|
6866
|
+
state.stats.memberCount++;
|
|
6867
|
+
if (!isRecord(value)) {
|
|
6868
|
+
addIssue(state, "error", "INVALID_MEMBER", "Expected a member object.", childLocation(location, path30));
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6871
|
+
if (validateRequiredString(state, value.platformId, `${path30}.platformId`, location)) {
|
|
6872
|
+
if (state.memberIds.has(value.platformId)) {
|
|
6873
|
+
addIssue(state, "error", "DUPLICATE_MEMBER_ID", "Member platformId must be unique.", {
|
|
6874
|
+
...location,
|
|
6875
|
+
path: `${path30}.platformId`
|
|
6876
|
+
});
|
|
6859
6877
|
} else {
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
if (
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
now.setHours(hours, minutes, 0, 0);
|
|
6890
|
-
timestamp = Math.floor(now.getTime() / 1e3);
|
|
6878
|
+
state.memberIds.add(value.platformId);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
|
|
6882
|
+
validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
|
|
6883
|
+
validateOptionalString(state, value, "avatar", `${path30}.avatar`, location);
|
|
6884
|
+
if (hasOwn(value, "aliases")) {
|
|
6885
|
+
if (!Array.isArray(value.aliases) || value.aliases.some((alias) => typeof alias !== "string")) {
|
|
6886
|
+
addIssue(state, "error", "INVALID_ALIASES", "Expected aliases to be an array of strings.", {
|
|
6887
|
+
...location,
|
|
6888
|
+
path: `${path30}.aliases`
|
|
6889
|
+
});
|
|
6890
|
+
}
|
|
6891
|
+
}
|
|
6892
|
+
if (hasOwn(value, "roles")) {
|
|
6893
|
+
if (!Array.isArray(value.roles)) {
|
|
6894
|
+
addIssue(state, "error", "INVALID_ROLES", "Expected roles to be an array.", {
|
|
6895
|
+
...location,
|
|
6896
|
+
path: `${path30}.roles`
|
|
6897
|
+
});
|
|
6898
|
+
} else {
|
|
6899
|
+
value.roles.forEach((role, index) => {
|
|
6900
|
+
const rolePath = `${path30}.roles[${index}]`;
|
|
6901
|
+
if (!isRecord(role)) {
|
|
6902
|
+
addIssue(state, "error", "INVALID_ROLE", "Expected a role object.", {
|
|
6903
|
+
...location,
|
|
6904
|
+
path: rolePath
|
|
6905
|
+
});
|
|
6906
|
+
return;
|
|
6891
6907
|
}
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6908
|
+
validateRequiredString(state, role.id, `${rolePath}.id`, location);
|
|
6909
|
+
validateOptionalString(state, role, "name", `${rolePath}.name`, location);
|
|
6910
|
+
});
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
function validateMessage(state, value, path30, location) {
|
|
6915
|
+
state.stats.messageCount++;
|
|
6916
|
+
if (!isRecord(value)) {
|
|
6917
|
+
addIssue(state, "error", "INVALID_MESSAGE", "Expected a message object.", childLocation(location, path30));
|
|
6918
|
+
return;
|
|
6919
|
+
}
|
|
6920
|
+
if (validateRequiredString(state, value.sender, `${path30}.sender`, location)) {
|
|
6921
|
+
state.senderIds.add(value.sender);
|
|
6922
|
+
}
|
|
6923
|
+
validateRequiredString(state, value.accountName, `${path30}.accountName`, location);
|
|
6924
|
+
if (validateUnixSeconds(state, value.timestamp, `${path30}.timestamp`, location)) {
|
|
6925
|
+
if (state.lastTimestamp !== void 0 && value.timestamp < state.lastTimestamp) {
|
|
6926
|
+
addIssue(
|
|
6927
|
+
state,
|
|
6928
|
+
"warning",
|
|
6929
|
+
"OUT_OF_ORDER_TIMESTAMP",
|
|
6930
|
+
"Messages should be ordered by timestamp ascending.",
|
|
6931
|
+
childLocation(location, `${path30}.timestamp`)
|
|
6932
|
+
);
|
|
6933
|
+
}
|
|
6934
|
+
state.lastTimestamp = value.timestamp;
|
|
6935
|
+
}
|
|
6936
|
+
if (typeof value.type !== "number" || !Number.isInteger(value.type) || !KNOWN_MESSAGE_TYPES.has(value.type)) {
|
|
6937
|
+
addIssue(state, "error", "INVALID_MESSAGE_TYPE", "Expected a supported numeric ChatLab message type.", {
|
|
6938
|
+
...location,
|
|
6939
|
+
path: `${path30}.type`
|
|
6940
|
+
});
|
|
6941
|
+
}
|
|
6942
|
+
if (!hasOwn(value, "content") || typeof value.content !== "string" && value.content !== null) {
|
|
6943
|
+
addIssue(state, "error", "INVALID_MESSAGE_CONTENT", "Expected content to be a string or null.", {
|
|
6944
|
+
...location,
|
|
6945
|
+
path: `${path30}.content`
|
|
6946
|
+
});
|
|
6947
|
+
}
|
|
6948
|
+
validateOptionalString(state, value, "groupNickname", `${path30}.groupNickname`, location);
|
|
6949
|
+
if (hasOwn(value, "platformMessageId")) {
|
|
6950
|
+
if (validateRequiredString(state, value.platformMessageId, `${path30}.platformMessageId`, location)) {
|
|
6951
|
+
if (state.messageIds.has(value.platformMessageId)) {
|
|
6952
|
+
addIssue(state, "error", "DUPLICATE_MESSAGE_ID", "platformMessageId must be unique.", {
|
|
6953
|
+
...location,
|
|
6954
|
+
path: `${path30}.platformMessageId`
|
|
6955
|
+
});
|
|
6956
|
+
} else {
|
|
6957
|
+
state.messageIds.add(value.platformMessageId);
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
if (hasOwn(value, "replyToMessageId")) {
|
|
6962
|
+
if (validateRequiredString(state, value.replyToMessageId, `${path30}.replyToMessageId`, location)) {
|
|
6963
|
+
state.replyTargetIds.add(value.replyToMessageId);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
function finalizeState(state, requireExplicitMembers, enforceRequiredSections = true) {
|
|
6968
|
+
if (enforceRequiredSections) {
|
|
6969
|
+
if (state.stats.headerCount === 0) {
|
|
6970
|
+
addIssue(state, "error", "MISSING_HEADER", "A ChatLab header is required.");
|
|
6971
|
+
}
|
|
6972
|
+
if (state.stats.messageCount === 0) {
|
|
6973
|
+
addIssue(state, "error", "MISSING_MESSAGES", "At least one message is required.");
|
|
6974
|
+
}
|
|
6975
|
+
if (requireExplicitMembers && state.stats.memberCount === 0) {
|
|
6976
|
+
addIssue(state, "error", "MISSING_MEMBERS", "The JSON format requires a members array with sender identities.");
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
const ownerMemberIds = state.memberIds.size > 0 ? state.memberIds : requireExplicitMembers ? void 0 : state.senderIds;
|
|
6980
|
+
if (ownerMemberIds) {
|
|
6981
|
+
for (const ownerId of state.ownerIds) {
|
|
6982
|
+
if (!ownerMemberIds.has(ownerId)) {
|
|
6983
|
+
addIssue(state, "error", "UNKNOWN_OWNER", "meta.ownerId must reference a member platformId.");
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
if (state.memberIds.size > 0) {
|
|
6988
|
+
for (const senderId of state.senderIds) {
|
|
6989
|
+
if (!state.memberIds.has(senderId)) {
|
|
6990
|
+
addIssue(
|
|
6991
|
+
state,
|
|
6992
|
+
"error",
|
|
6993
|
+
"UNKNOWN_MESSAGE_SENDER",
|
|
6994
|
+
"Every message sender must reference an explicit member platformId."
|
|
6995
|
+
);
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
for (const replyTargetId of state.replyTargetIds) {
|
|
7000
|
+
if (!state.messageIds.has(replyTargetId)) {
|
|
7001
|
+
addIssue(
|
|
7002
|
+
state,
|
|
7003
|
+
"warning",
|
|
7004
|
+
"UNKNOWN_REPLY_TARGET",
|
|
7005
|
+
"A replyToMessageId does not match any platformMessageId in this file."
|
|
7006
|
+
);
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
state.stats.uniqueSenderCount = state.senderIds.size;
|
|
7010
|
+
const totalIssueCount = state.errorCount + state.warningCount;
|
|
7011
|
+
return {
|
|
7012
|
+
valid: state.errorCount === 0,
|
|
7013
|
+
format: state.format,
|
|
7014
|
+
version: state.version,
|
|
7015
|
+
fileSize: state.fileSize,
|
|
7016
|
+
errorCount: state.errorCount,
|
|
7017
|
+
warningCount: state.warningCount,
|
|
7018
|
+
issues: state.issues,
|
|
7019
|
+
truncatedIssueCount: Math.max(0, totalIssueCount - state.issues.length),
|
|
7020
|
+
stats: state.stats
|
|
7021
|
+
};
|
|
7022
|
+
}
|
|
7023
|
+
async function validateJson(filePath, fileSize) {
|
|
7024
|
+
const state = createState("json", fileSize);
|
|
7025
|
+
let root;
|
|
7026
|
+
try {
|
|
7027
|
+
root = JSON.parse(await readFile(filePath, "utf8"));
|
|
7028
|
+
} catch {
|
|
7029
|
+
addIssue(state, "error", "INVALID_JSON", "The file is not valid JSON.");
|
|
7030
|
+
return finalizeState(state, true, false);
|
|
7031
|
+
}
|
|
7032
|
+
if (!isRecord(root)) {
|
|
7033
|
+
addIssue(state, "error", "INVALID_ROOT", "Expected a top-level JSON object.");
|
|
7034
|
+
return finalizeState(state, true, false);
|
|
7035
|
+
}
|
|
7036
|
+
validateHeader(state, root.chatlab, root.meta, {});
|
|
7037
|
+
if (!Array.isArray(root.members)) {
|
|
7038
|
+
addIssue(state, "error", "INVALID_MEMBERS", "Expected members to be an array.", { path: "members" });
|
|
7039
|
+
} else {
|
|
7040
|
+
root.members.forEach((member, index) => validateMember(state, member, `members[${index}]`, {}));
|
|
7041
|
+
}
|
|
7042
|
+
if (!Array.isArray(root.messages)) {
|
|
7043
|
+
addIssue(state, "error", "INVALID_MESSAGES", "Expected messages to be an array.", { path: "messages" });
|
|
7044
|
+
} else {
|
|
7045
|
+
root.messages.forEach((message, index) => validateMessage(state, message, `messages[${index}]`, {}));
|
|
7046
|
+
}
|
|
7047
|
+
return finalizeState(state, true);
|
|
7048
|
+
}
|
|
7049
|
+
async function validateJsonl(filePath, fileSize) {
|
|
7050
|
+
const state = createState("jsonl", fileSize);
|
|
7051
|
+
const input = createReadStream13(filePath, { encoding: "utf8" });
|
|
7052
|
+
const lines = createInterface5({ input, crlfDelay: Infinity });
|
|
7053
|
+
let lineNumber = 0;
|
|
7054
|
+
let dataLineCount = 0;
|
|
7055
|
+
let sawMessage = false;
|
|
7056
|
+
for await (const line of lines) {
|
|
7057
|
+
lineNumber++;
|
|
7058
|
+
const trimmed = line.trim();
|
|
7059
|
+
if (!trimmed) {
|
|
7060
|
+
state.stats.blankLineCount++;
|
|
7061
|
+
continue;
|
|
7062
|
+
}
|
|
7063
|
+
if (trimmed.startsWith("#")) {
|
|
7064
|
+
state.stats.commentLineCount++;
|
|
7065
|
+
continue;
|
|
7066
|
+
}
|
|
7067
|
+
dataLineCount++;
|
|
7068
|
+
let value;
|
|
7069
|
+
try {
|
|
7070
|
+
value = JSON.parse(trimmed);
|
|
7071
|
+
} catch {
|
|
7072
|
+
addIssue(state, "error", "INVALID_JSONL_LINE", "The line is not valid JSON.", { line: lineNumber });
|
|
7073
|
+
continue;
|
|
7074
|
+
}
|
|
7075
|
+
if (!isRecord(value)) {
|
|
7076
|
+
addIssue(state, "error", "INVALID_JSONL_VALUE", "Expected a JSON object on every data line.", {
|
|
7077
|
+
line: lineNumber
|
|
7078
|
+
});
|
|
7079
|
+
continue;
|
|
7080
|
+
}
|
|
7081
|
+
if (dataLineCount === 1 && value._type !== "header") {
|
|
7082
|
+
addIssue(state, "error", "HEADER_NOT_FIRST", "The first data line must be a header.", {
|
|
7083
|
+
line: lineNumber,
|
|
7084
|
+
path: "_type"
|
|
7085
|
+
});
|
|
7086
|
+
}
|
|
7087
|
+
switch (value._type) {
|
|
7088
|
+
case "header":
|
|
7089
|
+
if (state.stats.headerCount > 0) {
|
|
7090
|
+
addIssue(state, "error", "DUPLICATE_HEADER", "Only one header line is allowed.", {
|
|
7091
|
+
line: lineNumber,
|
|
7092
|
+
path: "_type"
|
|
7093
|
+
});
|
|
6907
7094
|
}
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
7095
|
+
validateHeader(state, value.chatlab, value.meta, { line: lineNumber });
|
|
7096
|
+
break;
|
|
7097
|
+
case "member":
|
|
7098
|
+
if (sawMessage) {
|
|
7099
|
+
addIssue(state, "error", "MEMBER_AFTER_MESSAGE", "Member lines must appear before message lines.", {
|
|
7100
|
+
line: lineNumber
|
|
7101
|
+
});
|
|
6912
7102
|
}
|
|
6913
|
-
|
|
7103
|
+
validateMember(state, value, `line[${lineNumber}]`, { line: lineNumber });
|
|
7104
|
+
break;
|
|
7105
|
+
case "message":
|
|
7106
|
+
sawMessage = true;
|
|
7107
|
+
validateMessage(state, value, `line[${lineNumber}]`, { line: lineNumber });
|
|
7108
|
+
break;
|
|
7109
|
+
default:
|
|
7110
|
+
addIssue(state, "error", "UNKNOWN_LINE_TYPE", "Expected _type to be header, member, or message.", {
|
|
7111
|
+
line: lineNumber,
|
|
7112
|
+
path: "_type"
|
|
7113
|
+
});
|
|
6914
7114
|
}
|
|
6915
7115
|
}
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
for (let i = 0; i < messages.length; i += batchSize) {
|
|
6927
|
-
const batch = messages.slice(i, i + batchSize);
|
|
6928
|
-
yield { type: "messages", data: batch };
|
|
6929
|
-
}
|
|
6930
|
-
const doneProgress = createProgress("done", totalBytes, totalBytes, messagesProcessed, "");
|
|
6931
|
-
yield { type: "progress", data: doneProgress };
|
|
6932
|
-
onProgress?.(doneProgress);
|
|
6933
|
-
onLog?.("info", `\u89E3\u6790\u5B8C\u6210: ${messagesProcessed} \u6761\u6D88\u606F, ${memberCount} \u4E2A\u6210\u5458`);
|
|
6934
|
-
yield {
|
|
6935
|
-
type: "done",
|
|
6936
|
-
data: { messageCount: messagesProcessed, memberCount }
|
|
6937
|
-
};
|
|
7116
|
+
return finalizeState(state, false);
|
|
7117
|
+
}
|
|
7118
|
+
async function validateChatLabFile(filePath) {
|
|
7119
|
+
const fileStat = await stat(filePath);
|
|
7120
|
+
const extension = extname2(filePath).toLowerCase();
|
|
7121
|
+
if (extension === ".json") return validateJson(filePath, fileStat.size);
|
|
7122
|
+
if (extension === ".jsonl") return validateJsonl(filePath, fileStat.size);
|
|
7123
|
+
const state = createState("unknown", fileStat.size);
|
|
7124
|
+
addIssue(state, "error", "UNSUPPORTED_EXTENSION", "Expected a .json or .jsonl file.");
|
|
7125
|
+
return finalizeState(state, false, false);
|
|
6938
7126
|
}
|
|
6939
|
-
var parser_13 = {
|
|
6940
|
-
feature: feature14,
|
|
6941
|
-
parse: parseLINE
|
|
6942
|
-
};
|
|
6943
|
-
var module_14 = {
|
|
6944
|
-
feature: feature14,
|
|
6945
|
-
parser: parser_13
|
|
6946
|
-
};
|
|
6947
|
-
var line_native_txt_default = module_14;
|
|
6948
|
-
|
|
6949
|
-
// ../../packages/parser/src/formats/index.ts
|
|
6950
|
-
var formats = [
|
|
6951
|
-
shuakami_qq_exporter_chunked_default,
|
|
6952
|
-
// 优先级 5 - shuakami/qq-chat-exporter chunked-jsonl
|
|
6953
|
-
shuakami_qq_exporter_default,
|
|
6954
|
-
// 优先级 10 - shuakami/qq-chat-exporter
|
|
6955
|
-
weflow_default,
|
|
6956
|
-
// 优先级 15 - WeFlow 微信导出
|
|
6957
|
-
ycccccccy_echotrace_default,
|
|
6958
|
-
// 优先级 16 - ycccccccy/echotrace 微信导出
|
|
6959
|
-
tyrrrz_discord_exporter_default,
|
|
6960
|
-
// 优先级 20 - Tyrrrz/DiscordChatExporter
|
|
6961
|
-
telegram_native_default,
|
|
6962
|
-
// 优先级 22 - Telegram 官方全量导出 JSON
|
|
6963
|
-
telegram_native_single_default,
|
|
6964
|
-
// 优先级 23 - Telegram 单聊天导出 JSON
|
|
6965
|
-
google_chat_takeout_default,
|
|
6966
|
-
// 优先级 24 - Google Chat Takeout 内部 manifest
|
|
6967
|
-
instagram_native_default,
|
|
6968
|
-
// 优先级 25 - Instagram 官方导出
|
|
6969
|
-
whatsapp_native_txt_default,
|
|
6970
|
-
// 优先级 26 - WhatsApp 官方导出 TXT
|
|
6971
|
-
qq_native_txt_default,
|
|
6972
|
-
// 优先级 30 - QQ 官方导出 TXT
|
|
6973
|
-
line_native_txt_default,
|
|
6974
|
-
// 优先级 35 - LINE 官方导出 TXT
|
|
6975
|
-
chatlab_default,
|
|
6976
|
-
// 优先级 50 - ChatLab JSON
|
|
6977
|
-
chatlab_jsonl_default
|
|
6978
|
-
// 优先级 51 - ChatLab JSONL(流式格式,支持超大文件)
|
|
6979
|
-
];
|
|
6980
7127
|
|
|
6981
7128
|
// ../../packages/parser/src/index.ts
|
|
6982
7129
|
var sniffer = createSniffer();
|
|
@@ -7051,15 +7198,15 @@ function findEntryFileInDirectory(dirPath, maxDepth = 1) {
|
|
|
7051
7198
|
return null;
|
|
7052
7199
|
}
|
|
7053
7200
|
for (const entry of entries) {
|
|
7054
|
-
const full =
|
|
7055
|
-
if (entry.isFile() && supportedExtensions.has(
|
|
7201
|
+
const full = path11.join(current, entry.name);
|
|
7202
|
+
if (entry.isFile() && supportedExtensions.has(path11.extname(entry.name).toLowerCase())) {
|
|
7056
7203
|
const format = detectFormat(full);
|
|
7057
7204
|
if (format) return full;
|
|
7058
7205
|
}
|
|
7059
7206
|
}
|
|
7060
7207
|
for (const entry of entries) {
|
|
7061
7208
|
if (entry.isDirectory()) {
|
|
7062
|
-
const found = walk(
|
|
7209
|
+
const found = walk(path11.join(current, entry.name), depth + 1);
|
|
7063
7210
|
if (found) return found;
|
|
7064
7211
|
}
|
|
7065
7212
|
}
|
|
@@ -7109,7 +7256,7 @@ function logNativeParserStatus() {
|
|
|
7109
7256
|
|
|
7110
7257
|
// ../../packages/node-runtime/src/import/perf-logger.ts
|
|
7111
7258
|
import * as fs19 from "fs";
|
|
7112
|
-
import * as
|
|
7259
|
+
import * as path12 from "path";
|
|
7113
7260
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
7114
7261
|
LogLevel2["ERROR"] = "ERROR";
|
|
7115
7262
|
LogLevel2["INFO"] = "INFO";
|
|
@@ -7124,7 +7271,7 @@ function initPerfLog(sessionId, logDir) {
|
|
|
7124
7271
|
if (!fs19.existsSync(logDir)) {
|
|
7125
7272
|
fs19.mkdirSync(logDir, { recursive: true });
|
|
7126
7273
|
}
|
|
7127
|
-
currentLogFile =
|
|
7274
|
+
currentLogFile = path12.join(logDir, `import_${sessionId}_${Date.now()}.log`);
|
|
7128
7275
|
fs19.writeFileSync(currentLogFile, `=== Import Log ===
|
|
7129
7276
|
Start time: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7130
7277
|
|
|
@@ -7301,7 +7448,7 @@ function segmentText(text, locale, minLength) {
|
|
|
7301
7448
|
|
|
7302
7449
|
// ../../packages/node-runtime/src/nlp/dict-manager.ts
|
|
7303
7450
|
import * as fs20 from "fs";
|
|
7304
|
-
import * as
|
|
7451
|
+
import * as path13 from "path";
|
|
7305
7452
|
import { createHash } from "crypto";
|
|
7306
7453
|
var DICT_DOWNLOAD_URL_BASE = "https://chatlab.fun/assets/nlp";
|
|
7307
7454
|
var DICT_SHA256 = {
|
|
@@ -7313,7 +7460,7 @@ var AVAILABLE_DICTS = [
|
|
|
7313
7460
|
{ id: "zh-TW", label: "\u7E41\u9AD4\u4E2D\u6587", locale: "zh-TW" }
|
|
7314
7461
|
];
|
|
7315
7462
|
function getDictFilePath(nlpDir, dictId) {
|
|
7316
|
-
return
|
|
7463
|
+
return path13.join(nlpDir, `${dictId}.dict`);
|
|
7317
7464
|
}
|
|
7318
7465
|
function isDictDownloaded(nlpDir, dictId) {
|
|
7319
7466
|
return fs20.existsSync(getDictFilePath(nlpDir, dictId));
|
|
@@ -8779,7 +8926,7 @@ async function analyzeAutoImportFile(filePath, deps, options2 = {}) {
|
|
|
8779
8926
|
|
|
8780
8927
|
// ../../packages/node-runtime/src/import/import-lock.ts
|
|
8781
8928
|
import * as fs22 from "fs";
|
|
8782
|
-
import * as
|
|
8929
|
+
import * as path14 from "path";
|
|
8783
8930
|
import { randomUUID } from "crypto";
|
|
8784
8931
|
var IMPORT_LOCK_FILENAME = ".chatlab-import.lock";
|
|
8785
8932
|
var IMPORT_IN_PROGRESS_ERROR_KEY = "error.import_in_progress";
|
|
@@ -8848,7 +8995,7 @@ function releaseLock(lockPath, fd, owner) {
|
|
|
8848
8995
|
}
|
|
8849
8996
|
async function withDataDirImportLock(userDataDir, task) {
|
|
8850
8997
|
fs22.mkdirSync(userDataDir, { recursive: true });
|
|
8851
|
-
const lockPath =
|
|
8998
|
+
const lockPath = path14.join(userDataDir, IMPORT_LOCK_FILENAME);
|
|
8852
8999
|
let fd = null;
|
|
8853
9000
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8854
9001
|
try {
|
|
@@ -8893,7 +9040,7 @@ var ArchiveImportError = class extends Error {
|
|
|
8893
9040
|
// ../../packages/node-runtime/src/import/archive/archive-reader.ts
|
|
8894
9041
|
var import_yauzl = __toESM(require_yauzl(), 1);
|
|
8895
9042
|
import * as fs23 from "fs";
|
|
8896
|
-
import * as
|
|
9043
|
+
import * as path15 from "path";
|
|
8897
9044
|
import { pipeline } from "stream/promises";
|
|
8898
9045
|
var DEFAULT_MAX_ENTRIES = 1e5;
|
|
8899
9046
|
var DEFAULT_MAX_ENTRY_BYTES = 20 * 1024 * 1024 * 1024;
|
|
@@ -9012,7 +9159,7 @@ var ZipArchiveReader = class {
|
|
|
9012
9159
|
await this.visitEntries(async (entry, openStream) => {
|
|
9013
9160
|
const destination = targets.get(entry.name);
|
|
9014
9161
|
if (!destination || entry.isDirectory) return;
|
|
9015
|
-
fs23.mkdirSync(
|
|
9162
|
+
fs23.mkdirSync(path15.dirname(destination), { recursive: true });
|
|
9016
9163
|
await pipeline(await openStream(), fs23.createWriteStream(destination, { flags: "wx" }));
|
|
9017
9164
|
remaining.delete(entry.name);
|
|
9018
9165
|
});
|
|
@@ -9026,16 +9173,16 @@ var ZipArchiveReader = class {
|
|
|
9026
9173
|
};
|
|
9027
9174
|
|
|
9028
9175
|
// ../../packages/node-runtime/src/import/archive/google-chat-resolver.ts
|
|
9029
|
-
var
|
|
9176
|
+
var import_stream_chain8 = __toESM(require_stream_chain(), 1);
|
|
9030
9177
|
var import_stream_json9 = __toESM(require_stream_json(), 1);
|
|
9031
9178
|
var import_Pick8 = __toESM(require_Pick(), 1);
|
|
9032
|
-
var
|
|
9179
|
+
var import_StreamValues8 = __toESM(require_StreamValues(), 1);
|
|
9033
9180
|
import * as fs24 from "fs";
|
|
9034
|
-
import * as
|
|
9035
|
-
var { chain:
|
|
9181
|
+
import * as path16 from "path";
|
|
9182
|
+
var { chain: chain8 } = import_stream_chain8.default;
|
|
9036
9183
|
var { parser: parser10 } = import_stream_json9.default;
|
|
9037
9184
|
var { pick: pick8 } = import_Pick8.default;
|
|
9038
|
-
var { streamValues:
|
|
9185
|
+
var { streamValues: streamValues8 } = import_StreamValues8.default;
|
|
9039
9186
|
var GROUP_FILE = /^Takeout\/Google Chat\/Groups\/((DM|Space) [^/]+)\/(group_info|messages)\.json$/;
|
|
9040
9187
|
var USER_INFO_FILE = /^Takeout\/Google Chat\/Users\/[^/]+\/user_info\.json$/;
|
|
9041
9188
|
var TAKEOUT_ROOT = /^Takeout\/([^/]+)\//;
|
|
@@ -9058,7 +9205,7 @@ async function readSmallJson(stream, maxBytes = MAX_METADATA_BYTES) {
|
|
|
9058
9205
|
}
|
|
9059
9206
|
}
|
|
9060
9207
|
async function countMessages(stream) {
|
|
9061
|
-
const pipeline2 =
|
|
9208
|
+
const pipeline2 = chain8([stream, parser10(), pick8({ filter: /^messages\.\d+$/ }), streamValues8()]);
|
|
9062
9209
|
let count = 0;
|
|
9063
9210
|
try {
|
|
9064
9211
|
for await (const _item of pipeline2) count++;
|
|
@@ -9163,12 +9310,12 @@ var GoogleChatTakeoutResolver = class {
|
|
|
9163
9310
|
fs24.mkdirSync(targetDir, { recursive: true });
|
|
9164
9311
|
await reader.pipeEntries(
|
|
9165
9312
|
/* @__PURE__ */ new Map([
|
|
9166
|
-
[userInfoEntry,
|
|
9167
|
-
[groupInfoEntry,
|
|
9168
|
-
[messagesEntry,
|
|
9313
|
+
[userInfoEntry, path16.join(targetDir, "user_info.json")],
|
|
9314
|
+
[groupInfoEntry, path16.join(targetDir, "group_info.json")],
|
|
9315
|
+
[messagesEntry, path16.join(targetDir, "messages.json")]
|
|
9169
9316
|
])
|
|
9170
9317
|
);
|
|
9171
|
-
const manifestPath =
|
|
9318
|
+
const manifestPath = path16.join(targetDir, "google-chat-import.json");
|
|
9172
9319
|
fs24.writeFileSync(
|
|
9173
9320
|
manifestPath,
|
|
9174
9321
|
JSON.stringify(
|
|
@@ -9193,7 +9340,7 @@ var GoogleChatTakeoutResolver = class {
|
|
|
9193
9340
|
|
|
9194
9341
|
// ../../packages/node-runtime/src/import/archive/source-manager.ts
|
|
9195
9342
|
import * as fs25 from "fs";
|
|
9196
|
-
import * as
|
|
9343
|
+
import * as path17 from "path";
|
|
9197
9344
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9198
9345
|
var DEFAULT_TTL_MS = 30 * 60 * 1e3;
|
|
9199
9346
|
var ArchiveImportSourceManager = class {
|
|
@@ -9265,7 +9412,7 @@ var ArchiveImportSourceManager = class {
|
|
|
9265
9412
|
`Archive conversation was not found: ${chatId}`
|
|
9266
9413
|
);
|
|
9267
9414
|
}
|
|
9268
|
-
const materializedDir = fs25.mkdtempSync(
|
|
9415
|
+
const materializedDir = fs25.mkdtempSync(path17.join(this.tempRoot, `materialized-${sourceId}-`));
|
|
9269
9416
|
try {
|
|
9270
9417
|
const reader = new ZipArchiveReader(record.archivePath);
|
|
9271
9418
|
const materialized = await record.resolver.materialize(reader, chat, materializedDir);
|
|
@@ -9386,12 +9533,12 @@ ${rawErrorLabel}: ${details}`;
|
|
|
9386
9533
|
}
|
|
9387
9534
|
|
|
9388
9535
|
// ../../packages/node-runtime/src/ai/assistant-parser.ts
|
|
9389
|
-
import * as
|
|
9536
|
+
import * as path18 from "path";
|
|
9390
9537
|
import matter from "gray-matter";
|
|
9391
9538
|
function parseAssistantFile(content, filePath) {
|
|
9392
9539
|
try {
|
|
9393
9540
|
const { data: fm, content: body } = matter(content);
|
|
9394
|
-
const id = fm.id ??
|
|
9541
|
+
const id = fm.id ?? path18.basename(filePath, ".md");
|
|
9395
9542
|
const name = fm.name;
|
|
9396
9543
|
if (!name) return null;
|
|
9397
9544
|
return {
|
|
@@ -9611,8 +9758,8 @@ var AssistantManager = class {
|
|
|
9611
9758
|
}
|
|
9612
9759
|
// ==================== Init ====================
|
|
9613
9760
|
init() {
|
|
9614
|
-
const { fs:
|
|
9615
|
-
|
|
9761
|
+
const { fs: fs36, assistantsDir } = this.deps;
|
|
9762
|
+
fs36.ensureDir(assistantsDir);
|
|
9616
9763
|
const { generalCreated, generalUpdated } = this.syncGeneralAssistants();
|
|
9617
9764
|
this.loadAll();
|
|
9618
9765
|
this.initialized = true;
|
|
@@ -9624,25 +9771,25 @@ var AssistantManager = class {
|
|
|
9624
9771
|
return { total: this.cache.size, generalCreated, generalUpdated };
|
|
9625
9772
|
}
|
|
9626
9773
|
syncGeneralAssistants() {
|
|
9627
|
-
const { fs:
|
|
9774
|
+
const { fs: fs36, assistantsDir } = this.deps;
|
|
9628
9775
|
let generalCreated = false;
|
|
9629
9776
|
let generalUpdated = false;
|
|
9630
9777
|
for (const id of this.generalIds) {
|
|
9631
9778
|
const config = this.getBuiltinConfig(id);
|
|
9632
9779
|
if (!config) continue;
|
|
9633
|
-
const filePath =
|
|
9634
|
-
if (!
|
|
9635
|
-
|
|
9780
|
+
const filePath = fs36.joinPath(assistantsDir, `${id}.md`);
|
|
9781
|
+
if (!fs36.fileExists(filePath)) {
|
|
9782
|
+
fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9636
9783
|
generalCreated = true;
|
|
9637
9784
|
continue;
|
|
9638
9785
|
}
|
|
9639
9786
|
try {
|
|
9640
|
-
const existing = parseAssistantFile(
|
|
9787
|
+
const existing = parseAssistantFile(fs36.readFile(filePath), filePath);
|
|
9641
9788
|
if (!existing) continue;
|
|
9642
9789
|
if (existing.builtinId !== id) {
|
|
9643
9790
|
try {
|
|
9644
9791
|
const backupId = this.backupAssistantOccupyingReservedId(existing);
|
|
9645
|
-
|
|
9792
|
+
fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config)));
|
|
9646
9793
|
generalCreated = true;
|
|
9647
9794
|
this.deps.logger?.info("AssistantManager", "Backed up assistant occupying reserved default ID", {
|
|
9648
9795
|
reservedId: id,
|
|
@@ -9658,7 +9805,7 @@ var AssistantManager = class {
|
|
|
9658
9805
|
continue;
|
|
9659
9806
|
}
|
|
9660
9807
|
if (!this.shouldUpgradeBuiltin(existing, config)) continue;
|
|
9661
|
-
|
|
9808
|
+
fs36.writeFile(filePath, serializeAssistant(this.withBuiltinTracking(config, existing.id)));
|
|
9662
9809
|
generalUpdated = true;
|
|
9663
9810
|
this.deps.logger?.info("AssistantManager", "Updated unmodified default assistant template", {
|
|
9664
9811
|
id,
|
|
@@ -9674,14 +9821,14 @@ var AssistantManager = class {
|
|
|
9674
9821
|
return { generalCreated, generalUpdated };
|
|
9675
9822
|
}
|
|
9676
9823
|
backupAssistantOccupyingReservedId(config) {
|
|
9677
|
-
const { fs:
|
|
9824
|
+
const { fs: fs36, assistantsDir } = this.deps;
|
|
9678
9825
|
const baseId = this.deps.generateId?.() || `custom_${Date.now().toString(36)}`;
|
|
9679
9826
|
let backupId = baseId;
|
|
9680
9827
|
let suffix = 2;
|
|
9681
|
-
while (this.generalIds.includes(backupId) ||
|
|
9828
|
+
while (this.generalIds.includes(backupId) || fs36.fileExists(fs36.joinPath(assistantsDir, `${backupId}.md`))) {
|
|
9682
9829
|
backupId = `${baseId}_${suffix++}`;
|
|
9683
9830
|
}
|
|
9684
|
-
|
|
9831
|
+
fs36.writeFile(fs36.joinPath(assistantsDir, `${backupId}.md`), serializeAssistant({ ...config, id: backupId }));
|
|
9685
9832
|
return backupId;
|
|
9686
9833
|
}
|
|
9687
9834
|
getConfigDigest(config) {
|
|
@@ -9718,13 +9865,13 @@ var AssistantManager = class {
|
|
|
9718
9865
|
return { isUnmodified, isOutdated };
|
|
9719
9866
|
}
|
|
9720
9867
|
loadAll() {
|
|
9721
|
-
const { fs:
|
|
9868
|
+
const { fs: fs36, assistantsDir } = this.deps;
|
|
9722
9869
|
this.cache.clear();
|
|
9723
|
-
const files =
|
|
9870
|
+
const files = fs36.listFiles(assistantsDir, ".md");
|
|
9724
9871
|
for (const file of files) {
|
|
9725
9872
|
try {
|
|
9726
|
-
const filePath =
|
|
9727
|
-
const content =
|
|
9873
|
+
const filePath = fs36.joinPath(assistantsDir, file);
|
|
9874
|
+
const content = fs36.readFile(filePath);
|
|
9728
9875
|
const config = parseAssistantFile(content, filePath);
|
|
9729
9876
|
if (config) {
|
|
9730
9877
|
this.cache.set(config.id, config);
|
|
@@ -9901,12 +10048,12 @@ var AssistantManager = class {
|
|
|
9901
10048
|
};
|
|
9902
10049
|
|
|
9903
10050
|
// ../../packages/node-runtime/src/ai/skill-parser.ts
|
|
9904
|
-
import * as
|
|
10051
|
+
import * as path19 from "path";
|
|
9905
10052
|
import matter2 from "gray-matter";
|
|
9906
10053
|
function parseSkillFile(content, filePath) {
|
|
9907
10054
|
try {
|
|
9908
10055
|
const { data: fm, content: prompt } = matter2(content);
|
|
9909
|
-
const id = fm.id ??
|
|
10056
|
+
const id = fm.id ?? path19.basename(filePath, ".md");
|
|
9910
10057
|
const name = fm.name;
|
|
9911
10058
|
if (!name) return null;
|
|
9912
10059
|
return {
|
|
@@ -9925,7 +10072,7 @@ function parseSkillFile(content, filePath) {
|
|
|
9925
10072
|
function extractSkillId(content, filePath) {
|
|
9926
10073
|
try {
|
|
9927
10074
|
const { data: fm } = matter2(content);
|
|
9928
|
-
return fm.id ??
|
|
10075
|
+
return fm.id ?? path19.basename(filePath, ".md");
|
|
9929
10076
|
} catch {
|
|
9930
10077
|
return null;
|
|
9931
10078
|
}
|
|
@@ -9945,7 +10092,7 @@ function validateChatScope(raw) {
|
|
|
9945
10092
|
// ../../packages/node-runtime/src/ai/chats.ts
|
|
9946
10093
|
import Database from "better-sqlite3";
|
|
9947
10094
|
import * as fs26 from "fs";
|
|
9948
|
-
import * as
|
|
10095
|
+
import * as path20 from "path";
|
|
9949
10096
|
var DEFAULT_GENERAL_ID = DEFAULT_GENERAL_ASSISTANT_ID;
|
|
9950
10097
|
var defaultLogger = {
|
|
9951
10098
|
warn(_category, message, extra) {
|
|
@@ -9971,7 +10118,7 @@ var AIChatManager = class {
|
|
|
9971
10118
|
getDb() {
|
|
9972
10119
|
if (this.db) return this.db;
|
|
9973
10120
|
this.ensureDir(this.aiDataDir);
|
|
9974
|
-
const dbPath =
|
|
10121
|
+
const dbPath = path20.join(this.aiDataDir, "conversations.db");
|
|
9975
10122
|
this.db = this.nativeBinding ? new Database(dbPath, { nativeBinding: this.nativeBinding }) : new Database(dbPath);
|
|
9976
10123
|
this.db.pragma("journal_mode = WAL");
|
|
9977
10124
|
this.db.exec(`
|
|
@@ -10214,16 +10361,16 @@ var AIChatManager = class {
|
|
|
10214
10361
|
if (allRows.length === 0) return [];
|
|
10215
10362
|
const rowMap = new Map(allRows.map((row) => [row.id, row]));
|
|
10216
10363
|
let currentId = leafMessageId ?? this.getActiveMessageId(aiChatId);
|
|
10217
|
-
const
|
|
10364
|
+
const path30 = [];
|
|
10218
10365
|
const seen = /* @__PURE__ */ new Set();
|
|
10219
10366
|
while (currentId && !seen.has(currentId)) {
|
|
10220
10367
|
seen.add(currentId);
|
|
10221
10368
|
const row = rowMap.get(currentId);
|
|
10222
10369
|
if (!row) break;
|
|
10223
|
-
|
|
10370
|
+
path30.push(row);
|
|
10224
10371
|
currentId = row.parentId;
|
|
10225
10372
|
}
|
|
10226
|
-
return
|
|
10373
|
+
return path30.length > 0 ? path30.reverse() : allRows;
|
|
10227
10374
|
}
|
|
10228
10375
|
// ==================== 生命周期 ====================
|
|
10229
10376
|
close() {
|
|
@@ -10793,21 +10940,21 @@ var SkillManagerCore = class {
|
|
|
10793
10940
|
}
|
|
10794
10941
|
// ==================== Init ====================
|
|
10795
10942
|
init() {
|
|
10796
|
-
const { fs:
|
|
10797
|
-
|
|
10943
|
+
const { fs: fs36, skillsDir } = this.deps;
|
|
10944
|
+
fs36.ensureDir(skillsDir);
|
|
10798
10945
|
this.loadAll();
|
|
10799
10946
|
this.initialized = true;
|
|
10800
10947
|
this.deps.logger?.info("SkillManager", "Initialized", { total: this.cache.size });
|
|
10801
10948
|
return { total: this.cache.size };
|
|
10802
10949
|
}
|
|
10803
10950
|
loadAll() {
|
|
10804
|
-
const { fs:
|
|
10951
|
+
const { fs: fs36, skillsDir } = this.deps;
|
|
10805
10952
|
this.cache.clear();
|
|
10806
|
-
const files =
|
|
10953
|
+
const files = fs36.listFiles(skillsDir, ".md");
|
|
10807
10954
|
for (const file of files) {
|
|
10808
10955
|
try {
|
|
10809
|
-
const filePath =
|
|
10810
|
-
const content =
|
|
10956
|
+
const filePath = fs36.joinPath(skillsDir, file);
|
|
10957
|
+
const content = fs36.readFile(filePath);
|
|
10811
10958
|
const def = parseSkillFile(content, filePath);
|
|
10812
10959
|
if (def) {
|
|
10813
10960
|
this.cache.set(def.id, def);
|
|
@@ -10960,7 +11107,7 @@ var SkillManagerCore = class {
|
|
|
10960
11107
|
|
|
10961
11108
|
// ../../packages/node-runtime/src/ai/skill-manager.ts
|
|
10962
11109
|
import * as fs27 from "fs";
|
|
10963
|
-
import * as
|
|
11110
|
+
import * as path21 from "path";
|
|
10964
11111
|
var SKILLS_DIR_NAME = "skills";
|
|
10965
11112
|
var defaultLogger2 = {
|
|
10966
11113
|
info: () => {
|
|
@@ -10989,7 +11136,7 @@ var nodeFs = {
|
|
|
10989
11136
|
return fs27.existsSync(filePath);
|
|
10990
11137
|
},
|
|
10991
11138
|
joinPath(...parts) {
|
|
10992
|
-
return
|
|
11139
|
+
return path21.join(...parts);
|
|
10993
11140
|
}
|
|
10994
11141
|
};
|
|
10995
11142
|
var SkillManager = class {
|
|
@@ -10999,7 +11146,7 @@ var SkillManager = class {
|
|
|
10999
11146
|
this.logger = logger ?? defaultLogger2;
|
|
11000
11147
|
this.core = new SkillManagerCore({
|
|
11001
11148
|
fs: nodeFs,
|
|
11002
|
-
skillsDir:
|
|
11149
|
+
skillsDir: path21.join(aiDataDir, SKILLS_DIR_NAME),
|
|
11003
11150
|
builtinRawSkills: [],
|
|
11004
11151
|
logger: {
|
|
11005
11152
|
info: () => {
|
|
@@ -12861,7 +13008,7 @@ var PLAN_INTENTS = [
|
|
|
12861
13008
|
"mixed"
|
|
12862
13009
|
];
|
|
12863
13010
|
var STREAM_MARKER_HOLDBACK_CHARS = Math.max("</draft>".length, "</plan>".length, "<json>".length) - 1;
|
|
12864
|
-
function
|
|
13011
|
+
function isRecord2(value) {
|
|
12865
13012
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12866
13013
|
}
|
|
12867
13014
|
function isIntent(value) {
|
|
@@ -12883,10 +13030,10 @@ function extractJsonObject4(text) {
|
|
|
12883
13030
|
function parsePlanJson(rawText, availableTools) {
|
|
12884
13031
|
try {
|
|
12885
13032
|
const parsed = JSON.parse(extractJsonObject4(rawText));
|
|
12886
|
-
if (!
|
|
13033
|
+
if (!isRecord2(parsed)) return null;
|
|
12887
13034
|
const title = normalizeString(parsed.title);
|
|
12888
13035
|
if (!title || !isIntent(parsed.intent) || !Array.isArray(parsed.steps)) return null;
|
|
12889
|
-
const steps = parsed.steps.filter(
|
|
13036
|
+
const steps = parsed.steps.filter(isRecord2).map((step) => {
|
|
12890
13037
|
const goal = normalizeString(step.goal);
|
|
12891
13038
|
const evidenceNeeded = normalizeString(step.evidenceNeeded);
|
|
12892
13039
|
if (!goal || !evidenceNeeded) return null;
|
|
@@ -14865,9 +15012,9 @@ var localeMap = {
|
|
|
14865
15012
|
"ja-JP": ja_JP_default,
|
|
14866
15013
|
"zh-TW": zh_TW_default
|
|
14867
15014
|
};
|
|
14868
|
-
function getNestedValue(obj,
|
|
15015
|
+
function getNestedValue(obj, path30) {
|
|
14869
15016
|
let current = obj;
|
|
14870
|
-
for (const segment2 of
|
|
15017
|
+
for (const segment2 of path30.split(".")) {
|
|
14871
15018
|
if (current == null || typeof current !== "object") return void 0;
|
|
14872
15019
|
current = current[segment2];
|
|
14873
15020
|
}
|
|
@@ -15213,19 +15360,26 @@ var LLMConfigStore = class {
|
|
|
15213
15360
|
this.resolveApiKey = deps.resolveApiKey;
|
|
15214
15361
|
this.onStoreLoaded = deps.onStoreLoaded;
|
|
15215
15362
|
}
|
|
15363
|
+
withStoreLock(action) {
|
|
15364
|
+
return this.storage.withLock ? this.storage.withLock("llm-config", action) : action();
|
|
15365
|
+
}
|
|
15216
15366
|
loadStore() {
|
|
15217
15367
|
const store = this.storage.readJson("llm-config");
|
|
15218
15368
|
if (!store) {
|
|
15219
15369
|
return { configs: [], defaultAssistant: null, fastModel: null };
|
|
15220
15370
|
}
|
|
15221
15371
|
this.onStoreLoaded?.(store.configs);
|
|
15222
|
-
const
|
|
15372
|
+
const sanitizedConfigs = store.configs.map((config) => ({
|
|
15373
|
+
...config,
|
|
15374
|
+
apiKey: config.apiKey?.startsWith("enc:") ? "" : config.apiKey || ""
|
|
15375
|
+
}));
|
|
15376
|
+
const resolvedConfigs = this.resolveApiKey ? sanitizedConfigs.map((config) => {
|
|
15223
15377
|
const profileKey = this.resolveApiKey(
|
|
15224
15378
|
config.provider,
|
|
15225
15379
|
config.authProfile
|
|
15226
15380
|
);
|
|
15227
|
-
return { ...config, apiKey: profileKey || config.apiKey
|
|
15228
|
-
}) :
|
|
15381
|
+
return { ...config, apiKey: profileKey || config.apiKey };
|
|
15382
|
+
}) : sanitizedConfigs;
|
|
15229
15383
|
return {
|
|
15230
15384
|
...store,
|
|
15231
15385
|
configs: resolvedConfigs,
|
|
@@ -15277,103 +15431,113 @@ var LLMConfigStore = class {
|
|
|
15277
15431
|
return store.configs.find((c) => c.id === id) || null;
|
|
15278
15432
|
}
|
|
15279
15433
|
addConfig(config) {
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
const now = Date.now();
|
|
15285
|
-
const newConfig = {
|
|
15286
|
-
...config,
|
|
15287
|
-
id: this.generateId(),
|
|
15288
|
-
createdAt: now,
|
|
15289
|
-
updatedAt: now
|
|
15290
|
-
};
|
|
15291
|
-
store.configs.push(newConfig);
|
|
15292
|
-
if (store.configs.length === 1) {
|
|
15293
|
-
store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || "" };
|
|
15294
|
-
}
|
|
15295
|
-
if (newConfig.apiKey && this.onApiKeyCreated) {
|
|
15296
|
-
const profileName = this.onApiKeyCreated(newConfig, newConfig.apiKey);
|
|
15297
|
-
if (profileName) {
|
|
15298
|
-
;
|
|
15299
|
-
newConfig.authProfile = profileName;
|
|
15434
|
+
return this.withStoreLock(() => {
|
|
15435
|
+
const store = this.loadStore();
|
|
15436
|
+
if (store.configs.length >= MAX_CONFIG_COUNT) {
|
|
15437
|
+
return { success: false, error: this.t("llm.maxConfigs", { count: MAX_CONFIG_COUNT }) };
|
|
15300
15438
|
}
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15439
|
+
const now = Date.now();
|
|
15440
|
+
const newConfig = {
|
|
15441
|
+
...config,
|
|
15442
|
+
id: this.generateId(),
|
|
15443
|
+
createdAt: now,
|
|
15444
|
+
updatedAt: now
|
|
15445
|
+
};
|
|
15446
|
+
store.configs.push(newConfig);
|
|
15447
|
+
if (store.configs.length === 1) {
|
|
15448
|
+
store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || "" };
|
|
15449
|
+
}
|
|
15450
|
+
if (newConfig.apiKey && this.onApiKeyCreated) {
|
|
15451
|
+
const profileName = this.onApiKeyCreated(newConfig, newConfig.apiKey);
|
|
15452
|
+
if (profileName) {
|
|
15453
|
+
;
|
|
15454
|
+
newConfig.authProfile = profileName;
|
|
15455
|
+
}
|
|
15456
|
+
}
|
|
15457
|
+
this.saveStore(store);
|
|
15458
|
+
return { success: true, config: { ...newConfig, apiKey: "" } };
|
|
15459
|
+
});
|
|
15304
15460
|
}
|
|
15305
15461
|
updateConfig(id, updates) {
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15462
|
+
return this.withStoreLock(() => {
|
|
15463
|
+
const store = this.loadStore();
|
|
15464
|
+
const index = store.configs.findIndex((c) => c.id === id);
|
|
15465
|
+
if (index === -1) {
|
|
15466
|
+
return { success: false, error: this.t("llm.configNotFound") };
|
|
15467
|
+
}
|
|
15468
|
+
const oldConfig = store.configs[index];
|
|
15469
|
+
const oldProfileName = oldConfig.authProfile;
|
|
15470
|
+
const updated = {
|
|
15471
|
+
...oldConfig,
|
|
15472
|
+
...updates,
|
|
15473
|
+
updatedAt: Date.now()
|
|
15474
|
+
};
|
|
15475
|
+
store.configs[index] = updated;
|
|
15476
|
+
let oldProfileToDelete = null;
|
|
15477
|
+
if (updates.apiKey && this.onApiKeyCreated) {
|
|
15478
|
+
const profileName = this.onApiKeyCreated(updated, updates.apiKey);
|
|
15479
|
+
if (profileName) {
|
|
15480
|
+
;
|
|
15481
|
+
store.configs[index].authProfile = profileName;
|
|
15482
|
+
if (oldProfileName && oldProfileName !== profileName && !isAuthProfileUsed(store.configs, oldProfileName, oldConfig.provider)) {
|
|
15483
|
+
oldProfileToDelete = oldConfig;
|
|
15484
|
+
}
|
|
15327
15485
|
}
|
|
15328
15486
|
}
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15487
|
+
this.saveStore(store);
|
|
15488
|
+
if (oldProfileToDelete) this.onApiKeyDeleted?.(oldProfileToDelete);
|
|
15489
|
+
return { success: true };
|
|
15490
|
+
});
|
|
15333
15491
|
}
|
|
15334
15492
|
deleteConfig(id) {
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
store.defaultAssistant
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
store.fastModel
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15493
|
+
return this.withStoreLock(() => {
|
|
15494
|
+
const store = this.loadStore();
|
|
15495
|
+
const index = store.configs.findIndex((c) => c.id === id);
|
|
15496
|
+
if (index === -1) {
|
|
15497
|
+
return { success: false, error: this.t("llm.configNotFound") };
|
|
15498
|
+
}
|
|
15499
|
+
const deleted = store.configs[index];
|
|
15500
|
+
store.configs.splice(index, 1);
|
|
15501
|
+
const fallback = store.configs[0];
|
|
15502
|
+
if (store.defaultAssistant?.configId === id) {
|
|
15503
|
+
store.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
|
|
15504
|
+
}
|
|
15505
|
+
if (store.fastModel?.configId === id) {
|
|
15506
|
+
store.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || "" } : null;
|
|
15507
|
+
}
|
|
15508
|
+
this.saveStore(store);
|
|
15509
|
+
const deletedProfileName = getAuthProfile(deleted);
|
|
15510
|
+
if (!deletedProfileName || !isAuthProfileUsed(store.configs, deletedProfileName, deleted.provider)) {
|
|
15511
|
+
this.onApiKeyDeleted?.(deleted);
|
|
15512
|
+
}
|
|
15513
|
+
return { success: true };
|
|
15514
|
+
});
|
|
15355
15515
|
}
|
|
15356
15516
|
setDefaultAssistantModel(configId, modelId) {
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
return { success: false, error: this.t("llm.configNotFound") };
|
|
15361
|
-
}
|
|
15362
|
-
store.defaultAssistant = { configId, modelId };
|
|
15363
|
-
this.saveStore(store);
|
|
15364
|
-
return { success: true };
|
|
15365
|
-
}
|
|
15366
|
-
setFastModel(slot) {
|
|
15367
|
-
const store = this.loadStore();
|
|
15368
|
-
if (slot !== null) {
|
|
15369
|
-
const config = store.configs.find((c) => c.id === slot.configId);
|
|
15517
|
+
return this.withStoreLock(() => {
|
|
15518
|
+
const store = this.loadStore();
|
|
15519
|
+
const config = store.configs.find((c) => c.id === configId);
|
|
15370
15520
|
if (!config) {
|
|
15371
15521
|
return { success: false, error: this.t("llm.configNotFound") };
|
|
15372
15522
|
}
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15523
|
+
store.defaultAssistant = { configId, modelId };
|
|
15524
|
+
this.saveStore(store);
|
|
15525
|
+
return { success: true };
|
|
15526
|
+
});
|
|
15527
|
+
}
|
|
15528
|
+
setFastModel(slot) {
|
|
15529
|
+
return this.withStoreLock(() => {
|
|
15530
|
+
const store = this.loadStore();
|
|
15531
|
+
if (slot !== null) {
|
|
15532
|
+
const config = store.configs.find((c) => c.id === slot.configId);
|
|
15533
|
+
if (!config) {
|
|
15534
|
+
return { success: false, error: this.t("llm.configNotFound") };
|
|
15535
|
+
}
|
|
15536
|
+
}
|
|
15537
|
+
store.fastModel = slot;
|
|
15538
|
+
this.saveStore(store);
|
|
15539
|
+
return { success: true };
|
|
15540
|
+
});
|
|
15377
15541
|
}
|
|
15378
15542
|
hasActiveConfig() {
|
|
15379
15543
|
return this.getDefaultAssistantConfig() !== null;
|
|
@@ -15381,20 +15545,30 @@ var LLMConfigStore = class {
|
|
|
15381
15545
|
};
|
|
15382
15546
|
|
|
15383
15547
|
// ../../packages/node-runtime/src/ai/file-config-storage.ts
|
|
15384
|
-
import * as
|
|
15385
|
-
import * as path25 from "path";
|
|
15548
|
+
import * as path22 from "path";
|
|
15386
15549
|
function createFileConfigStorage(baseDir) {
|
|
15550
|
+
const heldLocks = /* @__PURE__ */ new Set();
|
|
15551
|
+
const getFilePath = (key) => path22.join(baseDir, `${key}.json`);
|
|
15387
15552
|
return {
|
|
15388
15553
|
readJson(key) {
|
|
15389
|
-
|
|
15390
|
-
return JSON.parse(fs28.readFileSync(path25.join(baseDir, `${key}.json`), "utf-8"));
|
|
15391
|
-
} catch {
|
|
15392
|
-
return null;
|
|
15393
|
-
}
|
|
15554
|
+
return readJsonFile(getFilePath(key));
|
|
15394
15555
|
},
|
|
15395
15556
|
writeJson(key, data) {
|
|
15396
|
-
|
|
15397
|
-
|
|
15557
|
+
const filePath = getFilePath(key);
|
|
15558
|
+
const write = () => writeJsonFileAtomically(filePath, data);
|
|
15559
|
+
if (heldLocks.has(key)) write();
|
|
15560
|
+
else withFileLock(filePath, write);
|
|
15561
|
+
},
|
|
15562
|
+
withLock(key, action) {
|
|
15563
|
+
if (heldLocks.has(key)) return action();
|
|
15564
|
+
return withFileLock(getFilePath(key), () => {
|
|
15565
|
+
heldLocks.add(key);
|
|
15566
|
+
try {
|
|
15567
|
+
return action();
|
|
15568
|
+
} finally {
|
|
15569
|
+
heldLocks.delete(key);
|
|
15570
|
+
}
|
|
15571
|
+
});
|
|
15398
15572
|
}
|
|
15399
15573
|
};
|
|
15400
15574
|
}
|
|
@@ -15402,23 +15576,57 @@ function createFileConfigStorage(baseDir) {
|
|
|
15402
15576
|
// ../../packages/node-runtime/src/ai/auth-profile-llm-config-store.ts
|
|
15403
15577
|
var defaultDeps = {
|
|
15404
15578
|
resolveApiKey: (provider, authProfile) => resolveApiKey(provider, authProfile) || void 0,
|
|
15579
|
+
loadAuthProfiles,
|
|
15405
15580
|
writeAuthProfile,
|
|
15406
15581
|
deleteAuthProfile
|
|
15407
15582
|
};
|
|
15408
15583
|
function getAuthProfile2(config) {
|
|
15409
15584
|
return config.authProfile;
|
|
15410
15585
|
}
|
|
15411
|
-
function
|
|
15586
|
+
function getNamedAuthProfile(config) {
|
|
15587
|
+
return config.name?.toLowerCase().replace(/\s+/g, "-") || config.provider;
|
|
15588
|
+
}
|
|
15589
|
+
function restoreLegacyAuthProfileReferences(configs, authProfiles) {
|
|
15590
|
+
const usedProfiles = new Set(configs.map(getAuthProfile2).filter((name) => Boolean(name)));
|
|
15591
|
+
for (const config of configs) {
|
|
15592
|
+
if (getAuthProfile2(config)) continue;
|
|
15593
|
+
const namedProfile = getNamedAuthProfile(config);
|
|
15594
|
+
const namedProfileValue = authProfiles.profiles[namedProfile];
|
|
15595
|
+
if (namedProfileValue?.provider === config.provider && !usedProfiles.has(namedProfile)) {
|
|
15596
|
+
;
|
|
15597
|
+
config.authProfile = namedProfile;
|
|
15598
|
+
usedProfiles.add(namedProfile);
|
|
15599
|
+
continue;
|
|
15600
|
+
}
|
|
15601
|
+
const baseName = deriveAuthProfileName(config.provider, config);
|
|
15602
|
+
for (let index = 1; index <= Object.keys(authProfiles.profiles).length; index++) {
|
|
15603
|
+
const candidate = index === 1 ? baseName : `${baseName}-${index}`;
|
|
15604
|
+
const profile = authProfiles.profiles[candidate];
|
|
15605
|
+
if (!profile || profile.provider !== config.provider || usedProfiles.has(candidate)) continue;
|
|
15606
|
+
config.authProfile = candidate;
|
|
15607
|
+
usedProfiles.add(candidate);
|
|
15608
|
+
break;
|
|
15609
|
+
}
|
|
15610
|
+
}
|
|
15611
|
+
}
|
|
15612
|
+
function createAuthProfileLlmConfigStore(storage, deps = {}) {
|
|
15613
|
+
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
15412
15614
|
return new LLMConfigStore(storage, {
|
|
15413
|
-
|
|
15615
|
+
t: deps.t,
|
|
15616
|
+
generateId: deps.generateId,
|
|
15617
|
+
onStoreLoaded: (configs) => {
|
|
15618
|
+
restoreLegacyAuthProfileReferences(configs, resolvedDeps.loadAuthProfiles());
|
|
15619
|
+
deps.onStoreLoaded?.(configs);
|
|
15620
|
+
},
|
|
15621
|
+
resolveApiKey: resolvedDeps.resolveApiKey,
|
|
15414
15622
|
onApiKeyCreated: (config, apiKey) => {
|
|
15415
|
-
const profileName = config
|
|
15416
|
-
|
|
15623
|
+
const profileName = getNamedAuthProfile(config);
|
|
15624
|
+
resolvedDeps.writeAuthProfile(profileName, { type: "api_key", provider: config.provider, key: apiKey });
|
|
15417
15625
|
return profileName;
|
|
15418
15626
|
},
|
|
15419
15627
|
onApiKeyDeleted: (config) => {
|
|
15420
15628
|
const profileName = getAuthProfile2(config);
|
|
15421
|
-
if (profileName)
|
|
15629
|
+
if (profileName) resolvedDeps.deleteAuthProfile(profileName);
|
|
15422
15630
|
}
|
|
15423
15631
|
});
|
|
15424
15632
|
}
|
|
@@ -15432,43 +15640,52 @@ var CustomProviderStore = class {
|
|
|
15432
15640
|
this.storage = storage;
|
|
15433
15641
|
}
|
|
15434
15642
|
storage;
|
|
15643
|
+
withLock(action) {
|
|
15644
|
+
return this.storage.withLock ? this.storage.withLock("custom-providers", action) : action();
|
|
15645
|
+
}
|
|
15435
15646
|
getAll() {
|
|
15436
15647
|
return this.storage.readJson("custom-providers") ?? [];
|
|
15437
15648
|
}
|
|
15438
15649
|
add(input) {
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15650
|
+
return this.withLock(() => {
|
|
15651
|
+
const providers = this.getAll();
|
|
15652
|
+
const newProvider = {
|
|
15653
|
+
id: generateCustomId(),
|
|
15654
|
+
name: input.name,
|
|
15655
|
+
kind: input.kind || "openai-compatible",
|
|
15656
|
+
defaultBaseUrl: input.defaultBaseUrl,
|
|
15657
|
+
authMode: "api-key",
|
|
15658
|
+
supportsCustomModels: input.supportsCustomModels ?? true,
|
|
15659
|
+
modelIds: input.modelIds ?? [],
|
|
15660
|
+
builtin: false,
|
|
15661
|
+
enabledByDefault: false,
|
|
15662
|
+
website: input.website,
|
|
15663
|
+
consoleUrl: input.consoleUrl
|
|
15664
|
+
};
|
|
15665
|
+
providers.push(newProvider);
|
|
15666
|
+
this.storage.writeJson("custom-providers", providers);
|
|
15667
|
+
return newProvider;
|
|
15668
|
+
});
|
|
15456
15669
|
}
|
|
15457
15670
|
update(id, updates) {
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15671
|
+
return this.withLock(() => {
|
|
15672
|
+
const providers = this.getAll();
|
|
15673
|
+
const index = providers.findIndex((p) => p.id === id);
|
|
15674
|
+
if (index === -1) return { success: false, error: "Custom provider not found" };
|
|
15675
|
+
providers[index] = { ...providers[index], ...updates };
|
|
15676
|
+
this.storage.writeJson("custom-providers", providers);
|
|
15677
|
+
return { success: true };
|
|
15678
|
+
});
|
|
15464
15679
|
}
|
|
15465
15680
|
delete(id) {
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15681
|
+
return this.withLock(() => {
|
|
15682
|
+
const providers = this.getAll();
|
|
15683
|
+
const index = providers.findIndex((p) => p.id === id);
|
|
15684
|
+
if (index === -1) return { success: false, error: "Custom provider not found" };
|
|
15685
|
+
providers.splice(index, 1);
|
|
15686
|
+
this.storage.writeJson("custom-providers", providers);
|
|
15687
|
+
return { success: true };
|
|
15688
|
+
});
|
|
15472
15689
|
}
|
|
15473
15690
|
};
|
|
15474
15691
|
var CustomModelStore = class {
|
|
@@ -15476,51 +15693,96 @@ var CustomModelStore = class {
|
|
|
15476
15693
|
this.storage = storage;
|
|
15477
15694
|
}
|
|
15478
15695
|
storage;
|
|
15696
|
+
withLock(action) {
|
|
15697
|
+
return this.storage.withLock ? this.storage.withLock("custom-models", action) : action();
|
|
15698
|
+
}
|
|
15479
15699
|
getAll() {
|
|
15480
15700
|
return this.storage.readJson("custom-models") ?? [];
|
|
15481
15701
|
}
|
|
15482
15702
|
add(input) {
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15703
|
+
return this.withLock(() => {
|
|
15704
|
+
const models = this.getAll();
|
|
15705
|
+
if (models.find((m) => m.id === input.id && m.providerId === input.providerId)) {
|
|
15706
|
+
return {
|
|
15707
|
+
success: false,
|
|
15708
|
+
error: `Model "${input.id}" already exists under provider "${input.providerId}"`
|
|
15709
|
+
};
|
|
15710
|
+
}
|
|
15711
|
+
const newModel = {
|
|
15712
|
+
id: input.id,
|
|
15713
|
+
providerId: input.providerId,
|
|
15714
|
+
name: input.name,
|
|
15715
|
+
description: input.description,
|
|
15716
|
+
contextWindow: input.contextWindow,
|
|
15717
|
+
capabilities: input.capabilities ?? ["chat"],
|
|
15718
|
+
recommendedFor: input.recommendedFor ?? ["chat"],
|
|
15719
|
+
status: input.status ?? "stable",
|
|
15720
|
+
builtin: false,
|
|
15721
|
+
editable: true
|
|
15488
15722
|
};
|
|
15489
|
-
|
|
15490
|
-
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
name: input.name,
|
|
15494
|
-
description: input.description,
|
|
15495
|
-
contextWindow: input.contextWindow,
|
|
15496
|
-
capabilities: input.capabilities ?? ["chat"],
|
|
15497
|
-
recommendedFor: input.recommendedFor ?? ["chat"],
|
|
15498
|
-
status: input.status ?? "stable",
|
|
15499
|
-
builtin: false,
|
|
15500
|
-
editable: true
|
|
15501
|
-
};
|
|
15502
|
-
models.push(newModel);
|
|
15503
|
-
this.storage.writeJson("custom-models", models);
|
|
15504
|
-
return { success: true, model: newModel };
|
|
15723
|
+
models.push(newModel);
|
|
15724
|
+
this.storage.writeJson("custom-models", models);
|
|
15725
|
+
return { success: true, model: newModel };
|
|
15726
|
+
});
|
|
15505
15727
|
}
|
|
15506
15728
|
update(providerId, modelId, updates) {
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15729
|
+
return this.withLock(() => {
|
|
15730
|
+
const models = this.getAll();
|
|
15731
|
+
const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
|
|
15732
|
+
if (index === -1) return { success: false, error: "Custom model not found" };
|
|
15733
|
+
models[index] = { ...models[index], ...updates };
|
|
15734
|
+
this.storage.writeJson("custom-models", models);
|
|
15735
|
+
return { success: true };
|
|
15736
|
+
});
|
|
15513
15737
|
}
|
|
15514
15738
|
delete(providerId, modelId) {
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15739
|
+
return this.withLock(() => {
|
|
15740
|
+
const models = this.getAll();
|
|
15741
|
+
const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId);
|
|
15742
|
+
if (index === -1) return { success: false, error: "Custom model not found" };
|
|
15743
|
+
models.splice(index, 1);
|
|
15744
|
+
this.storage.writeJson("custom-models", models);
|
|
15745
|
+
return { success: true };
|
|
15746
|
+
});
|
|
15521
15747
|
}
|
|
15522
15748
|
};
|
|
15523
15749
|
|
|
15750
|
+
// ../../packages/node-runtime/src/ai/llm-runtime-stores.ts
|
|
15751
|
+
function ensureLegacyCustomModels(configs, customModelStore) {
|
|
15752
|
+
for (const config of configs) {
|
|
15753
|
+
const providerId = config.provider;
|
|
15754
|
+
if (config.model && getBuiltinProviderById(providerId) && !getBuiltinModelById(providerId, config.model)) {
|
|
15755
|
+
customModelStore.add({
|
|
15756
|
+
id: config.model,
|
|
15757
|
+
providerId,
|
|
15758
|
+
name: config.model,
|
|
15759
|
+
capabilities: ["chat"],
|
|
15760
|
+
recommendedFor: ["chat"],
|
|
15761
|
+
status: "stable"
|
|
15762
|
+
});
|
|
15763
|
+
}
|
|
15764
|
+
}
|
|
15765
|
+
}
|
|
15766
|
+
function createLlmRuntimeStores(aiDataDir, options2 = {}) {
|
|
15767
|
+
const storage = createFileConfigStorage(aiDataDir);
|
|
15768
|
+
const customProviderStore = new CustomProviderStore(storage);
|
|
15769
|
+
const customModelStore = new CustomModelStore(storage);
|
|
15770
|
+
const llmConfigStore = createAuthProfileLlmConfigStore(storage, {
|
|
15771
|
+
...options2.authProfileDeps,
|
|
15772
|
+
t: options2.t,
|
|
15773
|
+
generateId: options2.generateId,
|
|
15774
|
+
onStoreLoaded: (configs) => {
|
|
15775
|
+
ensureLegacyCustomModels(configs, customModelStore);
|
|
15776
|
+
options2.onStoreLoaded?.(configs);
|
|
15777
|
+
}
|
|
15778
|
+
});
|
|
15779
|
+
return {
|
|
15780
|
+
llmConfigStore,
|
|
15781
|
+
customProviderStore,
|
|
15782
|
+
customModelStore
|
|
15783
|
+
};
|
|
15784
|
+
}
|
|
15785
|
+
|
|
15524
15786
|
// ../../packages/node-runtime/src/ai/llm-builder.ts
|
|
15525
15787
|
function normalizeAnthropicBaseUrl(url) {
|
|
15526
15788
|
return url.replace(/\/v1\/?$/, "");
|
|
@@ -16133,7 +16395,7 @@ function exportAsJson(db, messages, sessionName, sourceSessionId) {
|
|
|
16133
16395
|
).all();
|
|
16134
16396
|
const data = {
|
|
16135
16397
|
chatlab: {
|
|
16136
|
-
version:
|
|
16398
|
+
version: CHATLAB_FORMAT_VERSION,
|
|
16137
16399
|
exportedAt: Math.floor(Date.now() / 1e3),
|
|
16138
16400
|
generator: "ChatLab"
|
|
16139
16401
|
},
|
|
@@ -16242,8 +16504,8 @@ function exportWithFormat(params, openDatabase) {
|
|
|
16242
16504
|
}
|
|
16243
16505
|
|
|
16244
16506
|
// ../../packages/node-runtime/src/merger/temp-db.ts
|
|
16245
|
-
import * as
|
|
16246
|
-
import * as
|
|
16507
|
+
import * as fs28 from "fs";
|
|
16508
|
+
import * as path23 from "path";
|
|
16247
16509
|
var TEMP_DB_SCHEMA = `
|
|
16248
16510
|
CREATE TABLE IF NOT EXISTS meta (
|
|
16249
16511
|
name TEXT NOT NULL,
|
|
@@ -16458,7 +16720,7 @@ function exportSessionToJson(db) {
|
|
|
16458
16720
|
).all();
|
|
16459
16721
|
return {
|
|
16460
16722
|
chatlab: {
|
|
16461
|
-
version:
|
|
16723
|
+
version: CHATLAB_FORMAT_VERSION,
|
|
16462
16724
|
exportedAt: Math.floor(Date.now() / 1e3),
|
|
16463
16725
|
generator: "ChatLab Export",
|
|
16464
16726
|
description: `Exported from session: ${meta.name}`
|
|
@@ -16488,21 +16750,21 @@ function exportSessionToJson(db) {
|
|
|
16488
16750
|
}
|
|
16489
16751
|
function deleteTempDatabase(dbPath) {
|
|
16490
16752
|
try {
|
|
16491
|
-
if (
|
|
16753
|
+
if (fs28.existsSync(dbPath)) fs28.unlinkSync(dbPath);
|
|
16492
16754
|
const walPath = dbPath + "-wal";
|
|
16493
16755
|
const shmPath = dbPath + "-shm";
|
|
16494
|
-
if (
|
|
16495
|
-
if (
|
|
16756
|
+
if (fs28.existsSync(walPath)) fs28.unlinkSync(walPath);
|
|
16757
|
+
if (fs28.existsSync(shmPath)) fs28.unlinkSync(shmPath);
|
|
16496
16758
|
} catch {
|
|
16497
16759
|
}
|
|
16498
16760
|
}
|
|
16499
16761
|
function cleanupTempDatabases(tempDir) {
|
|
16500
16762
|
try {
|
|
16501
|
-
if (!
|
|
16502
|
-
const files =
|
|
16763
|
+
if (!fs28.existsSync(tempDir)) return;
|
|
16764
|
+
const files = fs28.readdirSync(tempDir);
|
|
16503
16765
|
for (const file of files) {
|
|
16504
16766
|
if (file.startsWith("merge_") && file.endsWith(".db")) {
|
|
16505
|
-
deleteTempDatabase(
|
|
16767
|
+
deleteTempDatabase(path23.join(tempDir, file));
|
|
16506
16768
|
}
|
|
16507
16769
|
}
|
|
16508
16770
|
} catch {
|
|
@@ -16887,8 +17149,8 @@ function clearSessionOwner(adapter, sessionId) {
|
|
|
16887
17149
|
}
|
|
16888
17150
|
|
|
16889
17151
|
// ../../packages/node-runtime/src/services/contacts/overrides.ts
|
|
16890
|
-
import
|
|
16891
|
-
import
|
|
17152
|
+
import fs29 from "fs";
|
|
17153
|
+
import path24 from "path";
|
|
16892
17154
|
var CONTACT_OVERRIDES_FILE_NAME = "contact-overrides.json";
|
|
16893
17155
|
var CONTACT_OVERRIDES_TMP_PREFIX = "contact-overrides.tmp-";
|
|
16894
17156
|
var CONTACT_OVERRIDES_VERSION = 1;
|
|
@@ -16899,30 +17161,30 @@ function createEmptyContactOverridesFile() {
|
|
|
16899
17161
|
};
|
|
16900
17162
|
}
|
|
16901
17163
|
function getContactOverridesPath(snapshotDir) {
|
|
16902
|
-
return
|
|
17164
|
+
return path24.join(snapshotDir, CONTACT_OVERRIDES_FILE_NAME);
|
|
16903
17165
|
}
|
|
16904
17166
|
function readContactOverrides(snapshotDir) {
|
|
16905
17167
|
const filePath = getContactOverridesPath(snapshotDir);
|
|
16906
|
-
if (!
|
|
17168
|
+
if (!fs29.existsSync(filePath)) return createEmptyContactOverridesFile();
|
|
16907
17169
|
try {
|
|
16908
|
-
return normalizeContactOverrides(JSON.parse(
|
|
17170
|
+
return normalizeContactOverrides(JSON.parse(fs29.readFileSync(filePath, "utf-8")));
|
|
16909
17171
|
} catch (error) {
|
|
16910
17172
|
appLogger.warn("contacts", "contact overrides file is unreadable", error);
|
|
16911
17173
|
return createEmptyContactOverridesFile();
|
|
16912
17174
|
}
|
|
16913
17175
|
}
|
|
16914
17176
|
function writeContactOverrides(snapshotDir, overrides) {
|
|
16915
|
-
if (!
|
|
16916
|
-
const tmpPath =
|
|
16917
|
-
|
|
16918
|
-
|
|
17177
|
+
if (!fs29.existsSync(snapshotDir)) fs29.mkdirSync(snapshotDir, { recursive: true });
|
|
17178
|
+
const tmpPath = path24.join(snapshotDir, `${CONTACT_OVERRIDES_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
17179
|
+
fs29.writeFileSync(tmpPath, JSON.stringify(normalizeContactOverrides(overrides), null, 2), "utf-8");
|
|
17180
|
+
fs29.renameSync(tmpPath, getContactOverridesPath(snapshotDir));
|
|
16919
17181
|
}
|
|
16920
17182
|
function normalizeContactOverrides(value) {
|
|
16921
|
-
const input =
|
|
16922
|
-
const manualFriendsInput =
|
|
17183
|
+
const input = isRecord3(value) ? value : {};
|
|
17184
|
+
const manualFriendsInput = isRecord3(input.manualFriends) ? input.manualFriends : {};
|
|
16923
17185
|
const manualFriends = {};
|
|
16924
17186
|
for (const [key, entry] of Object.entries(manualFriendsInput)) {
|
|
16925
|
-
if (!key || !
|
|
17187
|
+
if (!key || !isRecord3(entry)) continue;
|
|
16926
17188
|
const createdAt = Number.isFinite(entry.createdAt) ? Number(entry.createdAt) : Date.now();
|
|
16927
17189
|
const updatedAt = Number.isFinite(entry.updatedAt) ? Number(entry.updatedAt) : createdAt;
|
|
16928
17190
|
manualFriends[key] = { key, createdAt, updatedAt };
|
|
@@ -16932,7 +17194,7 @@ function normalizeContactOverrides(value) {
|
|
|
16932
17194
|
manualFriends
|
|
16933
17195
|
};
|
|
16934
17196
|
}
|
|
16935
|
-
function
|
|
17197
|
+
function isRecord3(value) {
|
|
16936
17198
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16937
17199
|
}
|
|
16938
17200
|
|
|
@@ -16950,23 +17212,23 @@ function buildContactsSignature(adapter, timeRangePreset) {
|
|
|
16950
17212
|
}
|
|
16951
17213
|
|
|
16952
17214
|
// ../../packages/node-runtime/src/services/contacts/snapshot.ts
|
|
16953
|
-
import
|
|
16954
|
-
import
|
|
17215
|
+
import fs30 from "fs";
|
|
17216
|
+
import path25 from "path";
|
|
16955
17217
|
var CONTACTS_SNAPSHOT_TMP_PREFIX = "contacts-snapshot.tmp-";
|
|
16956
17218
|
function getContactsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
16957
17219
|
const preset = normalizeContactsTimeRangePreset(timeRangePreset);
|
|
16958
|
-
return
|
|
17220
|
+
return path25.join(snapshotDir, `contacts-snapshot-${preset}.json`);
|
|
16959
17221
|
}
|
|
16960
17222
|
function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
16961
17223
|
const snapshotPath = getContactsSnapshotPath(snapshotDir, timeRangePreset);
|
|
16962
|
-
if (!
|
|
17224
|
+
if (!fs30.existsSync(snapshotPath)) return null;
|
|
16963
17225
|
try {
|
|
16964
|
-
return JSON.parse(
|
|
17226
|
+
return JSON.parse(fs30.readFileSync(snapshotPath, "utf-8"));
|
|
16965
17227
|
} catch (error) {
|
|
16966
17228
|
const ts = options2.now?.() ?? Date.now();
|
|
16967
|
-
const backupPath =
|
|
17229
|
+
const backupPath = path25.join(snapshotDir, `contacts-snapshot.corrupt-${ts}.json`);
|
|
16968
17230
|
try {
|
|
16969
|
-
|
|
17231
|
+
fs30.renameSync(snapshotPath, backupPath);
|
|
16970
17232
|
} catch (renameError) {
|
|
16971
17233
|
appLogger.warn("contacts", "failed to backup corrupt contacts snapshot", renameError);
|
|
16972
17234
|
}
|
|
@@ -16975,17 +17237,17 @@ function readContactsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
|
16975
17237
|
}
|
|
16976
17238
|
}
|
|
16977
17239
|
function writeContactsSnapshot(snapshotDir, snapshot) {
|
|
16978
|
-
if (!
|
|
16979
|
-
const tmpPath =
|
|
16980
|
-
|
|
16981
|
-
|
|
17240
|
+
if (!fs30.existsSync(snapshotDir)) fs30.mkdirSync(snapshotDir, { recursive: true });
|
|
17241
|
+
const tmpPath = path25.join(snapshotDir, `${CONTACTS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
17242
|
+
fs30.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
17243
|
+
fs30.renameSync(tmpPath, getContactsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
16982
17244
|
}
|
|
16983
17245
|
function cleanupContactsSnapshotTempFiles(snapshotDir) {
|
|
16984
|
-
if (!
|
|
16985
|
-
for (const name of
|
|
17246
|
+
if (!fs30.existsSync(snapshotDir)) return;
|
|
17247
|
+
for (const name of fs30.readdirSync(snapshotDir)) {
|
|
16986
17248
|
if (!name.startsWith(CONTACTS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
16987
17249
|
try {
|
|
16988
|
-
|
|
17250
|
+
fs30.rmSync(path25.join(snapshotDir, name), { force: true });
|
|
16989
17251
|
} catch (error) {
|
|
16990
17252
|
appLogger.warn("contacts", "failed to remove contacts snapshot temp file", error);
|
|
16991
17253
|
}
|
|
@@ -17483,23 +17745,23 @@ function buildPeopleRelationshipsSignature(adapter, timeRangePreset) {
|
|
|
17483
17745
|
}
|
|
17484
17746
|
|
|
17485
17747
|
// ../../packages/node-runtime/src/services/people/relationships/snapshot.ts
|
|
17486
|
-
import
|
|
17487
|
-
import
|
|
17748
|
+
import fs31 from "fs";
|
|
17749
|
+
import path26 from "path";
|
|
17488
17750
|
var PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX = "people-relationships-snapshot.tmp-";
|
|
17489
17751
|
function getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset) {
|
|
17490
17752
|
const preset = normalizePeopleRelationshipsTimeRangePreset(timeRangePreset);
|
|
17491
|
-
return
|
|
17753
|
+
return path26.join(snapshotDir, `graph-snapshot-${preset}.json`);
|
|
17492
17754
|
}
|
|
17493
17755
|
function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2 = {}) {
|
|
17494
17756
|
const snapshotPath = getPeopleRelationshipsSnapshotPath(snapshotDir, timeRangePreset);
|
|
17495
|
-
if (!
|
|
17757
|
+
if (!fs31.existsSync(snapshotPath)) return null;
|
|
17496
17758
|
try {
|
|
17497
|
-
return JSON.parse(
|
|
17759
|
+
return JSON.parse(fs31.readFileSync(snapshotPath, "utf-8"));
|
|
17498
17760
|
} catch (error) {
|
|
17499
17761
|
const ts = options2.now?.() ?? Date.now();
|
|
17500
|
-
const backupPath =
|
|
17762
|
+
const backupPath = path26.join(snapshotDir, `graph-snapshot.corrupt-${ts}.json`);
|
|
17501
17763
|
try {
|
|
17502
|
-
|
|
17764
|
+
fs31.renameSync(snapshotPath, backupPath);
|
|
17503
17765
|
} catch (renameError) {
|
|
17504
17766
|
appLogger.warn("people-relationships", "failed to backup corrupt people relationships snapshot", renameError);
|
|
17505
17767
|
}
|
|
@@ -17508,17 +17770,17 @@ function readPeopleRelationshipsSnapshot(snapshotDir, timeRangePreset, options2
|
|
|
17508
17770
|
}
|
|
17509
17771
|
}
|
|
17510
17772
|
function writePeopleRelationshipsSnapshot(snapshotDir, snapshot) {
|
|
17511
|
-
if (!
|
|
17512
|
-
const tmpPath =
|
|
17513
|
-
|
|
17514
|
-
|
|
17773
|
+
if (!fs31.existsSync(snapshotDir)) fs31.mkdirSync(snapshotDir, { recursive: true });
|
|
17774
|
+
const tmpPath = path26.join(snapshotDir, `${PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX}${process.pid}-${Date.now()}`);
|
|
17775
|
+
fs31.writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
17776
|
+
fs31.renameSync(tmpPath, getPeopleRelationshipsSnapshotPath(snapshotDir, snapshot.timeRange.preset));
|
|
17515
17777
|
}
|
|
17516
17778
|
function cleanupPeopleRelationshipsSnapshotTempFiles(snapshotDir) {
|
|
17517
|
-
if (!
|
|
17518
|
-
for (const name of
|
|
17779
|
+
if (!fs31.existsSync(snapshotDir)) return;
|
|
17780
|
+
for (const name of fs31.readdirSync(snapshotDir)) {
|
|
17519
17781
|
if (!name.startsWith(PEOPLE_RELATIONSHIPS_SNAPSHOT_TMP_PREFIX)) continue;
|
|
17520
17782
|
try {
|
|
17521
|
-
|
|
17783
|
+
fs31.rmSync(path26.join(snapshotDir, name), { force: true });
|
|
17522
17784
|
} catch (error) {
|
|
17523
17785
|
appLogger.warn("people-relationships", "failed to remove people relationships snapshot temp file", error);
|
|
17524
17786
|
}
|
|
@@ -17654,8 +17916,10 @@ var DefaultPeopleRelationshipsService = class {
|
|
|
17654
17916
|
const snapshot = this.getSnapshotForResponse(timeRangePreset);
|
|
17655
17917
|
const status = this.getCacheStatus(signature, timeRangePreset);
|
|
17656
17918
|
const includeSnapshot = shouldIncludeSnapshot(status, options2.acceptStale);
|
|
17657
|
-
const
|
|
17658
|
-
const
|
|
17919
|
+
const graphScope = normalizePeopleRelationshipsGraphScope(options2.graphScope);
|
|
17920
|
+
const scopedSnapshot = snapshot ? buildNeighborhoodSnapshotForScope(snapshot, graphScope) : null;
|
|
17921
|
+
const graph = includeSnapshot && scopedSnapshot ? buildPeopleRelationshipsNeighborhoodGraph(scopedSnapshot, key) : emptyGraph();
|
|
17922
|
+
const contact = includeSnapshot ? scopedSnapshot?.nodes.find((node) => node.key === key) ?? null : null;
|
|
17659
17923
|
return {
|
|
17660
17924
|
contact,
|
|
17661
17925
|
graph,
|
|
@@ -17787,7 +18051,7 @@ var DefaultPeopleRelationshipsService = class {
|
|
|
17787
18051
|
const graph = includeSnapshot && snapshot ? buildGraphForScope(snapshot, graphScope) : emptyGraph();
|
|
17788
18052
|
return {
|
|
17789
18053
|
graph,
|
|
17790
|
-
searchResults: includeSnapshot && snapshot ? buildSearchResults(snapshot, options2.query, graph) : [],
|
|
18054
|
+
searchResults: includeSnapshot && snapshot ? buildSearchResults(snapshot, options2.query, graph, graphScope) : [],
|
|
17791
18055
|
diagnostics: includeSnapshot ? sanitizePeopleRelationshipsDiagnostics(snapshot?.diagnostics ?? createEmptyDiagnostics()) : createEmptyDiagnostics(),
|
|
17792
18056
|
algorithmVersion: includeSnapshot ? snapshot?.algorithmVersion ?? PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION : PEOPLE_RELATIONSHIPS_ALGORITHM_VERSION,
|
|
17793
18057
|
timeRange: snapshot?.timeRange ?? resolvePeopleRelationshipsTimeRange(timeRangePreset, null),
|
|
@@ -17845,6 +18109,16 @@ function buildFriendsRelationshipsGraph(snapshot) {
|
|
|
17845
18109
|
communities: filterCommunitiesForNodes(snapshot.communities, nodes)
|
|
17846
18110
|
};
|
|
17847
18111
|
}
|
|
18112
|
+
function buildNeighborhoodSnapshotForScope(snapshot, scope) {
|
|
18113
|
+
if (scope !== "friends") return snapshot;
|
|
18114
|
+
const graph = buildFriendsRelationshipsGraph(snapshot);
|
|
18115
|
+
return {
|
|
18116
|
+
...snapshot,
|
|
18117
|
+
nodes: graph.nodes,
|
|
18118
|
+
edges: graph.edges,
|
|
18119
|
+
communities: graph.communities
|
|
18120
|
+
};
|
|
18121
|
+
}
|
|
17848
18122
|
function buildCloseRelationshipsGraph(snapshot) {
|
|
17849
18123
|
const selectedKeys = /* @__PURE__ */ new Set();
|
|
17850
18124
|
for (const node of snapshot.nodes) {
|
|
@@ -17885,11 +18159,12 @@ function filterCommunitiesForNodes(communities, nodes) {
|
|
|
17885
18159
|
size: communitySizes.get(community.id) ?? community.size
|
|
17886
18160
|
}));
|
|
17887
18161
|
}
|
|
17888
|
-
function buildSearchResults(snapshot, queryInput, graph) {
|
|
18162
|
+
function buildSearchResults(snapshot, queryInput, graph, graphScope) {
|
|
17889
18163
|
const query = queryInput?.trim().toLowerCase() ?? "";
|
|
17890
18164
|
if (!query) return [];
|
|
17891
18165
|
const visibleKeys = new Set(graph.nodes.map((node) => node.key));
|
|
17892
|
-
|
|
18166
|
+
const searchableNodes = graphScope === "friends" ? graph.nodes : snapshot.nodes;
|
|
18167
|
+
return searchableNodes.filter((node) => node.searchText.includes(query)).sort(compareNodes).slice(0, snapshot.limits.searchResultLimit).map((node) => ({
|
|
17893
18168
|
key: node.key,
|
|
17894
18169
|
kind: node.kind,
|
|
17895
18170
|
displayName: node.displayName,
|
|
@@ -18040,7 +18315,7 @@ __export(relationships_exports, {
|
|
|
18040
18315
|
});
|
|
18041
18316
|
|
|
18042
18317
|
// ../../packages/node-runtime/src/services/global-insight/service.ts
|
|
18043
|
-
import
|
|
18318
|
+
import fs32 from "fs";
|
|
18044
18319
|
|
|
18045
18320
|
// ../../packages/node-runtime/src/services/global-insight/signature.ts
|
|
18046
18321
|
function buildAnnualSummarySignature(adapter, range) {
|
|
@@ -18273,6 +18548,7 @@ var DefaultGlobalInsightService = class {
|
|
|
18273
18548
|
latestDataYear: includeSnapshot ? snapshot?.latestDataYear ?? null : null,
|
|
18274
18549
|
metrics: includeSnapshot ? snapshot?.metrics ?? null : null,
|
|
18275
18550
|
monthlyActivity: includeSnapshot ? snapshot?.monthlyActivity ?? [] : [],
|
|
18551
|
+
monthlyDirectContacts: includeSnapshot ? snapshot?.monthlyDirectContacts ?? [] : [],
|
|
18276
18552
|
dailyActivity: includeSnapshot ? snapshot?.dailyActivity ?? [] : [],
|
|
18277
18553
|
messageTypes: includeSnapshot ? snapshot?.messageTypes ?? [] : [],
|
|
18278
18554
|
textLength: includeSnapshot ? snapshot?.textLength ?? null : null,
|
|
@@ -18293,7 +18569,7 @@ var DefaultGlobalInsightService = class {
|
|
|
18293
18569
|
}
|
|
18294
18570
|
getSnapshot(range) {
|
|
18295
18571
|
const key = toAnnualSummaryRangeKey(range);
|
|
18296
|
-
if (this.snapshots.has(key) && !
|
|
18572
|
+
if (this.snapshots.has(key) && !fs32.existsSync(getAnnualSummarySnapshotPath(this.snapshotDir, range))) {
|
|
18297
18573
|
this.snapshots.delete(key);
|
|
18298
18574
|
}
|
|
18299
18575
|
if (!this.snapshots.has(key)) {
|
|
@@ -18341,8 +18617,8 @@ __export(global_insight_exports, {
|
|
|
18341
18617
|
});
|
|
18342
18618
|
|
|
18343
18619
|
// ../../packages/node-runtime/src/semantic-index/config.ts
|
|
18344
|
-
import
|
|
18345
|
-
import
|
|
18620
|
+
import fs33 from "fs";
|
|
18621
|
+
import path27 from "path";
|
|
18346
18622
|
var DEFAULT_SEMANTIC_INDEX_MODEL_DOWNLOAD_SOURCE = "huggingface";
|
|
18347
18623
|
var SEMANTIC_INDEX_CONFIG_VERSION = 1;
|
|
18348
18624
|
var SEARCH_MAX_RESULTS_DEFAULT = 10;
|
|
@@ -18431,9 +18707,9 @@ var SemanticIndexConfigStore = class {
|
|
|
18431
18707
|
return this.set({ ...this.get(), enabled });
|
|
18432
18708
|
}
|
|
18433
18709
|
get() {
|
|
18434
|
-
if (!
|
|
18710
|
+
if (!fs33.existsSync(this.filePath)) return defaultSemanticIndexConfig();
|
|
18435
18711
|
try {
|
|
18436
|
-
const raw = JSON.parse(
|
|
18712
|
+
const raw = JSON.parse(fs33.readFileSync(this.filePath, "utf-8"));
|
|
18437
18713
|
return normalizeConfig(raw);
|
|
18438
18714
|
} catch {
|
|
18439
18715
|
return defaultSemanticIndexConfig();
|
|
@@ -18441,9 +18717,9 @@ var SemanticIndexConfigStore = class {
|
|
|
18441
18717
|
}
|
|
18442
18718
|
set(config) {
|
|
18443
18719
|
const normalized = normalizeConfig(config);
|
|
18444
|
-
const dir =
|
|
18445
|
-
if (!
|
|
18446
|
-
|
|
18720
|
+
const dir = path27.dirname(this.filePath);
|
|
18721
|
+
if (!fs33.existsSync(dir)) fs33.mkdirSync(dir, { recursive: true });
|
|
18722
|
+
fs33.writeFileSync(this.filePath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
18447
18723
|
return normalized;
|
|
18448
18724
|
}
|
|
18449
18725
|
resolveModelId() {
|
|
@@ -18452,7 +18728,7 @@ var SemanticIndexConfigStore = class {
|
|
|
18452
18728
|
};
|
|
18453
18729
|
|
|
18454
18730
|
// ../../packages/node-runtime/src/semantic-index/service.ts
|
|
18455
|
-
import
|
|
18731
|
+
import path28 from "path";
|
|
18456
18732
|
|
|
18457
18733
|
// ../../packages/node-runtime/src/semantic-index/chunker-config.ts
|
|
18458
18734
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -19983,16 +20259,16 @@ function resolveSearchToolEvidenceTokens(maxResultTokens) {
|
|
|
19983
20259
|
return maxResultTokens && maxResultTokens > 0 ? Math.min(SEARCH_TOOL_EVIDENCE_TOKENS, maxResultTokens) : SEARCH_TOOL_EVIDENCE_TOKENS;
|
|
19984
20260
|
}
|
|
19985
20261
|
function sessionIdFromDbPath(dbPath) {
|
|
19986
|
-
return
|
|
20262
|
+
return path28.basename(dbPath, ".db");
|
|
19987
20263
|
}
|
|
19988
20264
|
var SEMANTIC_INDEX_DB_FILE = "embedding_index.db";
|
|
19989
20265
|
var SEMANTIC_INDEX_CONFIG_FILE = "semantic-index-config.json";
|
|
19990
20266
|
function createSemanticIndexService(params) {
|
|
19991
20267
|
const { pathProvider } = params;
|
|
19992
20268
|
return new SemanticIndexService({
|
|
19993
|
-
vectorDbPath:
|
|
19994
|
-
configPath:
|
|
19995
|
-
modelsCacheDir:
|
|
20269
|
+
vectorDbPath: path28.join(pathProvider.getVectorDir(), SEMANTIC_INDEX_DB_FILE),
|
|
20270
|
+
configPath: path28.join(pathProvider.getAiDataDir(), SEMANTIC_INDEX_CONFIG_FILE),
|
|
20271
|
+
modelsCacheDir: path28.join(pathProvider.getAiDataDir(), "models", "semantic-index"),
|
|
19996
20272
|
modelDownloadProxyUrl: params.modelDownloadProxyUrl,
|
|
19997
20273
|
sessionAdapter: params.sessionAdapter,
|
|
19998
20274
|
nativeBinding: params.nativeBinding,
|
|
@@ -20542,8 +20818,8 @@ ${text}`;
|
|
|
20542
20818
|
};
|
|
20543
20819
|
|
|
20544
20820
|
// ../../packages/node-runtime/src/services/merge-cache.ts
|
|
20545
|
-
import * as
|
|
20546
|
-
import * as
|
|
20821
|
+
import * as fs34 from "fs";
|
|
20822
|
+
import * as path29 from "path";
|
|
20547
20823
|
import * as crypto from "crypto";
|
|
20548
20824
|
var MergeSessionCache = class {
|
|
20549
20825
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -20552,15 +20828,15 @@ var MergeSessionCache = class {
|
|
|
20552
20828
|
constructor(pathProvider, options2) {
|
|
20553
20829
|
this.tempDir = pathProvider.getTempDir();
|
|
20554
20830
|
this.nativeBinding = options2?.nativeBinding;
|
|
20555
|
-
if (!
|
|
20556
|
-
|
|
20831
|
+
if (!fs34.existsSync(this.tempDir)) {
|
|
20832
|
+
fs34.mkdirSync(this.tempDir, { recursive: true });
|
|
20557
20833
|
}
|
|
20558
20834
|
}
|
|
20559
20835
|
generateTempDbPath(filename) {
|
|
20560
20836
|
const ts = Date.now();
|
|
20561
20837
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
20562
20838
|
const safeName = filename.replace(/[/\\?%*:|"<>]/g, "_").slice(0, 40);
|
|
20563
|
-
return
|
|
20839
|
+
return path29.join(this.tempDir, `merge_${safeName}_${ts}_${rand}.db`);
|
|
20564
20840
|
}
|
|
20565
20841
|
/** Store a parsed file's temp DB and return a handle for subsequent requests. */
|
|
20566
20842
|
store(filename, tempDbPath) {
|
|
@@ -20571,7 +20847,7 @@ var MergeSessionCache = class {
|
|
|
20571
20847
|
/** Open a TempDbReader for a given handle. Caller must close the reader. */
|
|
20572
20848
|
openReader(handle) {
|
|
20573
20849
|
const entry = this.cache.get(handle);
|
|
20574
|
-
if (!entry || !
|
|
20850
|
+
if (!entry || !fs34.existsSync(entry.tempDbPath)) return null;
|
|
20575
20851
|
const adapter = openBetterSqliteDatabase(entry.tempDbPath, {
|
|
20576
20852
|
readonly: true,
|
|
20577
20853
|
nativeBinding: this.nativeBinding
|
|
@@ -20616,26 +20892,26 @@ var MergeSessionCache = class {
|
|
|
20616
20892
|
};
|
|
20617
20893
|
|
|
20618
20894
|
// ../../packages/node-runtime/src/services/push-importer.ts
|
|
20619
|
-
import * as
|
|
20895
|
+
import * as fs35 from "fs";
|
|
20620
20896
|
var SYSTEM_SENDER_ID = "SYSTEM";
|
|
20621
20897
|
var SYSTEM_MEMBER_NAME = "\u7CFB\u7EDF\u6D88\u606F";
|
|
20622
|
-
function
|
|
20898
|
+
function isRecord4(value) {
|
|
20623
20899
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20624
20900
|
}
|
|
20625
|
-
function
|
|
20901
|
+
function validateOptionalString2(value, field) {
|
|
20626
20902
|
return value !== void 0 && typeof value !== "string" ? `${field} must be a string` : null;
|
|
20627
20903
|
}
|
|
20628
20904
|
function validatePayload(payload, isNew) {
|
|
20629
|
-
if (!
|
|
20905
|
+
if (!isRecord4(payload)) return "payload must be an object";
|
|
20630
20906
|
const messages = payload.messages;
|
|
20631
20907
|
if (!Array.isArray(messages) || messages.length === 0) return "messages is required and must be a non-empty array";
|
|
20632
20908
|
if (payload.chatlab !== void 0) {
|
|
20633
|
-
if (!
|
|
20909
|
+
if (!isRecord4(payload.chatlab)) return "chatlab must be an object";
|
|
20634
20910
|
if (typeof payload.chatlab.version !== "string" || payload.chatlab.version.length === 0)
|
|
20635
20911
|
return "chatlab.version must be a string";
|
|
20636
20912
|
if (typeof payload.chatlab.exportedAt !== "number" || !Number.isFinite(payload.chatlab.exportedAt))
|
|
20637
20913
|
return "chatlab.exportedAt must be a number";
|
|
20638
|
-
const generatorError =
|
|
20914
|
+
const generatorError = validateOptionalString2(payload.chatlab.generator, "chatlab.generator");
|
|
20639
20915
|
if (generatorError) return generatorError;
|
|
20640
20916
|
}
|
|
20641
20917
|
if (isNew) {
|
|
@@ -20643,17 +20919,17 @@ function validatePayload(payload, isNew) {
|
|
|
20643
20919
|
if (!payload.meta) return "meta is required for new sessions";
|
|
20644
20920
|
}
|
|
20645
20921
|
if (payload.meta !== void 0) {
|
|
20646
|
-
if (!
|
|
20922
|
+
if (!isRecord4(payload.meta)) return "meta must be an object";
|
|
20647
20923
|
const metaFields = ["name", "platform", "type", "groupId", "groupAvatar", "ownerId"];
|
|
20648
20924
|
for (const field of metaFields) {
|
|
20649
|
-
const error =
|
|
20925
|
+
const error = validateOptionalString2(payload.meta[field], `meta.${field}`);
|
|
20650
20926
|
if (error) return error;
|
|
20651
20927
|
}
|
|
20652
20928
|
if (isNew && !payload.meta.name) return "meta.name is required";
|
|
20653
20929
|
if (isNew && !payload.meta.platform) return "meta.platform is required";
|
|
20654
20930
|
if (isNew && !payload.meta.type) return "meta.type is required";
|
|
20655
20931
|
}
|
|
20656
|
-
if (payload.options !== void 0 && !
|
|
20932
|
+
if (payload.options !== void 0 && !isRecord4(payload.options)) return "options must be an object";
|
|
20657
20933
|
const { metaUpdateMode, memberUpdateMode } = payload.options ?? {};
|
|
20658
20934
|
if (metaUpdateMode !== void 0 && metaUpdateMode !== "patch" && metaUpdateMode !== "none") {
|
|
20659
20935
|
return `options.metaUpdateMode must be 'patch' or 'none'`;
|
|
@@ -20665,18 +20941,18 @@ function validatePayload(payload, isNew) {
|
|
|
20665
20941
|
if (payload.members) {
|
|
20666
20942
|
for (let i = 0; i < payload.members.length; i++) {
|
|
20667
20943
|
const member = payload.members[i];
|
|
20668
|
-
if (!
|
|
20944
|
+
if (!isRecord4(member)) return `members[${i}] must be an object`;
|
|
20669
20945
|
if (typeof member.platformId !== "string" || member.platformId.length === 0)
|
|
20670
20946
|
return `members[${i}].platformId must be a string`;
|
|
20671
20947
|
for (const field of ["accountName", "groupNickname", "avatar"]) {
|
|
20672
|
-
const error =
|
|
20948
|
+
const error = validateOptionalString2(member[field], `members[${i}].${field}`);
|
|
20673
20949
|
if (error) return error;
|
|
20674
20950
|
}
|
|
20675
20951
|
if (member.roles !== void 0) {
|
|
20676
20952
|
if (!Array.isArray(member.roles)) return `members[${i}].roles must be an array`;
|
|
20677
20953
|
for (let j = 0; j < member.roles.length; j++) {
|
|
20678
20954
|
const role = member.roles[j];
|
|
20679
|
-
if (!
|
|
20955
|
+
if (!isRecord4(role) || typeof role.id !== "string" || role.id.length === 0)
|
|
20680
20956
|
return `members[${i}].roles[${j}].id must be a string`;
|
|
20681
20957
|
}
|
|
20682
20958
|
}
|
|
@@ -20684,13 +20960,13 @@ function validatePayload(payload, isNew) {
|
|
|
20684
20960
|
}
|
|
20685
20961
|
for (let i = 0; i < messages.length; i++) {
|
|
20686
20962
|
const msg = messages[i];
|
|
20687
|
-
if (!
|
|
20963
|
+
if (!isRecord4(msg)) return `messages[${i}] must be an object`;
|
|
20688
20964
|
if (typeof msg.sender !== "string" || msg.sender.length === 0) return `messages[${i}].sender must be a string`;
|
|
20689
20965
|
if (typeof msg.timestamp !== "number" || !Number.isFinite(msg.timestamp) || msg.timestamp <= 0)
|
|
20690
20966
|
return `messages[${i}].timestamp must be a positive number`;
|
|
20691
20967
|
if (typeof msg.type !== "number" || !Number.isFinite(msg.type)) return `messages[${i}].type must be a number`;
|
|
20692
20968
|
for (const field of ["accountName", "groupNickname"]) {
|
|
20693
|
-
const error =
|
|
20969
|
+
const error = validateOptionalString2(msg[field], `messages[${i}].${field}`);
|
|
20694
20970
|
if (error) return error;
|
|
20695
20971
|
}
|
|
20696
20972
|
if (msg.content !== void 0 && msg.content !== null && typeof msg.content !== "string")
|
|
@@ -20983,7 +21259,7 @@ async function executePushImportUnlocked(deps, sessionId, payload) {
|
|
|
20983
21259
|
return { ok: false, reason: "invalid_payload", message: "sessionId contains invalid characters" };
|
|
20984
21260
|
}
|
|
20985
21261
|
const dbPath = deps.getDbPath(sessionId);
|
|
20986
|
-
const isNew = !
|
|
21262
|
+
const isNew = !fs35.existsSync(dbPath);
|
|
20987
21263
|
const validationError = validatePayload(payload, isNew);
|
|
20988
21264
|
if (validationError) {
|
|
20989
21265
|
return { ok: false, reason: "invalid_payload", message: validationError };
|
|
@@ -21089,7 +21365,7 @@ export {
|
|
|
21089
21365
|
getChatLabTempScopeDir,
|
|
21090
21366
|
createChatLabTempDir,
|
|
21091
21367
|
removeChatLabTempDir,
|
|
21092
|
-
|
|
21368
|
+
validateChatLabFile,
|
|
21093
21369
|
detectFormat,
|
|
21094
21370
|
detectAllFormats,
|
|
21095
21371
|
getSupportedFormats,
|
|
@@ -21217,6 +21493,7 @@ export {
|
|
|
21217
21493
|
createAuthProfileLlmConfigStore,
|
|
21218
21494
|
CustomProviderStore,
|
|
21219
21495
|
CustomModelStore,
|
|
21496
|
+
createLlmRuntimeStores,
|
|
21220
21497
|
normalizeAnthropicBaseUrl,
|
|
21221
21498
|
normalizeOpenAICompatibleBaseUrl,
|
|
21222
21499
|
buildPiModel,
|
|
@@ -21309,4 +21586,4 @@ export {
|
|
|
21309
21586
|
hashImportBody,
|
|
21310
21587
|
ImportIdempotencyCache
|
|
21311
21588
|
};
|
|
21312
|
-
//# sourceMappingURL=chunk-
|
|
21589
|
+
//# sourceMappingURL=chunk-AQPDZAWB.mjs.map
|