openzca 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +595 -268
- package/dist/db-migrations.js +52 -0
- package/dist/db-worker.js +14 -146
- package/dist/migrations/001-initial-schema.sql +138 -0
- package/dist/migrations/002-add-contacts.sql +18 -0
- package/dist/migrations/003-backfill-contacts.sql +158 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -438,6 +438,35 @@ var INSERT_THREAD_MEMBER_SQL = `
|
|
|
438
438
|
account_status, member_type, raw_json, snapshot_at_ms, created_at, updated_at
|
|
439
439
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
440
440
|
`;
|
|
441
|
+
var UPSERT_CONTACT_SQL = `
|
|
442
|
+
INSERT INTO contacts (
|
|
443
|
+
profile, user_id, display_name, zalo_name, avatar, account_status,
|
|
444
|
+
relationship, first_seen_at_ms, last_seen_at_ms, raw_json, created_at, updated_at
|
|
445
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
446
|
+
ON CONFLICT(profile, user_id) DO UPDATE SET
|
|
447
|
+
display_name = COALESCE(excluded.display_name, contacts.display_name),
|
|
448
|
+
zalo_name = COALESCE(excluded.zalo_name, contacts.zalo_name),
|
|
449
|
+
avatar = COALESCE(excluded.avatar, contacts.avatar),
|
|
450
|
+
account_status = COALESCE(excluded.account_status, contacts.account_status),
|
|
451
|
+
relationship = CASE
|
|
452
|
+
WHEN contacts.relationship = 'friend' OR excluded.relationship = 'friend' THEN 'friend'
|
|
453
|
+
WHEN contacts.relationship = 'seen_dm' OR excluded.relationship = 'seen_dm' THEN 'seen_dm'
|
|
454
|
+
WHEN contacts.relationship = 'seen_group' OR excluded.relationship = 'seen_group' THEN 'seen_group'
|
|
455
|
+
ELSE COALESCE(excluded.relationship, contacts.relationship, 'unknown')
|
|
456
|
+
END,
|
|
457
|
+
first_seen_at_ms = CASE
|
|
458
|
+
WHEN contacts.first_seen_at_ms IS NULL THEN excluded.first_seen_at_ms
|
|
459
|
+
WHEN excluded.first_seen_at_ms IS NULL THEN contacts.first_seen_at_ms
|
|
460
|
+
ELSE MIN(contacts.first_seen_at_ms, excluded.first_seen_at_ms)
|
|
461
|
+
END,
|
|
462
|
+
last_seen_at_ms = CASE
|
|
463
|
+
WHEN contacts.last_seen_at_ms IS NULL THEN excluded.last_seen_at_ms
|
|
464
|
+
WHEN excluded.last_seen_at_ms IS NULL THEN contacts.last_seen_at_ms
|
|
465
|
+
ELSE MAX(contacts.last_seen_at_ms, excluded.last_seen_at_ms)
|
|
466
|
+
END,
|
|
467
|
+
raw_json = COALESCE(excluded.raw_json, contacts.raw_json),
|
|
468
|
+
updated_at = excluded.updated_at
|
|
469
|
+
`;
|
|
441
470
|
var UPSERT_MESSAGE_SQL = `
|
|
442
471
|
INSERT INTO messages (
|
|
443
472
|
profile, message_uid, scope_thread_id, raw_thread_id, thread_type,
|
|
@@ -500,6 +529,12 @@ function normalizeOptionalText(value) {
|
|
|
500
529
|
const trimmed = value.trim();
|
|
501
530
|
return trimmed || void 0;
|
|
502
531
|
}
|
|
532
|
+
function normalizeRelationship(value) {
|
|
533
|
+
if (value !== "friend" && value !== "seen_dm" && value !== "seen_group" && value !== "unknown") {
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
503
538
|
function normalizeSearchText(value) {
|
|
504
539
|
return value.normalize("NFD").replace(new RegExp("\\p{Diacritic}", "gu"), "").toLowerCase().trim();
|
|
505
540
|
}
|
|
@@ -755,35 +790,23 @@ async function replaceThreadMembers(profile, scopeThreadId, members) {
|
|
|
755
790
|
];
|
|
756
791
|
await db.batch(commands, true);
|
|
757
792
|
}
|
|
758
|
-
async function
|
|
793
|
+
async function persistContact(record) {
|
|
759
794
|
const db = await getDb(record.profile);
|
|
760
795
|
const now = nowIso2();
|
|
761
|
-
await db.run(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
[
|
|
776
|
-
record.profile,
|
|
777
|
-
record.userId,
|
|
778
|
-
record.displayName ?? null,
|
|
779
|
-
record.zaloName ?? null,
|
|
780
|
-
record.avatar ?? null,
|
|
781
|
-
record.accountStatus ?? null,
|
|
782
|
-
record.rawJson ?? null,
|
|
783
|
-
now,
|
|
784
|
-
now
|
|
785
|
-
]
|
|
786
|
-
);
|
|
796
|
+
await db.run(UPSERT_CONTACT_SQL, [
|
|
797
|
+
record.profile,
|
|
798
|
+
record.userId,
|
|
799
|
+
record.displayName ?? null,
|
|
800
|
+
record.zaloName ?? null,
|
|
801
|
+
record.avatar ?? null,
|
|
802
|
+
record.accountStatus ?? null,
|
|
803
|
+
normalizeRelationship(record.relationship) ?? "unknown",
|
|
804
|
+
record.firstSeenAtMs ?? null,
|
|
805
|
+
record.lastSeenAtMs ?? null,
|
|
806
|
+
record.rawJson ?? null,
|
|
807
|
+
now,
|
|
808
|
+
now
|
|
809
|
+
]);
|
|
787
810
|
}
|
|
788
811
|
async function persistSelfProfile(params) {
|
|
789
812
|
const db = await getDb(params.profile);
|
|
@@ -990,8 +1013,8 @@ async function listMessages(params) {
|
|
|
990
1013
|
NULLIF(m.sender_name, ''),
|
|
991
1014
|
NULLIF(tm.display_name, ''),
|
|
992
1015
|
NULLIF(tm.zalo_name, ''),
|
|
993
|
-
NULLIF(
|
|
994
|
-
NULLIF(
|
|
1016
|
+
NULLIF(c.display_name, ''),
|
|
1017
|
+
NULLIF(c.zalo_name, '')
|
|
995
1018
|
) AS sender_name,
|
|
996
1019
|
m.to_id,
|
|
997
1020
|
m.timestamp_ms,
|
|
@@ -1008,9 +1031,9 @@ async function listMessages(params) {
|
|
|
1008
1031
|
ON tm.profile = m.profile
|
|
1009
1032
|
AND tm.scope_thread_id = m.scope_thread_id
|
|
1010
1033
|
AND tm.user_id = m.sender_id
|
|
1011
|
-
LEFT JOIN
|
|
1012
|
-
ON
|
|
1013
|
-
AND
|
|
1034
|
+
LEFT JOIN contacts c
|
|
1035
|
+
ON c.profile = m.profile
|
|
1036
|
+
AND c.user_id = m.sender_id
|
|
1014
1037
|
WHERE m.profile = ?
|
|
1015
1038
|
AND m.scope_thread_id = ?
|
|
1016
1039
|
AND m.thread_type = ?
|
|
@@ -1222,34 +1245,74 @@ async function getThreadInfo(params) {
|
|
|
1222
1245
|
raw: row.raw_json ? JSON.parse(row.raw_json) : void 0
|
|
1223
1246
|
};
|
|
1224
1247
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1248
|
+
var CONTACT_CHAT_CTE = `
|
|
1249
|
+
WITH ranked_contact_threads AS (
|
|
1250
|
+
SELECT
|
|
1251
|
+
c.profile AS contact_profile,
|
|
1252
|
+
c.user_id,
|
|
1253
|
+
t.scope_thread_id,
|
|
1254
|
+
t.title,
|
|
1255
|
+
COUNT(m.message_uid) AS message_count,
|
|
1256
|
+
MAX(m.timestamp_ms) AS last_message_at_ms,
|
|
1257
|
+
ROW_NUMBER() OVER (
|
|
1258
|
+
PARTITION BY c.profile, c.user_id
|
|
1259
|
+
ORDER BY
|
|
1260
|
+
COALESCE(MAX(m.timestamp_ms), 0) DESC,
|
|
1261
|
+
CASE
|
|
1262
|
+
WHEN t.scope_thread_id = c.user_id THEN 0
|
|
1263
|
+
WHEN t.peer_id = c.user_id THEN 1
|
|
1264
|
+
WHEN t.raw_thread_id = c.user_id THEN 2
|
|
1265
|
+
ELSE 3
|
|
1266
|
+
END,
|
|
1267
|
+
t.updated_at DESC,
|
|
1268
|
+
t.scope_thread_id
|
|
1269
|
+
) AS thread_rank
|
|
1270
|
+
FROM contacts c
|
|
1271
|
+
LEFT JOIN threads t
|
|
1272
|
+
ON t.profile = c.profile
|
|
1273
|
+
AND t.thread_type = 'user'
|
|
1274
|
+
AND (t.peer_id = c.user_id OR t.scope_thread_id = c.user_id OR t.raw_thread_id = c.user_id)
|
|
1275
|
+
LEFT JOIN messages m
|
|
1276
|
+
ON m.profile = t.profile
|
|
1277
|
+
AND m.scope_thread_id = t.scope_thread_id
|
|
1278
|
+
GROUP BY
|
|
1279
|
+
c.profile,
|
|
1280
|
+
c.user_id,
|
|
1281
|
+
t.scope_thread_id,
|
|
1282
|
+
t.title,
|
|
1283
|
+
t.updated_at,
|
|
1284
|
+
t.peer_id,
|
|
1285
|
+
t.raw_thread_id
|
|
1286
|
+
)
|
|
1287
|
+
`;
|
|
1288
|
+
async function listContacts(params) {
|
|
1289
|
+
const db = await getDb(params.profile);
|
|
1227
1290
|
const rows = await db.all(
|
|
1228
1291
|
`
|
|
1292
|
+
${CONTACT_CHAT_CTE}
|
|
1229
1293
|
SELECT
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
AND
|
|
1247
|
-
WHERE
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
ORDER BY COALESCE(f.display_name, f.zalo_name, f.user_id), f.user_id
|
|
1294
|
+
c.user_id,
|
|
1295
|
+
c.display_name,
|
|
1296
|
+
c.zalo_name,
|
|
1297
|
+
c.avatar,
|
|
1298
|
+
c.account_status,
|
|
1299
|
+
c.relationship,
|
|
1300
|
+
c.first_seen_at_ms,
|
|
1301
|
+
c.last_seen_at_ms,
|
|
1302
|
+
r.scope_thread_id AS chat_id,
|
|
1303
|
+
r.title,
|
|
1304
|
+
COALESCE(r.message_count, 0) AS message_count,
|
|
1305
|
+
r.last_message_at_ms
|
|
1306
|
+
FROM contacts c
|
|
1307
|
+
LEFT JOIN ranked_contact_threads r
|
|
1308
|
+
ON r.contact_profile = c.profile
|
|
1309
|
+
AND r.user_id = c.user_id
|
|
1310
|
+
AND r.thread_rank = 1
|
|
1311
|
+
WHERE c.profile = ?
|
|
1312
|
+
AND (? IS NULL OR c.relationship = ?)
|
|
1313
|
+
ORDER BY COALESCE(c.display_name, c.zalo_name, c.user_id), c.user_id
|
|
1251
1314
|
`,
|
|
1252
|
-
[profile]
|
|
1315
|
+
[params.profile, params.relationship ?? null, params.relationship ?? null]
|
|
1253
1316
|
);
|
|
1254
1317
|
return rows.map((row) => ({
|
|
1255
1318
|
userId: row.user_id,
|
|
@@ -1257,43 +1320,53 @@ async function listFriends(profile) {
|
|
|
1257
1320
|
zaloName: row.zalo_name ?? void 0,
|
|
1258
1321
|
avatar: row.avatar ?? void 0,
|
|
1259
1322
|
accountStatus: row.account_status ?? void 0,
|
|
1323
|
+
relationship: normalizeRelationship(row.relationship) ?? "unknown",
|
|
1324
|
+
firstSeenAtMs: row.first_seen_at_ms ?? void 0,
|
|
1325
|
+
lastSeenAtMs: row.last_seen_at_ms ?? void 0,
|
|
1260
1326
|
title: row.title ?? void 0,
|
|
1261
1327
|
chatId: row.chat_id ?? row.user_id,
|
|
1262
1328
|
messageCount: row.message_count,
|
|
1263
1329
|
lastMessageAtMs: row.last_message_at_ms ?? void 0
|
|
1264
1330
|
}));
|
|
1265
1331
|
}
|
|
1266
|
-
async function
|
|
1332
|
+
async function listFriends(profile) {
|
|
1333
|
+
return await listContacts({ profile, relationship: "friend" });
|
|
1334
|
+
}
|
|
1335
|
+
async function getContactInfo(params) {
|
|
1267
1336
|
const db = await getDb(params.profile);
|
|
1268
1337
|
const row = await db.get(
|
|
1269
1338
|
`
|
|
1339
|
+
${CONTACT_CHAT_CTE}
|
|
1270
1340
|
SELECT
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
AND
|
|
1289
|
-
WHERE
|
|
1290
|
-
AND
|
|
1291
|
-
|
|
1292
|
-
f.user_id, f.display_name, f.zalo_name, f.avatar, f.account_status,
|
|
1293
|
-
f.raw_json, t.scope_thread_id, t.title
|
|
1341
|
+
c.user_id,
|
|
1342
|
+
c.display_name,
|
|
1343
|
+
c.zalo_name,
|
|
1344
|
+
c.avatar,
|
|
1345
|
+
c.account_status,
|
|
1346
|
+
c.relationship,
|
|
1347
|
+
c.first_seen_at_ms,
|
|
1348
|
+
c.last_seen_at_ms,
|
|
1349
|
+
c.raw_json,
|
|
1350
|
+
r.scope_thread_id AS chat_id,
|
|
1351
|
+
r.title,
|
|
1352
|
+
COALESCE(r.message_count, 0) AS message_count,
|
|
1353
|
+
r.last_message_at_ms
|
|
1354
|
+
FROM contacts c
|
|
1355
|
+
LEFT JOIN ranked_contact_threads r
|
|
1356
|
+
ON r.contact_profile = c.profile
|
|
1357
|
+
AND r.user_id = c.user_id
|
|
1358
|
+
AND r.thread_rank = 1
|
|
1359
|
+
WHERE c.profile = ?
|
|
1360
|
+
AND c.user_id = ?
|
|
1361
|
+
AND (? IS NULL OR c.relationship = ?)
|
|
1294
1362
|
LIMIT 1
|
|
1295
1363
|
`,
|
|
1296
|
-
[
|
|
1364
|
+
[
|
|
1365
|
+
params.profile,
|
|
1366
|
+
params.userId,
|
|
1367
|
+
params.relationship ?? null,
|
|
1368
|
+
params.relationship ?? null
|
|
1369
|
+
]
|
|
1297
1370
|
);
|
|
1298
1371
|
if (!row) {
|
|
1299
1372
|
return null;
|
|
@@ -1304,6 +1377,9 @@ async function getFriendInfo(params) {
|
|
|
1304
1377
|
zaloName: row.zalo_name ?? void 0,
|
|
1305
1378
|
avatar: row.avatar ?? void 0,
|
|
1306
1379
|
accountStatus: row.account_status ?? void 0,
|
|
1380
|
+
relationship: normalizeRelationship(row.relationship) ?? "unknown",
|
|
1381
|
+
firstSeenAtMs: row.first_seen_at_ms ?? void 0,
|
|
1382
|
+
lastSeenAtMs: row.last_seen_at_ms ?? void 0,
|
|
1307
1383
|
title: row.title ?? void 0,
|
|
1308
1384
|
chatId: row.chat_id ?? row.user_id,
|
|
1309
1385
|
messageCount: row.message_count,
|
|
@@ -1311,12 +1387,22 @@ async function getFriendInfo(params) {
|
|
|
1311
1387
|
raw: row.raw_json ? JSON.parse(row.raw_json) : void 0
|
|
1312
1388
|
};
|
|
1313
1389
|
}
|
|
1314
|
-
async function
|
|
1390
|
+
async function getFriendInfo(params) {
|
|
1391
|
+
return await getContactInfo({
|
|
1392
|
+
profile: params.profile,
|
|
1393
|
+
userId: params.userId,
|
|
1394
|
+
relationship: "friend"
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
async function findContacts(params) {
|
|
1315
1398
|
const query = normalizeSearchText(params.query);
|
|
1316
1399
|
if (!query) {
|
|
1317
1400
|
return [];
|
|
1318
1401
|
}
|
|
1319
|
-
const rows = await
|
|
1402
|
+
const rows = await listContacts({
|
|
1403
|
+
profile: params.profile,
|
|
1404
|
+
relationship: params.relationship
|
|
1405
|
+
});
|
|
1320
1406
|
return rows.filter((row) => {
|
|
1321
1407
|
const haystacks = [
|
|
1322
1408
|
row.userId,
|
|
@@ -1327,6 +1413,52 @@ async function findFriends(params) {
|
|
|
1327
1413
|
return haystacks.some((value) => matchesSearchPattern(value, query));
|
|
1328
1414
|
});
|
|
1329
1415
|
}
|
|
1416
|
+
async function findFriends(params) {
|
|
1417
|
+
return await findContacts({
|
|
1418
|
+
profile: params.profile,
|
|
1419
|
+
query: params.query,
|
|
1420
|
+
relationship: "friend"
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
async function reconcileFriendRelationships(params) {
|
|
1424
|
+
const db = await getDb(params.profile);
|
|
1425
|
+
const now = nowIso2();
|
|
1426
|
+
const friendIds = Array.from(
|
|
1427
|
+
new Set(params.currentFriendIds.map((value) => normalizeId(value)).filter(Boolean))
|
|
1428
|
+
);
|
|
1429
|
+
const stalePredicate = friendIds.length > 0 ? `AND contacts.user_id NOT IN (${friendIds.map(() => "?").join(", ")})` : "";
|
|
1430
|
+
const sqlParams = [now, params.profile, ...friendIds];
|
|
1431
|
+
await db.run(
|
|
1432
|
+
`
|
|
1433
|
+
UPDATE contacts
|
|
1434
|
+
SET relationship = CASE
|
|
1435
|
+
WHEN EXISTS (
|
|
1436
|
+
SELECT 1
|
|
1437
|
+
FROM threads t
|
|
1438
|
+
WHERE t.profile = contacts.profile
|
|
1439
|
+
AND t.thread_type = 'user'
|
|
1440
|
+
AND (
|
|
1441
|
+
t.peer_id = contacts.user_id
|
|
1442
|
+
OR t.scope_thread_id = contacts.user_id
|
|
1443
|
+
OR t.raw_thread_id = contacts.user_id
|
|
1444
|
+
)
|
|
1445
|
+
) THEN 'seen_dm'
|
|
1446
|
+
WHEN EXISTS (
|
|
1447
|
+
SELECT 1
|
|
1448
|
+
FROM thread_members tm
|
|
1449
|
+
WHERE tm.profile = contacts.profile
|
|
1450
|
+
AND tm.user_id = contacts.user_id
|
|
1451
|
+
) THEN 'seen_group'
|
|
1452
|
+
ELSE 'unknown'
|
|
1453
|
+
END,
|
|
1454
|
+
updated_at = ?
|
|
1455
|
+
WHERE contacts.profile = ?
|
|
1456
|
+
AND contacts.relationship = 'friend'
|
|
1457
|
+
${stalePredicate}
|
|
1458
|
+
`,
|
|
1459
|
+
sqlParams
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1330
1462
|
async function listChats(profile) {
|
|
1331
1463
|
return listThreads({ profile });
|
|
1332
1464
|
}
|
|
@@ -4018,6 +4150,20 @@ async function persistOutgoingMessageBestEffort(params) {
|
|
|
4018
4150
|
async function persistGroupMembersSnapshot(profile, groupId, api) {
|
|
4019
4151
|
const rows = await listGroupMemberRows(api, groupId);
|
|
4020
4152
|
const snapshotAtMs = Date.now();
|
|
4153
|
+
for (const row of rows) {
|
|
4154
|
+
await persistContact({
|
|
4155
|
+
profile,
|
|
4156
|
+
userId: row.userId,
|
|
4157
|
+
displayName: row.displayName,
|
|
4158
|
+
zaloName: row.zaloName,
|
|
4159
|
+
avatar: row.avatar,
|
|
4160
|
+
accountStatus: row.accountStatus,
|
|
4161
|
+
relationship: "seen_group",
|
|
4162
|
+
firstSeenAtMs: snapshotAtMs,
|
|
4163
|
+
lastSeenAtMs: snapshotAtMs,
|
|
4164
|
+
rawJson: row.rawJson
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4021
4167
|
await replaceThreadMembers(
|
|
4022
4168
|
profile,
|
|
4023
4169
|
groupId,
|
|
@@ -4027,7 +4173,9 @@ async function persistGroupMembersSnapshot(profile, groupId, api) {
|
|
|
4027
4173
|
userId: row.userId,
|
|
4028
4174
|
displayName: row.displayName,
|
|
4029
4175
|
zaloName: row.zaloName,
|
|
4030
|
-
|
|
4176
|
+
avatar: row.avatar,
|
|
4177
|
+
accountStatus: row.accountStatus,
|
|
4178
|
+
rawJson: row.rawJson ?? JSON.stringify(row),
|
|
4031
4179
|
snapshotAtMs
|
|
4032
4180
|
}))
|
|
4033
4181
|
);
|
|
@@ -4043,13 +4191,14 @@ async function persistFriendDirectory(profile, api) {
|
|
|
4043
4191
|
const zaloName = typeof record.zaloName === "string" && record.zaloName.trim() ? record.zaloName.trim() : void 0;
|
|
4044
4192
|
const avatar = typeof record.avatar === "string" && record.avatar.trim() ? record.avatar.trim() : void 0;
|
|
4045
4193
|
const title = displayName || zaloName || userId;
|
|
4046
|
-
await
|
|
4194
|
+
await persistContact({
|
|
4047
4195
|
profile,
|
|
4048
4196
|
userId,
|
|
4049
4197
|
displayName,
|
|
4050
4198
|
zaloName,
|
|
4051
4199
|
avatar,
|
|
4052
4200
|
accountStatus: typeof record.accountStatus === "number" && Number.isFinite(record.accountStatus) ? Math.trunc(record.accountStatus) : void 0,
|
|
4201
|
+
relationship: "friend",
|
|
4053
4202
|
rawJson: JSON.stringify(friend2)
|
|
4054
4203
|
});
|
|
4055
4204
|
await persistThread({
|
|
@@ -4063,6 +4212,10 @@ async function persistFriendDirectory(profile, api) {
|
|
|
4063
4212
|
});
|
|
4064
4213
|
nameById.set(userId, title);
|
|
4065
4214
|
}
|
|
4215
|
+
await reconcileFriendRelationships({
|
|
4216
|
+
profile,
|
|
4217
|
+
currentFriendIds: Array.from(nameById.keys())
|
|
4218
|
+
});
|
|
4066
4219
|
return nameById;
|
|
4067
4220
|
}
|
|
4068
4221
|
function parseSinceDuration(label, value) {
|
|
@@ -4202,6 +4355,130 @@ async function prepareDbGroupTarget(params) {
|
|
|
4202
4355
|
});
|
|
4203
4356
|
await persistGroupMembersSnapshot(params.profile, params.groupId, params.api);
|
|
4204
4357
|
}
|
|
4358
|
+
function resolveContactDisplayName(params) {
|
|
4359
|
+
return params.displayName?.trim() || params.zaloName?.trim() || params.fallbackTitle?.trim() || params.userId.trim() || void 0;
|
|
4360
|
+
}
|
|
4361
|
+
async function persistLiveDmContact(params) {
|
|
4362
|
+
if (!params.peerId) {
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
let displayName = params.senderDisplayName?.trim() || void 0;
|
|
4366
|
+
let zaloName = params.senderName?.trim() || void 0;
|
|
4367
|
+
let avatar;
|
|
4368
|
+
let accountStatus;
|
|
4369
|
+
let rawJson = params.rawJson;
|
|
4370
|
+
const existing = await getContactInfo({
|
|
4371
|
+
profile: params.profile,
|
|
4372
|
+
userId: params.peerId
|
|
4373
|
+
});
|
|
4374
|
+
if (!displayName || !existing?.avatar) {
|
|
4375
|
+
try {
|
|
4376
|
+
const response = await params.api.getUserInfo(params.peerId);
|
|
4377
|
+
const profiles = response.changed_profiles ?? {};
|
|
4378
|
+
const profile = profiles[params.peerId] ?? profiles[`${params.peerId}_0`] ?? Object.values(profiles).find((value) => normalizeCachedId(value?.userId ?? value?.uid) === params.peerId) ?? void 0;
|
|
4379
|
+
if (profile) {
|
|
4380
|
+
displayName = displayName || (typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : void 0) || (typeof profile.display_name === "string" && profile.display_name.trim() ? profile.display_name.trim() : void 0);
|
|
4381
|
+
zaloName = zaloName || (typeof profile.zaloName === "string" && profile.zaloName.trim() ? profile.zaloName.trim() : void 0) || (typeof profile.zalo_name === "string" && profile.zalo_name.trim() ? profile.zalo_name.trim() : void 0);
|
|
4382
|
+
avatar = typeof profile.avatar === "string" && profile.avatar.trim() ? profile.avatar.trim() : void 0;
|
|
4383
|
+
accountStatus = typeof profile.accountStatus === "number" && Number.isFinite(profile.accountStatus) ? Math.trunc(profile.accountStatus) : void 0;
|
|
4384
|
+
rawJson = JSON.stringify(profile);
|
|
4385
|
+
}
|
|
4386
|
+
} catch {
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
const title = resolveContactDisplayName({
|
|
4390
|
+
userId: params.peerId,
|
|
4391
|
+
displayName,
|
|
4392
|
+
zaloName,
|
|
4393
|
+
fallbackTitle: typeof existing?.title === "string" ? existing.title : void 0
|
|
4394
|
+
});
|
|
4395
|
+
await persistContact({
|
|
4396
|
+
profile: params.profile,
|
|
4397
|
+
userId: params.peerId,
|
|
4398
|
+
displayName,
|
|
4399
|
+
zaloName,
|
|
4400
|
+
avatar,
|
|
4401
|
+
accountStatus,
|
|
4402
|
+
relationship: "seen_dm",
|
|
4403
|
+
firstSeenAtMs: params.timestampMs,
|
|
4404
|
+
lastSeenAtMs: params.timestampMs,
|
|
4405
|
+
rawJson
|
|
4406
|
+
});
|
|
4407
|
+
await persistThread({
|
|
4408
|
+
profile: params.profile,
|
|
4409
|
+
scopeThreadId: params.peerId,
|
|
4410
|
+
rawThreadId: params.peerId,
|
|
4411
|
+
threadType: "user",
|
|
4412
|
+
peerId: params.peerId,
|
|
4413
|
+
title,
|
|
4414
|
+
rawJson
|
|
4415
|
+
});
|
|
4416
|
+
}
|
|
4417
|
+
function extractGroupTitle(record) {
|
|
4418
|
+
if (!record) {
|
|
4419
|
+
return void 0;
|
|
4420
|
+
}
|
|
4421
|
+
return typeof record.name === "string" && record.name.trim() ? record.name.trim() : typeof record.groupName === "string" && record.groupName.trim() ? record.groupName.trim() : void 0;
|
|
4422
|
+
}
|
|
4423
|
+
async function findGroupDirectoryEntry(api, groupId) {
|
|
4424
|
+
const groups = await buildGroupsDetailed(api);
|
|
4425
|
+
return groups.find((item) => {
|
|
4426
|
+
const record = item;
|
|
4427
|
+
return normalizeCachedId(record.groupId ?? record.grid ?? record.threadId ?? record.id) === groupId;
|
|
4428
|
+
});
|
|
4429
|
+
}
|
|
4430
|
+
async function hydrateUnknownLiveGroup(params) {
|
|
4431
|
+
const existing = await getThreadInfo({
|
|
4432
|
+
profile: params.profile,
|
|
4433
|
+
threadId: params.groupId,
|
|
4434
|
+
threadType: "group"
|
|
4435
|
+
});
|
|
4436
|
+
if (existing && (existing.title || typeof existing.memberCount === "number" && existing.memberCount > 0)) {
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
let group2;
|
|
4440
|
+
let title = params.fallbackTitle?.trim() || void 0;
|
|
4441
|
+
try {
|
|
4442
|
+
const info = await params.api.getGroupInfo(params.groupId);
|
|
4443
|
+
group2 = info.gridInfoMap[params.groupId];
|
|
4444
|
+
title = extractGroupTitle(group2) ?? title;
|
|
4445
|
+
} catch {
|
|
4446
|
+
}
|
|
4447
|
+
if (!group2 || !title) {
|
|
4448
|
+
try {
|
|
4449
|
+
const directoryGroup = await findGroupDirectoryEntry(params.api, params.groupId);
|
|
4450
|
+
if (directoryGroup) {
|
|
4451
|
+
group2 = group2 ?? directoryGroup;
|
|
4452
|
+
title = extractGroupTitle(directoryGroup) ?? title;
|
|
4453
|
+
}
|
|
4454
|
+
} catch {
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
if (group2 || title) {
|
|
4458
|
+
await persistThread({
|
|
4459
|
+
profile: params.profile,
|
|
4460
|
+
scopeThreadId: params.groupId,
|
|
4461
|
+
rawThreadId: params.groupId,
|
|
4462
|
+
threadType: "group",
|
|
4463
|
+
title,
|
|
4464
|
+
rawJson: group2 ? JSON.stringify(group2) : void 0
|
|
4465
|
+
});
|
|
4466
|
+
try {
|
|
4467
|
+
await persistGroupMembersSnapshot(params.profile, params.groupId, params.api);
|
|
4468
|
+
} catch {
|
|
4469
|
+
}
|
|
4470
|
+
return;
|
|
4471
|
+
}
|
|
4472
|
+
if (params.fallbackTitle?.trim()) {
|
|
4473
|
+
await persistThread({
|
|
4474
|
+
profile: params.profile,
|
|
4475
|
+
scopeThreadId: params.groupId,
|
|
4476
|
+
rawThreadId: params.groupId,
|
|
4477
|
+
threadType: "group",
|
|
4478
|
+
title: params.fallbackTitle.trim()
|
|
4479
|
+
});
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4205
4482
|
async function syncDbGroupHistoryFull(params) {
|
|
4206
4483
|
if (params.targetGroupIds.size === 0) {
|
|
4207
4484
|
return;
|
|
@@ -4408,128 +4685,132 @@ async function syncDbChatsBestEffort(params) {
|
|
|
4408
4685
|
}
|
|
4409
4686
|
async function runDbSync(params) {
|
|
4410
4687
|
const { profile, api } = await requireApi(params.command);
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
dbPath,
|
|
4416
|
-
params.mode === "all" || params.mode === "chats" || params.mode === "chat" ? params.count : void 0
|
|
4417
|
-
);
|
|
4418
|
-
const selfId = api.getOwnId();
|
|
4419
|
-
const selfInfo = normalizeMeInfoOutput(await api.fetchAccountInfo());
|
|
4420
|
-
await persistSelfProfile({
|
|
4421
|
-
profile,
|
|
4422
|
-
userId: selfId,
|
|
4423
|
-
displayName: typeof selfInfo.displayName === "string" && selfInfo.displayName.trim() ? selfInfo.displayName.trim() : void 0,
|
|
4424
|
-
infoJson: JSON.stringify(selfInfo)
|
|
4425
|
-
});
|
|
4426
|
-
const { pinnedIds, hiddenIds } = await collectConversationIds(api);
|
|
4427
|
-
let friendNames = /* @__PURE__ */ new Map();
|
|
4428
|
-
if (params.mode === "all" || params.mode === "friends" || params.mode === "chats") {
|
|
4429
|
-
friendNames = await syncDbFriendDirectory({
|
|
4688
|
+
try {
|
|
4689
|
+
const dbPath = await resolveDbPath(profile);
|
|
4690
|
+
params.progress?.(`starting sync for profile ${profile}`);
|
|
4691
|
+
const summary = createDbSyncSummary(
|
|
4430
4692
|
profile,
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4693
|
+
dbPath,
|
|
4694
|
+
params.mode === "all" || params.mode === "chats" || params.mode === "chat" ? params.count : void 0
|
|
4695
|
+
);
|
|
4696
|
+
const selfId = api.getOwnId();
|
|
4697
|
+
const selfInfo = normalizeMeInfoOutput(await api.fetchAccountInfo());
|
|
4698
|
+
await persistSelfProfile({
|
|
4699
|
+
profile,
|
|
4700
|
+
userId: selfId,
|
|
4701
|
+
displayName: typeof selfInfo.displayName === "string" && selfInfo.displayName.trim() ? selfInfo.displayName.trim() : void 0,
|
|
4702
|
+
infoJson: JSON.stringify(selfInfo)
|
|
4434
4703
|
});
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4704
|
+
const { pinnedIds, hiddenIds } = await collectConversationIds(api);
|
|
4705
|
+
let friendNames = /* @__PURE__ */ new Map();
|
|
4706
|
+
if (params.mode === "all" || params.mode === "friends" || params.mode === "chats") {
|
|
4707
|
+
friendNames = await syncDbFriendDirectory({
|
|
4708
|
+
profile,
|
|
4709
|
+
api,
|
|
4710
|
+
summary,
|
|
4711
|
+
progress: params.progress
|
|
4712
|
+
});
|
|
4713
|
+
}
|
|
4714
|
+
if (params.mode === "all" || params.mode === "groups") {
|
|
4715
|
+
const groups = await buildGroupsDetailed(api);
|
|
4716
|
+
const targetGroupIds = /* @__PURE__ */ new Set();
|
|
4717
|
+
const titleById = /* @__PURE__ */ new Map();
|
|
4718
|
+
for (const group2 of groups) {
|
|
4719
|
+
const record = group2;
|
|
4720
|
+
const groupId = normalizeCachedId(record.groupId);
|
|
4721
|
+
if (!groupId) continue;
|
|
4722
|
+
const title = typeof record.name === "string" && record.name.trim() ? record.name.trim() : typeof record.groupName === "string" && record.groupName.trim() ? record.groupName.trim() : void 0;
|
|
4723
|
+
targetGroupIds.add(groupId);
|
|
4724
|
+
titleById.set(groupId, title);
|
|
4725
|
+
await prepareDbGroupTarget({
|
|
4726
|
+
profile,
|
|
4727
|
+
api,
|
|
4728
|
+
groupId,
|
|
4729
|
+
title,
|
|
4730
|
+
rawJson: JSON.stringify(group2),
|
|
4731
|
+
pinnedIds,
|
|
4732
|
+
hiddenIds
|
|
4733
|
+
});
|
|
4734
|
+
}
|
|
4735
|
+
await syncDbGroupHistoryFull({
|
|
4736
|
+
profile,
|
|
4737
|
+
api,
|
|
4738
|
+
selfId,
|
|
4739
|
+
targetGroupIds,
|
|
4740
|
+
titleById,
|
|
4741
|
+
summary,
|
|
4742
|
+
progress: params.progress
|
|
4743
|
+
});
|
|
4744
|
+
}
|
|
4745
|
+
if (params.mode === "group") {
|
|
4746
|
+
if (!params.groupId) {
|
|
4747
|
+
throw new Error("Missing group id for db sync group.");
|
|
4748
|
+
}
|
|
4749
|
+
const groupInfo = await api.getGroupInfo(params.groupId);
|
|
4750
|
+
const group2 = groupInfo.gridInfoMap[params.groupId];
|
|
4751
|
+
const title = typeof group2?.name === "string" && group2.name.trim() ? group2.name.trim() : void 0;
|
|
4447
4752
|
await prepareDbGroupTarget({
|
|
4448
4753
|
profile,
|
|
4449
4754
|
api,
|
|
4450
|
-
groupId,
|
|
4755
|
+
groupId: params.groupId,
|
|
4451
4756
|
title,
|
|
4452
|
-
rawJson: JSON.stringify(group2),
|
|
4757
|
+
rawJson: group2 ? JSON.stringify(group2) : void 0,
|
|
4453
4758
|
pinnedIds,
|
|
4454
4759
|
hiddenIds
|
|
4455
4760
|
});
|
|
4761
|
+
await syncDbGroupHistoryFull({
|
|
4762
|
+
profile,
|
|
4763
|
+
api,
|
|
4764
|
+
selfId,
|
|
4765
|
+
targetGroupIds: /* @__PURE__ */ new Set([params.groupId]),
|
|
4766
|
+
titleById: /* @__PURE__ */ new Map([[params.groupId, title]]),
|
|
4767
|
+
summary,
|
|
4768
|
+
progress: params.progress
|
|
4769
|
+
});
|
|
4456
4770
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
api,
|
|
4477
|
-
groupId: params.groupId,
|
|
4478
|
-
title,
|
|
4479
|
-
rawJson: group2 ? JSON.stringify(group2) : void 0,
|
|
4480
|
-
pinnedIds,
|
|
4481
|
-
hiddenIds
|
|
4482
|
-
});
|
|
4483
|
-
await syncDbGroupHistoryFull({
|
|
4484
|
-
profile,
|
|
4485
|
-
api,
|
|
4486
|
-
selfId,
|
|
4487
|
-
targetGroupIds: /* @__PURE__ */ new Set([params.groupId]),
|
|
4488
|
-
titleById: /* @__PURE__ */ new Map([[params.groupId, title]]),
|
|
4489
|
-
summary,
|
|
4490
|
-
progress: params.progress
|
|
4491
|
-
});
|
|
4492
|
-
}
|
|
4493
|
-
if (params.mode === "chat") {
|
|
4494
|
-
if (!params.threadId) {
|
|
4495
|
-
throw new Error("Missing chat id for db sync chat.");
|
|
4496
|
-
}
|
|
4497
|
-
if (friendNames.size === 0) {
|
|
4498
|
-
friendNames = await persistFriendDirectory(profile, api);
|
|
4771
|
+
if (params.mode === "chat") {
|
|
4772
|
+
if (!params.threadId) {
|
|
4773
|
+
throw new Error("Missing chat id for db sync chat.");
|
|
4774
|
+
}
|
|
4775
|
+
if (friendNames.size === 0) {
|
|
4776
|
+
friendNames = await persistFriendDirectory(profile, api);
|
|
4777
|
+
}
|
|
4778
|
+
await syncDbChatThread({
|
|
4779
|
+
profile,
|
|
4780
|
+
api,
|
|
4781
|
+
selfId,
|
|
4782
|
+
threadId: params.threadId,
|
|
4783
|
+
count: params.count,
|
|
4784
|
+
title: friendNames.get(params.threadId),
|
|
4785
|
+
pinnedIds,
|
|
4786
|
+
hiddenIds,
|
|
4787
|
+
summary,
|
|
4788
|
+
progress: params.progress
|
|
4789
|
+
});
|
|
4499
4790
|
}
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
friendNames = await persistFriendDirectory(profile, api);
|
|
4791
|
+
if (params.mode === "all" || params.mode === "chats") {
|
|
4792
|
+
if (friendNames.size === 0) {
|
|
4793
|
+
friendNames = await persistFriendDirectory(profile, api);
|
|
4794
|
+
}
|
|
4795
|
+
await syncDbChatsBestEffort({
|
|
4796
|
+
profile,
|
|
4797
|
+
api,
|
|
4798
|
+
selfId,
|
|
4799
|
+
count: params.count,
|
|
4800
|
+
titleById: friendNames,
|
|
4801
|
+
pinnedIds,
|
|
4802
|
+
hiddenIds,
|
|
4803
|
+
summary,
|
|
4804
|
+
progress: params.progress
|
|
4805
|
+
});
|
|
4516
4806
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
pinnedIds,
|
|
4524
|
-
hiddenIds,
|
|
4525
|
-
summary,
|
|
4526
|
-
progress: params.progress
|
|
4527
|
-
});
|
|
4807
|
+
params.progress?.(
|
|
4808
|
+
`done: groups=${summary.groupsSynced}, groupMessages=${summary.groupMessagesImported}, friends=${summary.friendsSynced}, chats=${summary.chatsSynced}, dmMessages=${summary.dmMessagesImported}`
|
|
4809
|
+
);
|
|
4810
|
+
return summary;
|
|
4811
|
+
} finally {
|
|
4812
|
+
await closeDb(profile);
|
|
4528
4813
|
}
|
|
4529
|
-
params.progress?.(
|
|
4530
|
-
`done: groups=${summary.groupsSynced}, groupMessages=${summary.groupMessagesImported}, friends=${summary.friendsSynced}, chats=${summary.chatsSynced}, dmMessages=${summary.dmMessagesImported}`
|
|
4531
|
-
);
|
|
4532
|
-
return summary;
|
|
4533
4814
|
}
|
|
4534
4815
|
async function buildGroupsDetailed(api) {
|
|
4535
4816
|
const groups = await api.getAllGroups();
|
|
@@ -4580,17 +4861,26 @@ async function listGroupMemberRows(api, groupId) {
|
|
|
4580
4861
|
if (!profile) continue;
|
|
4581
4862
|
const normalizedKey = normalizeGroupMemberId(key);
|
|
4582
4863
|
if (normalizedKey && !profileMap.has(normalizedKey)) {
|
|
4583
|
-
profileMap.set(normalizedKey,
|
|
4864
|
+
profileMap.set(normalizedKey, {
|
|
4865
|
+
...profile,
|
|
4866
|
+
rawJson: JSON.stringify(profile)
|
|
4867
|
+
});
|
|
4584
4868
|
}
|
|
4585
4869
|
const profileId = normalizeGroupMemberId(profile.id);
|
|
4586
4870
|
if (profileId && !profileMap.has(profileId)) {
|
|
4587
|
-
profileMap.set(profileId,
|
|
4871
|
+
profileMap.set(profileId, {
|
|
4872
|
+
...profile,
|
|
4873
|
+
rawJson: JSON.stringify(profile)
|
|
4874
|
+
});
|
|
4588
4875
|
}
|
|
4589
4876
|
}
|
|
4590
4877
|
return ids.map((id) => ({
|
|
4591
4878
|
userId: id,
|
|
4592
4879
|
displayName: profileMap.get(id)?.displayName ?? currentMemberMap.get(id)?.displayName ?? "",
|
|
4593
|
-
zaloName: profileMap.get(id)?.zaloName ?? currentMemberMap.get(id)?.zaloName ?? ""
|
|
4880
|
+
zaloName: profileMap.get(id)?.zaloName ?? currentMemberMap.get(id)?.zaloName ?? "",
|
|
4881
|
+
avatar: profileMap.get(id)?.avatar,
|
|
4882
|
+
accountStatus: profileMap.get(id)?.accountStatus,
|
|
4883
|
+
rawJson: profileMap.get(id)?.rawJson
|
|
4594
4884
|
}));
|
|
4595
4885
|
}
|
|
4596
4886
|
async function listGroupMentionMembers(api, threadId) {
|
|
@@ -6555,52 +6845,69 @@ dbGroup.command("messages <groupId>").option("--since <duration>", "Rolling wind
|
|
|
6555
6845
|
);
|
|
6556
6846
|
})
|
|
6557
6847
|
);
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
)
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
);
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
);
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6848
|
+
function registerDbContactQueryCommand(params) {
|
|
6849
|
+
params.command.command("list").option("-j, --json", "JSON output").description(`List ${params.label} stored in the local DB`).action(
|
|
6850
|
+
wrapAction(async (opts, command) => {
|
|
6851
|
+
const profile = await currentProfile(command);
|
|
6852
|
+
const rows = params.relationship === "friend" ? await listFriends(profile) : await listContacts({ profile });
|
|
6853
|
+
output(rows, Boolean(opts.json));
|
|
6854
|
+
})
|
|
6855
|
+
);
|
|
6856
|
+
params.command.command("find <query>").option("-j, --json", "JSON output").description(`Find stored ${params.label} by ID or name`).action(
|
|
6857
|
+
wrapAction(async (query, opts, command) => {
|
|
6858
|
+
const profile = await currentProfile(command);
|
|
6859
|
+
const rows = params.relationship === "friend" ? await findFriends({ profile, query }) : await findContacts({ profile, query });
|
|
6860
|
+
output(rows, Boolean(opts.json));
|
|
6861
|
+
})
|
|
6862
|
+
);
|
|
6863
|
+
params.command.command("info <userId>").option("-j, --json", "JSON output").description(`Show stored info for a ${params.label.slice(0, -1)}`).action(
|
|
6864
|
+
wrapAction(async (userId, opts, command) => {
|
|
6865
|
+
const profile = await currentProfile(command);
|
|
6866
|
+
const row = params.relationship === "friend" ? await getFriendInfo({ profile, userId }) : await getContactInfo({ profile, userId });
|
|
6867
|
+
if (!row) {
|
|
6868
|
+
throw new Error(`${params.label.slice(0, -1).replace(/^./, (value) => value.toUpperCase())} not found in DB: ${userId}`);
|
|
6869
|
+
}
|
|
6870
|
+
output(row, Boolean(opts.json));
|
|
6871
|
+
})
|
|
6872
|
+
);
|
|
6873
|
+
params.command.command("messages <userId>").option("--since <duration>", "Rolling window ending now: duration like 30s, 7m, 24h, 7d, or 2w").option("--from <time>", "Lower time bound: ISO timestamp, date, or unix seconds/ms").option("--until <time>", "Upper time bound: ISO timestamp, date, or unix seconds/ms").option("--to <time>", "Alias for --until").option("--limit <count>", "Maximum number of rows").option("--all", "Return all matching rows").option("--oldest-first", "Sort oldest-first instead of newest-first").option("-j, --json", "JSON output").description(`List stored direct-message rows for a ${params.label.slice(0, -1)}`).action(
|
|
6874
|
+
wrapAction(async (userId, opts, command) => {
|
|
6875
|
+
const profile = await currentProfile(command);
|
|
6876
|
+
const { sinceMs, untilMs, limit, newestFirst } = resolveMessageQueryOptions(opts);
|
|
6877
|
+
const contact = params.relationship === "friend" ? await getFriendInfo({ profile, userId }) : await getContactInfo({ profile, userId });
|
|
6878
|
+
const threadId = contact && typeof contact.chatId === "string" && contact.chatId.trim() ? contact.chatId : userId;
|
|
6879
|
+
const rows = await listMessages({
|
|
6880
|
+
profile,
|
|
6881
|
+
threadId,
|
|
6882
|
+
threadType: "user",
|
|
6883
|
+
sinceMs,
|
|
6884
|
+
untilMs,
|
|
6885
|
+
limit,
|
|
6886
|
+
newestFirst
|
|
6887
|
+
});
|
|
6888
|
+
output(
|
|
6889
|
+
{
|
|
6890
|
+
userId,
|
|
6891
|
+
chatId: threadId,
|
|
6892
|
+
count: rows.length,
|
|
6893
|
+
messages: rows
|
|
6894
|
+
},
|
|
6895
|
+
Boolean(opts.json)
|
|
6896
|
+
);
|
|
6897
|
+
})
|
|
6898
|
+
);
|
|
6899
|
+
}
|
|
6900
|
+
var dbContact = dbCmd.command("contact").description("Query stored contact data");
|
|
6901
|
+
registerDbContactQueryCommand({
|
|
6902
|
+
command: dbContact,
|
|
6903
|
+
label: "contacts"
|
|
6904
|
+
});
|
|
6905
|
+
var dbFriend = dbCmd.command("friend").description("Query stored confirmed friend contacts");
|
|
6906
|
+
registerDbContactQueryCommand({
|
|
6907
|
+
command: dbFriend,
|
|
6908
|
+
label: "friends",
|
|
6909
|
+
relationship: "friend"
|
|
6910
|
+
});
|
|
6604
6911
|
var dbChat = dbCmd.command("chat").description("Query stored conversation data");
|
|
6605
6912
|
dbChat.command("list").option("-j, --json", "JSON output").description("List chats stored in the local DB").action(
|
|
6606
6913
|
wrapAction(async (opts, command) => {
|
|
@@ -8409,34 +8716,54 @@ ${replyContextText}` : replyContextText;
|
|
|
8409
8716
|
rawJson: JSON.stringify(mention)
|
|
8410
8717
|
}));
|
|
8411
8718
|
scheduleDbWrite(profile, command, "listen.db.persist_error", async () => {
|
|
8412
|
-
|
|
8413
|
-
|
|
8719
|
+
const normalizedRecord = normalizeInboundListenRecord({
|
|
8720
|
+
profile,
|
|
8721
|
+
threadType: chatType,
|
|
8722
|
+
rawThreadId: message.threadId,
|
|
8723
|
+
senderId,
|
|
8724
|
+
senderName: senderDisplayName,
|
|
8725
|
+
toId,
|
|
8726
|
+
selfId,
|
|
8727
|
+
title: chatType === "group" ? threadName : senderDisplayName,
|
|
8728
|
+
msgId: message.data.msgId,
|
|
8729
|
+
cliMsgId: message.data.cliMsgId,
|
|
8730
|
+
actionId: getStringCandidate(messageData, ["actionId"]),
|
|
8731
|
+
timestampMs,
|
|
8732
|
+
msgType: msgType || void 0,
|
|
8733
|
+
contentText: processedText || rawText || void 0,
|
|
8734
|
+
contentJson: rawContent && typeof rawContent === "object" ? JSON.stringify(rawContent) : void 0,
|
|
8735
|
+
quoteMsgId: quote?.globalMsgId ? String(quote.globalMsgId) : void 0,
|
|
8736
|
+
quoteCliMsgId: quote?.cliMsgId ? String(quote.cliMsgId) : void 0,
|
|
8737
|
+
quoteOwnerId: quote?.ownerId ? String(quote.ownerId) : void 0,
|
|
8738
|
+
quoteText: quote?.msg,
|
|
8739
|
+
media: mediaForDb,
|
|
8740
|
+
mentions: mentionsForDb,
|
|
8741
|
+
rawMessage: message.data,
|
|
8742
|
+
rawPayload: payload,
|
|
8743
|
+
source: "listen"
|
|
8744
|
+
});
|
|
8745
|
+
if (chatType === "group") {
|
|
8746
|
+
await hydrateUnknownLiveGroup({
|
|
8747
|
+
profile,
|
|
8748
|
+
api,
|
|
8749
|
+
groupId: normalizedRecord.scopeThreadId,
|
|
8750
|
+
fallbackTitle: threadName
|
|
8751
|
+
});
|
|
8752
|
+
} else {
|
|
8753
|
+
await persistLiveDmContact({
|
|
8414
8754
|
profile,
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8755
|
+
api,
|
|
8756
|
+
peerId: normalizedRecord.scopeThreadId,
|
|
8757
|
+
senderDisplayName,
|
|
8418
8758
|
senderName: senderDisplayName,
|
|
8419
|
-
toId,
|
|
8420
|
-
selfId,
|
|
8421
|
-
title: threadName,
|
|
8422
|
-
msgId: message.data.msgId,
|
|
8423
|
-
cliMsgId: message.data.cliMsgId,
|
|
8424
|
-
actionId: getStringCandidate(messageData, ["actionId"]),
|
|
8425
8759
|
timestampMs,
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
media: mediaForDb,
|
|
8434
|
-
mentions: mentionsForDb,
|
|
8435
|
-
rawMessage: message.data,
|
|
8436
|
-
rawPayload: payload,
|
|
8437
|
-
source: "listen"
|
|
8438
|
-
})
|
|
8439
|
-
);
|
|
8760
|
+
rawJson: JSON.stringify({
|
|
8761
|
+
userId: normalizedRecord.scopeThreadId,
|
|
8762
|
+
displayName: senderDisplayName
|
|
8763
|
+
})
|
|
8764
|
+
});
|
|
8765
|
+
}
|
|
8766
|
+
await persistMessage(normalizedRecord);
|
|
8440
8767
|
});
|
|
8441
8768
|
}
|
|
8442
8769
|
if (opts.raw) {
|