openzca 0.1.49 → 0.1.51
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 +15 -2
- package/dist/cli.js +701 -360
- package/dist/db-worker.js +292 -0
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { createRequire } from "module";
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
5
|
import { spawn as spawn2 } from "child_process";
|
|
6
6
|
import fsSync from "fs";
|
|
7
7
|
import fs6 from "fs/promises";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
Gender,
|
|
17
17
|
Reactions,
|
|
18
18
|
ReviewPendingMemberRequestStatus,
|
|
19
|
-
ThreadType as
|
|
19
|
+
ThreadType as ThreadType3
|
|
20
20
|
} from "zca-js";
|
|
21
21
|
|
|
22
22
|
// src/lib/store.ts
|
|
@@ -214,13 +214,273 @@ async function clearCache(profileName) {
|
|
|
214
214
|
// src/lib/db.ts
|
|
215
215
|
import crypto from "crypto";
|
|
216
216
|
import fs2 from "fs/promises";
|
|
217
|
+
import { createRequire } from "module";
|
|
217
218
|
import path2 from "path";
|
|
218
|
-
import {
|
|
219
|
-
|
|
219
|
+
import { Worker } from "worker_threads";
|
|
220
|
+
var require2 = createRequire(import.meta.url);
|
|
221
|
+
function buildDbError(error) {
|
|
222
|
+
const built = new Error(error.message);
|
|
223
|
+
built.name = error.name || "Error";
|
|
224
|
+
built.stack = error.stack ?? built.stack;
|
|
225
|
+
built.code = error.code;
|
|
226
|
+
return built;
|
|
227
|
+
}
|
|
228
|
+
function resolveWorkerSpec() {
|
|
229
|
+
const currentUrl = new URL(import.meta.url);
|
|
230
|
+
if (currentUrl.pathname.endsWith("/src/lib/db.ts")) {
|
|
231
|
+
return {
|
|
232
|
+
url: new URL("./db-worker.ts", currentUrl),
|
|
233
|
+
execArgv: ["--import", require2.resolve("tsx")]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
url: new URL("./db-worker.js", currentUrl)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
var Database = class _Database {
|
|
241
|
+
#worker;
|
|
242
|
+
#closed = false;
|
|
243
|
+
#closing = false;
|
|
244
|
+
#released = false;
|
|
245
|
+
#nextId = 1;
|
|
246
|
+
#activeRequests = 0;
|
|
247
|
+
#idleTimer;
|
|
248
|
+
#closePromise;
|
|
249
|
+
#pending = /* @__PURE__ */ new Map();
|
|
250
|
+
#ready;
|
|
251
|
+
#exited;
|
|
252
|
+
#releaseConnection;
|
|
253
|
+
constructor(worker, releaseConnection) {
|
|
254
|
+
this.#worker = worker;
|
|
255
|
+
this.#releaseConnection = releaseConnection;
|
|
256
|
+
let resolveReady;
|
|
257
|
+
let rejectReady;
|
|
258
|
+
this.#ready = new Promise((resolve, reject) => {
|
|
259
|
+
resolveReady = resolve;
|
|
260
|
+
rejectReady = reject;
|
|
261
|
+
});
|
|
262
|
+
let resolveExited;
|
|
263
|
+
this.#exited = new Promise((resolve) => {
|
|
264
|
+
resolveExited = resolve;
|
|
265
|
+
});
|
|
266
|
+
const rejectPending = (error) => {
|
|
267
|
+
for (const { reject } of this.#pending.values()) {
|
|
268
|
+
reject(error);
|
|
269
|
+
}
|
|
270
|
+
this.#pending.clear();
|
|
271
|
+
};
|
|
272
|
+
worker.on("message", (message) => {
|
|
273
|
+
if (message.type === "ready") {
|
|
274
|
+
resolveReady();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (message.type === "fatal") {
|
|
278
|
+
const error = buildDbError(message.error);
|
|
279
|
+
rejectReady(error);
|
|
280
|
+
rejectPending(error);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const pending = this.#pending.get(message.id);
|
|
284
|
+
if (!pending) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.#pending.delete(message.id);
|
|
288
|
+
if (message.type === "error") {
|
|
289
|
+
pending.reject(buildDbError(message.error));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
pending.resolve(message.result);
|
|
293
|
+
});
|
|
294
|
+
worker.once("error", (error) => {
|
|
295
|
+
rejectReady(error);
|
|
296
|
+
rejectPending(error instanceof Error ? error : new Error(String(error)));
|
|
297
|
+
});
|
|
298
|
+
worker.once("exit", (code) => {
|
|
299
|
+
this.#closed = true;
|
|
300
|
+
this.#release();
|
|
301
|
+
resolveExited();
|
|
302
|
+
const error = new Error(
|
|
303
|
+
code === 0 ? "DB worker exited" : `DB worker exited with code ${code}`
|
|
304
|
+
);
|
|
305
|
+
if (code !== 0) {
|
|
306
|
+
rejectReady(error);
|
|
307
|
+
}
|
|
308
|
+
if (this.#pending.size > 0) {
|
|
309
|
+
rejectPending(error);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
static async open(filename, onClosed) {
|
|
314
|
+
const { url, execArgv } = resolveWorkerSpec();
|
|
315
|
+
const worker = new Worker(url, {
|
|
316
|
+
execArgv,
|
|
317
|
+
workerData: { filename }
|
|
318
|
+
});
|
|
319
|
+
worker.unref();
|
|
320
|
+
const db = new _Database(worker, onClosed);
|
|
321
|
+
await db.#ready;
|
|
322
|
+
return db;
|
|
323
|
+
}
|
|
324
|
+
get isClosing() {
|
|
325
|
+
return this.#closing || this.#closed;
|
|
326
|
+
}
|
|
327
|
+
#release() {
|
|
328
|
+
if (this.#released) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.#released = true;
|
|
332
|
+
this.#releaseConnection();
|
|
333
|
+
}
|
|
334
|
+
#clearIdleClose() {
|
|
335
|
+
if (this.#idleTimer) {
|
|
336
|
+
clearTimeout(this.#idleTimer);
|
|
337
|
+
this.#idleTimer = void 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
#scheduleIdleClose() {
|
|
341
|
+
this.#clearIdleClose();
|
|
342
|
+
this.#idleTimer = setTimeout(() => {
|
|
343
|
+
if (this.#activeRequests === 0 && !this.#closed) {
|
|
344
|
+
void this.close().catch(() => {
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}, 100);
|
|
348
|
+
this.#idleTimer.unref();
|
|
349
|
+
}
|
|
350
|
+
async #request(type, payload) {
|
|
351
|
+
if (this.#closed) {
|
|
352
|
+
throw new Error("DB worker is closed");
|
|
353
|
+
}
|
|
354
|
+
if (this.#closing && type !== "close") {
|
|
355
|
+
throw new Error("DB worker is closing");
|
|
356
|
+
}
|
|
357
|
+
this.#clearIdleClose();
|
|
358
|
+
this.#activeRequests += 1;
|
|
359
|
+
await this.#ready;
|
|
360
|
+
const id = this.#nextId;
|
|
361
|
+
this.#nextId += 1;
|
|
362
|
+
const request = payload === void 0 ? { id, type } : { id, type, payload };
|
|
363
|
+
const result = new Promise((resolve, reject) => {
|
|
364
|
+
this.#pending.set(id, { resolve, reject });
|
|
365
|
+
});
|
|
366
|
+
try {
|
|
367
|
+
this.#worker.postMessage(request);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.#pending.delete(id);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
return await result;
|
|
374
|
+
} finally {
|
|
375
|
+
this.#activeRequests -= 1;
|
|
376
|
+
if (this.#activeRequests === 0 && !this.#closed && !this.#closing) {
|
|
377
|
+
this.#scheduleIdleClose();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async exec(sql) {
|
|
382
|
+
await this.#request("exec", { sql });
|
|
383
|
+
}
|
|
384
|
+
async run(sql, params = []) {
|
|
385
|
+
return await this.#request("run", { sql, params });
|
|
386
|
+
}
|
|
387
|
+
async get(sql, params = []) {
|
|
388
|
+
const result = await this.#request("get", { sql, params });
|
|
389
|
+
return result ?? void 0;
|
|
390
|
+
}
|
|
391
|
+
async all(sql, params = []) {
|
|
392
|
+
return await this.#request("all", { sql, params });
|
|
393
|
+
}
|
|
394
|
+
async batch(commands, transactional = false) {
|
|
395
|
+
await this.#request("batch", { commands, transactional });
|
|
396
|
+
}
|
|
397
|
+
async close() {
|
|
398
|
+
if (this.#closed) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (this.#closePromise) {
|
|
402
|
+
return this.#closePromise;
|
|
403
|
+
}
|
|
404
|
+
this.#closePromise = (async () => {
|
|
405
|
+
this.#closing = true;
|
|
406
|
+
this.#release();
|
|
407
|
+
this.#clearIdleClose();
|
|
408
|
+
await this.#request("close");
|
|
409
|
+
this.#closed = true;
|
|
410
|
+
await this.#exited;
|
|
411
|
+
})();
|
|
412
|
+
return this.#closePromise;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
220
415
|
var DB_CONFIG_FILE = "db.json";
|
|
221
416
|
var DB_FILENAME = "messages.sqlite";
|
|
222
417
|
var connections = /* @__PURE__ */ new Map();
|
|
223
418
|
var writeQueues = /* @__PURE__ */ new Map();
|
|
419
|
+
var UPSERT_THREAD_SQL = `
|
|
420
|
+
INSERT INTO threads (
|
|
421
|
+
profile, scope_thread_id, raw_thread_id, thread_type, peer_id, title,
|
|
422
|
+
is_pinned, is_hidden, is_archived, raw_json, created_at, updated_at
|
|
423
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
424
|
+
ON CONFLICT(profile, scope_thread_id) DO UPDATE SET
|
|
425
|
+
raw_thread_id = excluded.raw_thread_id,
|
|
426
|
+
thread_type = excluded.thread_type,
|
|
427
|
+
peer_id = COALESCE(excluded.peer_id, threads.peer_id),
|
|
428
|
+
title = COALESCE(excluded.title, threads.title),
|
|
429
|
+
is_pinned = excluded.is_pinned,
|
|
430
|
+
is_hidden = excluded.is_hidden,
|
|
431
|
+
is_archived = excluded.is_archived,
|
|
432
|
+
raw_json = COALESCE(excluded.raw_json, threads.raw_json),
|
|
433
|
+
updated_at = excluded.updated_at
|
|
434
|
+
`;
|
|
435
|
+
var INSERT_THREAD_MEMBER_SQL = `
|
|
436
|
+
INSERT INTO thread_members (
|
|
437
|
+
profile, scope_thread_id, user_id, display_name, zalo_name, avatar,
|
|
438
|
+
account_status, member_type, raw_json, snapshot_at_ms, created_at, updated_at
|
|
439
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
440
|
+
`;
|
|
441
|
+
var UPSERT_MESSAGE_SQL = `
|
|
442
|
+
INSERT INTO messages (
|
|
443
|
+
profile, message_uid, scope_thread_id, raw_thread_id, thread_type,
|
|
444
|
+
msg_id, cli_msg_id, action_id, sender_id, sender_name, to_id,
|
|
445
|
+
timestamp_ms, msg_type, content_text, content_json,
|
|
446
|
+
quote_msg_id, quote_cli_msg_id, quote_owner_id, quote_text,
|
|
447
|
+
source, raw_message_json, raw_payload_json, created_at, updated_at
|
|
448
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
449
|
+
ON CONFLICT(profile, message_uid) DO UPDATE SET
|
|
450
|
+
scope_thread_id = excluded.scope_thread_id,
|
|
451
|
+
raw_thread_id = excluded.raw_thread_id,
|
|
452
|
+
thread_type = excluded.thread_type,
|
|
453
|
+
msg_id = COALESCE(excluded.msg_id, messages.msg_id),
|
|
454
|
+
cli_msg_id = COALESCE(excluded.cli_msg_id, messages.cli_msg_id),
|
|
455
|
+
action_id = COALESCE(excluded.action_id, messages.action_id),
|
|
456
|
+
sender_id = COALESCE(excluded.sender_id, messages.sender_id),
|
|
457
|
+
sender_name = COALESCE(excluded.sender_name, messages.sender_name),
|
|
458
|
+
to_id = COALESCE(excluded.to_id, messages.to_id),
|
|
459
|
+
timestamp_ms = excluded.timestamp_ms,
|
|
460
|
+
msg_type = COALESCE(excluded.msg_type, messages.msg_type),
|
|
461
|
+
content_text = COALESCE(excluded.content_text, messages.content_text),
|
|
462
|
+
content_json = COALESCE(excluded.content_json, messages.content_json),
|
|
463
|
+
quote_msg_id = COALESCE(excluded.quote_msg_id, messages.quote_msg_id),
|
|
464
|
+
quote_cli_msg_id = COALESCE(excluded.quote_cli_msg_id, messages.quote_cli_msg_id),
|
|
465
|
+
quote_owner_id = COALESCE(excluded.quote_owner_id, messages.quote_owner_id),
|
|
466
|
+
quote_text = COALESCE(excluded.quote_text, messages.quote_text),
|
|
467
|
+
source = excluded.source,
|
|
468
|
+
raw_message_json = COALESCE(excluded.raw_message_json, messages.raw_message_json),
|
|
469
|
+
raw_payload_json = COALESCE(excluded.raw_payload_json, messages.raw_payload_json),
|
|
470
|
+
updated_at = excluded.updated_at
|
|
471
|
+
`;
|
|
472
|
+
var INSERT_MESSAGE_MEDIA_SQL = `
|
|
473
|
+
INSERT INTO message_media (
|
|
474
|
+
profile, message_uid, item_index, media_kind, media_url,
|
|
475
|
+
media_path, media_type, raw_json, created_at, updated_at
|
|
476
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
477
|
+
`;
|
|
478
|
+
var INSERT_MESSAGE_MENTION_SQL = `
|
|
479
|
+
INSERT INTO message_mentions (
|
|
480
|
+
profile, message_uid, item_index, target_user_id, pos, len,
|
|
481
|
+
mention_type, raw_json, created_at, updated_at
|
|
482
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
483
|
+
`;
|
|
224
484
|
function nowIso2() {
|
|
225
485
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
226
486
|
}
|
|
@@ -326,165 +586,21 @@ async function resolveDbPath(profile) {
|
|
|
326
586
|
}
|
|
327
587
|
return path2.isAbsolute(configured) ? configured : path2.resolve(getProfileDir(profile), configured);
|
|
328
588
|
}
|
|
329
|
-
async function migrateDb(db) {
|
|
330
|
-
await db.exec(`
|
|
331
|
-
PRAGMA journal_mode = WAL;
|
|
332
|
-
PRAGMA foreign_keys = ON;
|
|
333
|
-
|
|
334
|
-
CREATE TABLE IF NOT EXISTS threads (
|
|
335
|
-
profile TEXT NOT NULL,
|
|
336
|
-
scope_thread_id TEXT NOT NULL,
|
|
337
|
-
raw_thread_id TEXT NOT NULL,
|
|
338
|
-
thread_type TEXT NOT NULL,
|
|
339
|
-
peer_id TEXT,
|
|
340
|
-
title TEXT,
|
|
341
|
-
is_pinned INTEGER NOT NULL DEFAULT 0,
|
|
342
|
-
is_hidden INTEGER NOT NULL DEFAULT 0,
|
|
343
|
-
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
344
|
-
raw_json TEXT,
|
|
345
|
-
created_at TEXT NOT NULL,
|
|
346
|
-
updated_at TEXT NOT NULL,
|
|
347
|
-
PRIMARY KEY (profile, scope_thread_id)
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
CREATE TABLE IF NOT EXISTS thread_members (
|
|
351
|
-
profile TEXT NOT NULL,
|
|
352
|
-
scope_thread_id TEXT NOT NULL,
|
|
353
|
-
user_id TEXT NOT NULL,
|
|
354
|
-
display_name TEXT,
|
|
355
|
-
zalo_name TEXT,
|
|
356
|
-
avatar TEXT,
|
|
357
|
-
account_status INTEGER,
|
|
358
|
-
member_type INTEGER,
|
|
359
|
-
raw_json TEXT,
|
|
360
|
-
snapshot_at_ms INTEGER NOT NULL,
|
|
361
|
-
created_at TEXT NOT NULL,
|
|
362
|
-
updated_at TEXT NOT NULL,
|
|
363
|
-
PRIMARY KEY (profile, scope_thread_id, user_id)
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
CREATE TABLE IF NOT EXISTS friends (
|
|
367
|
-
profile TEXT NOT NULL,
|
|
368
|
-
user_id TEXT NOT NULL,
|
|
369
|
-
display_name TEXT,
|
|
370
|
-
zalo_name TEXT,
|
|
371
|
-
avatar TEXT,
|
|
372
|
-
account_status INTEGER,
|
|
373
|
-
raw_json TEXT,
|
|
374
|
-
created_at TEXT NOT NULL,
|
|
375
|
-
updated_at TEXT NOT NULL,
|
|
376
|
-
PRIMARY KEY (profile, user_id)
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
CREATE TABLE IF NOT EXISTS self_profiles (
|
|
380
|
-
profile TEXT NOT NULL,
|
|
381
|
-
user_id TEXT NOT NULL,
|
|
382
|
-
display_name TEXT,
|
|
383
|
-
info_json TEXT,
|
|
384
|
-
created_at TEXT NOT NULL,
|
|
385
|
-
updated_at TEXT NOT NULL,
|
|
386
|
-
PRIMARY KEY (profile)
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
390
|
-
profile TEXT NOT NULL,
|
|
391
|
-
message_uid TEXT NOT NULL,
|
|
392
|
-
scope_thread_id TEXT NOT NULL,
|
|
393
|
-
raw_thread_id TEXT NOT NULL,
|
|
394
|
-
thread_type TEXT NOT NULL,
|
|
395
|
-
msg_id TEXT,
|
|
396
|
-
cli_msg_id TEXT,
|
|
397
|
-
action_id TEXT,
|
|
398
|
-
sender_id TEXT,
|
|
399
|
-
sender_name TEXT,
|
|
400
|
-
to_id TEXT,
|
|
401
|
-
timestamp_ms INTEGER NOT NULL,
|
|
402
|
-
msg_type TEXT,
|
|
403
|
-
content_text TEXT,
|
|
404
|
-
content_json TEXT,
|
|
405
|
-
quote_msg_id TEXT,
|
|
406
|
-
quote_cli_msg_id TEXT,
|
|
407
|
-
quote_owner_id TEXT,
|
|
408
|
-
quote_text TEXT,
|
|
409
|
-
source TEXT NOT NULL,
|
|
410
|
-
raw_message_json TEXT,
|
|
411
|
-
raw_payload_json TEXT,
|
|
412
|
-
created_at TEXT NOT NULL,
|
|
413
|
-
updated_at TEXT NOT NULL,
|
|
414
|
-
PRIMARY KEY (profile, message_uid)
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
CREATE TABLE IF NOT EXISTS message_media (
|
|
418
|
-
profile TEXT NOT NULL,
|
|
419
|
-
message_uid TEXT NOT NULL,
|
|
420
|
-
item_index INTEGER NOT NULL,
|
|
421
|
-
media_kind TEXT,
|
|
422
|
-
media_url TEXT,
|
|
423
|
-
media_path TEXT,
|
|
424
|
-
media_type TEXT,
|
|
425
|
-
raw_json TEXT,
|
|
426
|
-
created_at TEXT NOT NULL,
|
|
427
|
-
updated_at TEXT NOT NULL,
|
|
428
|
-
PRIMARY KEY (profile, message_uid, item_index)
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
432
|
-
profile TEXT NOT NULL,
|
|
433
|
-
message_uid TEXT NOT NULL,
|
|
434
|
-
item_index INTEGER NOT NULL,
|
|
435
|
-
target_user_id TEXT NOT NULL,
|
|
436
|
-
pos INTEGER,
|
|
437
|
-
len INTEGER,
|
|
438
|
-
mention_type INTEGER,
|
|
439
|
-
raw_json TEXT,
|
|
440
|
-
created_at TEXT NOT NULL,
|
|
441
|
-
updated_at TEXT NOT NULL,
|
|
442
|
-
PRIMARY KEY (profile, message_uid, item_index)
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
CREATE TABLE IF NOT EXISTS sync_state (
|
|
446
|
-
profile TEXT NOT NULL,
|
|
447
|
-
scope TEXT NOT NULL,
|
|
448
|
-
scope_thread_id TEXT NOT NULL,
|
|
449
|
-
thread_type TEXT NOT NULL,
|
|
450
|
-
status TEXT NOT NULL,
|
|
451
|
-
completeness TEXT,
|
|
452
|
-
cursor TEXT,
|
|
453
|
-
last_sync_at TEXT,
|
|
454
|
-
error TEXT,
|
|
455
|
-
created_at TEXT NOT NULL,
|
|
456
|
-
updated_at TEXT NOT NULL,
|
|
457
|
-
PRIMARY KEY (profile, scope)
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
CREATE INDEX IF NOT EXISTS idx_messages_thread_time
|
|
461
|
-
ON messages (profile, scope_thread_id, timestamp_ms DESC);
|
|
462
|
-
CREATE INDEX IF NOT EXISTS idx_messages_msg_id
|
|
463
|
-
ON messages (profile, msg_id);
|
|
464
|
-
CREATE INDEX IF NOT EXISTS idx_messages_cli_msg_id
|
|
465
|
-
ON messages (profile, cli_msg_id);
|
|
466
|
-
CREATE INDEX IF NOT EXISTS idx_threads_type
|
|
467
|
-
ON threads (profile, thread_type, updated_at DESC);
|
|
468
|
-
CREATE INDEX IF NOT EXISTS idx_members_thread
|
|
469
|
-
ON thread_members (profile, scope_thread_id);
|
|
470
|
-
CREATE INDEX IF NOT EXISTS idx_friends_name
|
|
471
|
-
ON friends (profile, display_name, zalo_name, user_id);
|
|
472
|
-
`);
|
|
473
|
-
}
|
|
474
589
|
async function openDb(profile) {
|
|
475
590
|
const filename = await resolveDbPath(profile);
|
|
476
591
|
await fs2.mkdir(path2.dirname(filename), { recursive: true });
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
driver: sqlite3.Database
|
|
592
|
+
return Database.open(filename, () => {
|
|
593
|
+
connections.delete(profile);
|
|
480
594
|
});
|
|
481
|
-
await migrateDb(db);
|
|
482
|
-
return db;
|
|
483
595
|
}
|
|
484
596
|
async function getDb(profile) {
|
|
485
597
|
const existing = connections.get(profile);
|
|
486
598
|
if (existing) {
|
|
487
|
-
|
|
599
|
+
const db = await existing;
|
|
600
|
+
if (!db.isClosing) {
|
|
601
|
+
return db;
|
|
602
|
+
}
|
|
603
|
+
connections.delete(profile);
|
|
488
604
|
}
|
|
489
605
|
const created = openDb(profile).catch((error) => {
|
|
490
606
|
connections.delete(profile);
|
|
@@ -596,77 +712,48 @@ function enqueueDbWrite(profile, task) {
|
|
|
596
712
|
async function persistThread(record) {
|
|
597
713
|
const db = await getDb(record.profile);
|
|
598
714
|
const now = nowIso2();
|
|
599
|
-
await db.run(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
raw_json = COALESCE(excluded.raw_json, threads.raw_json),
|
|
614
|
-
updated_at = excluded.updated_at
|
|
615
|
-
`,
|
|
616
|
-
[
|
|
617
|
-
record.profile,
|
|
618
|
-
record.scopeThreadId,
|
|
619
|
-
record.rawThreadId,
|
|
620
|
-
record.threadType,
|
|
621
|
-
record.peerId ?? null,
|
|
622
|
-
record.title ?? null,
|
|
623
|
-
record.isPinned ? 1 : 0,
|
|
624
|
-
record.isHidden ? 1 : 0,
|
|
625
|
-
record.isArchived ? 1 : 0,
|
|
626
|
-
record.rawJson ?? null,
|
|
627
|
-
now,
|
|
628
|
-
now
|
|
629
|
-
]
|
|
630
|
-
);
|
|
715
|
+
await db.run(UPSERT_THREAD_SQL, [
|
|
716
|
+
record.profile,
|
|
717
|
+
record.scopeThreadId,
|
|
718
|
+
record.rawThreadId,
|
|
719
|
+
record.threadType,
|
|
720
|
+
record.peerId ?? null,
|
|
721
|
+
record.title ?? null,
|
|
722
|
+
record.isPinned ? 1 : 0,
|
|
723
|
+
record.isHidden ? 1 : 0,
|
|
724
|
+
record.isArchived ? 1 : 0,
|
|
725
|
+
record.rawJson ?? null,
|
|
726
|
+
now,
|
|
727
|
+
now
|
|
728
|
+
]);
|
|
631
729
|
}
|
|
632
730
|
async function replaceThreadMembers(profile, scopeThreadId, members) {
|
|
633
731
|
const db = await getDb(profile);
|
|
634
732
|
const now = nowIso2();
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
)
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
member.snapshotAtMs,
|
|
660
|
-
now,
|
|
661
|
-
now
|
|
662
|
-
]
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
await db.exec("COMMIT");
|
|
666
|
-
} catch (error) {
|
|
667
|
-
await db.exec("ROLLBACK");
|
|
668
|
-
throw error;
|
|
669
|
-
}
|
|
733
|
+
const commands = [
|
|
734
|
+
{
|
|
735
|
+
sql: `DELETE FROM thread_members WHERE profile = ? AND scope_thread_id = ?`,
|
|
736
|
+
params: [profile, scopeThreadId]
|
|
737
|
+
},
|
|
738
|
+
...members.map((member) => ({
|
|
739
|
+
sql: INSERT_THREAD_MEMBER_SQL,
|
|
740
|
+
params: [
|
|
741
|
+
member.profile,
|
|
742
|
+
member.scopeThreadId,
|
|
743
|
+
member.userId,
|
|
744
|
+
member.displayName ?? null,
|
|
745
|
+
member.zaloName ?? null,
|
|
746
|
+
member.avatar ?? null,
|
|
747
|
+
member.accountStatus ?? null,
|
|
748
|
+
member.memberType ?? null,
|
|
749
|
+
member.rawJson ?? null,
|
|
750
|
+
member.snapshotAtMs,
|
|
751
|
+
now,
|
|
752
|
+
now
|
|
753
|
+
]
|
|
754
|
+
}))
|
|
755
|
+
];
|
|
756
|
+
await db.batch(commands, true);
|
|
670
757
|
}
|
|
671
758
|
async function persistFriend(record) {
|
|
672
759
|
const db = await getDb(record.profile);
|
|
@@ -726,49 +813,27 @@ async function persistMessage(record) {
|
|
|
726
813
|
const db = await getDb(record.profile);
|
|
727
814
|
const now = nowIso2();
|
|
728
815
|
const messageUid = toMessageUid(record);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
raw_thread_id = excluded.raw_thread_id,
|
|
751
|
-
thread_type = excluded.thread_type,
|
|
752
|
-
msg_id = COALESCE(excluded.msg_id, messages.msg_id),
|
|
753
|
-
cli_msg_id = COALESCE(excluded.cli_msg_id, messages.cli_msg_id),
|
|
754
|
-
action_id = COALESCE(excluded.action_id, messages.action_id),
|
|
755
|
-
sender_id = COALESCE(excluded.sender_id, messages.sender_id),
|
|
756
|
-
sender_name = COALESCE(excluded.sender_name, messages.sender_name),
|
|
757
|
-
to_id = COALESCE(excluded.to_id, messages.to_id),
|
|
758
|
-
timestamp_ms = excluded.timestamp_ms,
|
|
759
|
-
msg_type = COALESCE(excluded.msg_type, messages.msg_type),
|
|
760
|
-
content_text = COALESCE(excluded.content_text, messages.content_text),
|
|
761
|
-
content_json = COALESCE(excluded.content_json, messages.content_json),
|
|
762
|
-
quote_msg_id = COALESCE(excluded.quote_msg_id, messages.quote_msg_id),
|
|
763
|
-
quote_cli_msg_id = COALESCE(excluded.quote_cli_msg_id, messages.quote_cli_msg_id),
|
|
764
|
-
quote_owner_id = COALESCE(excluded.quote_owner_id, messages.quote_owner_id),
|
|
765
|
-
quote_text = COALESCE(excluded.quote_text, messages.quote_text),
|
|
766
|
-
source = excluded.source,
|
|
767
|
-
raw_message_json = COALESCE(excluded.raw_message_json, messages.raw_message_json),
|
|
768
|
-
raw_payload_json = COALESCE(excluded.raw_payload_json, messages.raw_payload_json),
|
|
769
|
-
updated_at = excluded.updated_at
|
|
770
|
-
`,
|
|
771
|
-
[
|
|
816
|
+
const commands = [
|
|
817
|
+
{
|
|
818
|
+
sql: UPSERT_THREAD_SQL,
|
|
819
|
+
params: [
|
|
820
|
+
record.profile,
|
|
821
|
+
record.scopeThreadId,
|
|
822
|
+
record.rawThreadId,
|
|
823
|
+
record.threadType,
|
|
824
|
+
record.peerId ?? null,
|
|
825
|
+
record.title ?? null,
|
|
826
|
+
0,
|
|
827
|
+
0,
|
|
828
|
+
0,
|
|
829
|
+
null,
|
|
830
|
+
now,
|
|
831
|
+
now
|
|
832
|
+
]
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
sql: UPSERT_MESSAGE_SQL,
|
|
836
|
+
params: [
|
|
772
837
|
record.profile,
|
|
773
838
|
messageUid,
|
|
774
839
|
record.scopeThreadId,
|
|
@@ -794,64 +859,47 @@ async function persistMessage(record) {
|
|
|
794
859
|
now,
|
|
795
860
|
now
|
|
796
861
|
]
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
`DELETE FROM message_media WHERE profile = ? AND message_uid = ?`,
|
|
800
|
-
[record.profile, messageUid]
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
`DELETE FROM message_mentions WHERE profile = ? AND message_uid = ?`,
|
|
804
|
-
[record.profile, messageUid]
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
messageUid,
|
|
839
|
-
index,
|
|
840
|
-
mention.uid,
|
|
841
|
-
mention.pos ?? null,
|
|
842
|
-
mention.len ?? null,
|
|
843
|
-
mention.type ?? null,
|
|
844
|
-
mention.rawJson ?? null,
|
|
845
|
-
now,
|
|
846
|
-
now
|
|
847
|
-
]
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
await db.exec("COMMIT");
|
|
851
|
-
} catch (error) {
|
|
852
|
-
await db.exec("ROLLBACK");
|
|
853
|
-
throw error;
|
|
854
|
-
}
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
sql: `DELETE FROM message_media WHERE profile = ? AND message_uid = ?`,
|
|
865
|
+
params: [record.profile, messageUid]
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
sql: `DELETE FROM message_mentions WHERE profile = ? AND message_uid = ?`,
|
|
869
|
+
params: [record.profile, messageUid]
|
|
870
|
+
},
|
|
871
|
+
...(record.media ?? []).map((media, index) => ({
|
|
872
|
+
sql: INSERT_MESSAGE_MEDIA_SQL,
|
|
873
|
+
params: [
|
|
874
|
+
record.profile,
|
|
875
|
+
messageUid,
|
|
876
|
+
index,
|
|
877
|
+
media.mediaKind ?? null,
|
|
878
|
+
media.mediaUrl ?? null,
|
|
879
|
+
media.mediaPath ?? null,
|
|
880
|
+
media.mediaType ?? null,
|
|
881
|
+
media.rawJson ?? null,
|
|
882
|
+
now,
|
|
883
|
+
now
|
|
884
|
+
]
|
|
885
|
+
})),
|
|
886
|
+
...(record.mentions ?? []).map((mention, index) => ({
|
|
887
|
+
sql: INSERT_MESSAGE_MENTION_SQL,
|
|
888
|
+
params: [
|
|
889
|
+
record.profile,
|
|
890
|
+
messageUid,
|
|
891
|
+
index,
|
|
892
|
+
mention.uid,
|
|
893
|
+
mention.pos ?? null,
|
|
894
|
+
mention.len ?? null,
|
|
895
|
+
mention.type ?? null,
|
|
896
|
+
mention.rawJson ?? null,
|
|
897
|
+
now,
|
|
898
|
+
now
|
|
899
|
+
]
|
|
900
|
+
}))
|
|
901
|
+
];
|
|
902
|
+
await db.batch(commands, true);
|
|
855
903
|
}
|
|
856
904
|
async function setSyncState(params) {
|
|
857
905
|
const db = await getDb(params.profile);
|
|
@@ -2494,9 +2542,234 @@ async function sendNativeVideo(params) {
|
|
|
2494
2542
|
}
|
|
2495
2543
|
}
|
|
2496
2544
|
|
|
2545
|
+
// src/lib/reply.ts
|
|
2546
|
+
import { ThreadType as ThreadType2 } from "zca-js";
|
|
2547
|
+
function prepareReplyMessage(value, params) {
|
|
2548
|
+
const sourceRecord = asReplyMessageRecord(value);
|
|
2549
|
+
const metadata = asOptionalReplyMessageRecord(sourceRecord.metadata);
|
|
2550
|
+
const rawMessageRecord = asOptionalReplyMessageRecord(sourceRecord.rawMessage);
|
|
2551
|
+
const rawPayloadRecord = asOptionalReplyMessageRecord(sourceRecord.rawPayload);
|
|
2552
|
+
const canonicalRecord = rawMessageRecord ?? sourceRecord;
|
|
2553
|
+
const content = parseReplyMessageContent(
|
|
2554
|
+
canonicalRecord.content ?? sourceRecord.content,
|
|
2555
|
+
isLikelyOpenzcaListenPayload(sourceRecord) && !rawMessageRecord
|
|
2556
|
+
);
|
|
2557
|
+
const msgType = requireStringLike(
|
|
2558
|
+
[canonicalRecord.msgType, sourceRecord.msgType, metadata?.msgType],
|
|
2559
|
+
"reply message msgType"
|
|
2560
|
+
);
|
|
2561
|
+
const uidFrom = requireStringLike(
|
|
2562
|
+
[
|
|
2563
|
+
canonicalRecord.uidFrom,
|
|
2564
|
+
sourceRecord.uidFrom,
|
|
2565
|
+
sourceRecord.senderId,
|
|
2566
|
+
sourceRecord.fromId,
|
|
2567
|
+
metadata?.senderId,
|
|
2568
|
+
metadata?.fromId
|
|
2569
|
+
],
|
|
2570
|
+
"reply message uidFrom"
|
|
2571
|
+
);
|
|
2572
|
+
const msgId = requireStringLike(
|
|
2573
|
+
[canonicalRecord.msgId, sourceRecord.msgId, rawPayloadRecord?.msgId],
|
|
2574
|
+
"reply message msgId"
|
|
2575
|
+
);
|
|
2576
|
+
const cliMsgId = requireStringLike(
|
|
2577
|
+
[canonicalRecord.cliMsgId, sourceRecord.cliMsgId, rawPayloadRecord?.cliMsgId],
|
|
2578
|
+
"reply message cliMsgId"
|
|
2579
|
+
);
|
|
2580
|
+
const ts = requireTsString(
|
|
2581
|
+
[canonicalRecord.ts, sourceRecord.ts, maybeTimestampSecondsToMsString(sourceRecord.timestamp)],
|
|
2582
|
+
"reply message ts"
|
|
2583
|
+
);
|
|
2584
|
+
const ttl = parseReplyMessageTtl(canonicalRecord.ttl ?? sourceRecord.ttl);
|
|
2585
|
+
const propertyExt = parseReplyMessagePropertyExt(canonicalRecord.propertyExt);
|
|
2586
|
+
return {
|
|
2587
|
+
quote: {
|
|
2588
|
+
content,
|
|
2589
|
+
msgType,
|
|
2590
|
+
propertyExt,
|
|
2591
|
+
uidFrom,
|
|
2592
|
+
msgId,
|
|
2593
|
+
cliMsgId,
|
|
2594
|
+
ts,
|
|
2595
|
+
ttl
|
|
2596
|
+
},
|
|
2597
|
+
inferredThreadId: inferReplyMessageThreadId({
|
|
2598
|
+
sourceRecord,
|
|
2599
|
+
canonicalRecord,
|
|
2600
|
+
metadata,
|
|
2601
|
+
threadType: params?.threadType,
|
|
2602
|
+
selfId: params?.selfId
|
|
2603
|
+
})
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
function prepareStoredReplyMessage(value, params) {
|
|
2607
|
+
const record = asReplyMessageRecord(value);
|
|
2608
|
+
const storedThreadType = record.threadType === "group" ? ThreadType2.Group : record.threadType === "user" ? ThreadType2.User : void 0;
|
|
2609
|
+
if (storedThreadType !== void 0 && storedThreadType !== params.threadType) {
|
|
2610
|
+
throw new Error("Reply source thread type does not match --group.");
|
|
2611
|
+
}
|
|
2612
|
+
const storedThreadId = firstString([record.threadId, record.rawThreadId]) ?? void 0;
|
|
2613
|
+
if (storedThreadId && storedThreadId !== params.threadId) {
|
|
2614
|
+
throw new Error("Reply source belongs to a different thread.");
|
|
2615
|
+
}
|
|
2616
|
+
const rawMessage = asOptionalReplyMessageRecord(record.rawMessage);
|
|
2617
|
+
const rawPayload = asOptionalReplyMessageRecord(record.rawPayload);
|
|
2618
|
+
const replyRecord = rawMessage ?? rawPayload;
|
|
2619
|
+
if (!replyRecord) {
|
|
2620
|
+
throw new Error(
|
|
2621
|
+
"Reply source found in DB but has no reusable raw message payload. Re-sync or capture it via listener first."
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
return prepareReplyMessage(replyRecord, {
|
|
2625
|
+
threadType: params.threadType,
|
|
2626
|
+
selfId: params.selfId
|
|
2627
|
+
}).quote;
|
|
2628
|
+
}
|
|
2629
|
+
function asReplyMessageRecord(value) {
|
|
2630
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2631
|
+
throw new Error("Reply message must be a JSON object matching the raw message.data shape.");
|
|
2632
|
+
}
|
|
2633
|
+
return value;
|
|
2634
|
+
}
|
|
2635
|
+
function asOptionalReplyMessageRecord(value) {
|
|
2636
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2637
|
+
return void 0;
|
|
2638
|
+
}
|
|
2639
|
+
return value;
|
|
2640
|
+
}
|
|
2641
|
+
function parseReplyMessageContent(value, stripOpenzcaDecorations) {
|
|
2642
|
+
if (typeof value === "string") {
|
|
2643
|
+
return stripOpenzcaDecorations ? stripEnrichedReplyDecorations(value) : value;
|
|
2644
|
+
}
|
|
2645
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2646
|
+
return value;
|
|
2647
|
+
}
|
|
2648
|
+
throw new Error("Reply message content must be a string or object.");
|
|
2649
|
+
}
|
|
2650
|
+
function stripEnrichedReplyDecorations(value) {
|
|
2651
|
+
const lines = value.split("\n");
|
|
2652
|
+
while (lines.length > 0) {
|
|
2653
|
+
const last = lines[lines.length - 1].trim();
|
|
2654
|
+
if (last.startsWith("[reply context: ") || last.startsWith("[reply media attached:") || last.startsWith("[reply media attached ")) {
|
|
2655
|
+
lines.pop();
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
break;
|
|
2659
|
+
}
|
|
2660
|
+
return lines.join("\n");
|
|
2661
|
+
}
|
|
2662
|
+
function parseReplyMessagePropertyExt(value) {
|
|
2663
|
+
if (value === void 0) {
|
|
2664
|
+
return void 0;
|
|
2665
|
+
}
|
|
2666
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2667
|
+
throw new Error("Reply message propertyExt must be an object when provided.");
|
|
2668
|
+
}
|
|
2669
|
+
return value;
|
|
2670
|
+
}
|
|
2671
|
+
function parseReplyMessageTtl(value) {
|
|
2672
|
+
if (value === void 0 || value === null || value === "") {
|
|
2673
|
+
return 0;
|
|
2674
|
+
}
|
|
2675
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
2676
|
+
if (!Number.isFinite(parsed)) {
|
|
2677
|
+
throw new Error("Reply message ttl must be a finite number.");
|
|
2678
|
+
}
|
|
2679
|
+
return Math.trunc(parsed);
|
|
2680
|
+
}
|
|
2681
|
+
function requireStringLike(values, label) {
|
|
2682
|
+
const value = firstString(values);
|
|
2683
|
+
if (!value) {
|
|
2684
|
+
throw new Error(`Missing ${label}.`);
|
|
2685
|
+
}
|
|
2686
|
+
return value;
|
|
2687
|
+
}
|
|
2688
|
+
function requireTsString(values, label) {
|
|
2689
|
+
for (const value of values) {
|
|
2690
|
+
if (typeof value === "string" && value.trim()) {
|
|
2691
|
+
return value.trim();
|
|
2692
|
+
}
|
|
2693
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2694
|
+
return String(Math.trunc(value));
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
throw new Error(`Missing ${label}.`);
|
|
2698
|
+
}
|
|
2699
|
+
function firstString(values) {
|
|
2700
|
+
for (const value of values) {
|
|
2701
|
+
if (typeof value === "string" && value.trim()) {
|
|
2702
|
+
return value.trim();
|
|
2703
|
+
}
|
|
2704
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2705
|
+
return String(Math.trunc(value));
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
return void 0;
|
|
2709
|
+
}
|
|
2710
|
+
function maybeTimestampSecondsToMsString(value) {
|
|
2711
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2712
|
+
return void 0;
|
|
2713
|
+
}
|
|
2714
|
+
return String(Math.trunc(value * 1e3));
|
|
2715
|
+
}
|
|
2716
|
+
function isLikelyOpenzcaListenPayload(record) {
|
|
2717
|
+
return typeof record.threadId === "string" && (typeof record.senderId === "string" || typeof record.chatType === "string" || typeof record.metadata === "object");
|
|
2718
|
+
}
|
|
2719
|
+
function inferReplyMessageThreadId(params) {
|
|
2720
|
+
const directThreadId = firstString([
|
|
2721
|
+
params.sourceRecord.threadId,
|
|
2722
|
+
params.sourceRecord.targetId,
|
|
2723
|
+
params.sourceRecord.conversationId,
|
|
2724
|
+
params.metadata?.threadId,
|
|
2725
|
+
params.metadata?.targetId
|
|
2726
|
+
]);
|
|
2727
|
+
if (directThreadId) {
|
|
2728
|
+
return directThreadId;
|
|
2729
|
+
}
|
|
2730
|
+
if (params.threadType === void 0) {
|
|
2731
|
+
return void 0;
|
|
2732
|
+
}
|
|
2733
|
+
const idTo = firstString([
|
|
2734
|
+
params.canonicalRecord.idTo,
|
|
2735
|
+
params.sourceRecord.idTo,
|
|
2736
|
+
params.sourceRecord.toId,
|
|
2737
|
+
params.metadata?.toId
|
|
2738
|
+
]);
|
|
2739
|
+
if (params.threadType === ThreadType2.Group) {
|
|
2740
|
+
return idTo;
|
|
2741
|
+
}
|
|
2742
|
+
const uidFrom = firstString([
|
|
2743
|
+
params.canonicalRecord.uidFrom,
|
|
2744
|
+
params.sourceRecord.uidFrom,
|
|
2745
|
+
params.sourceRecord.senderId,
|
|
2746
|
+
params.sourceRecord.fromId,
|
|
2747
|
+
params.metadata?.senderId,
|
|
2748
|
+
params.metadata?.fromId
|
|
2749
|
+
]);
|
|
2750
|
+
if (!uidFrom && !idTo) {
|
|
2751
|
+
return void 0;
|
|
2752
|
+
}
|
|
2753
|
+
if (params.selfId) {
|
|
2754
|
+
if (uidFrom && uidFrom !== params.selfId && uidFrom !== "0") {
|
|
2755
|
+
return uidFrom;
|
|
2756
|
+
}
|
|
2757
|
+
if (idTo && idTo !== params.selfId && idTo !== "0") {
|
|
2758
|
+
return idTo;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
if (uidFrom && uidFrom !== "0") {
|
|
2762
|
+
return uidFrom;
|
|
2763
|
+
}
|
|
2764
|
+
if (idTo && idTo !== "0") {
|
|
2765
|
+
return idTo;
|
|
2766
|
+
}
|
|
2767
|
+
return void 0;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2497
2770
|
// src/cli.ts
|
|
2498
|
-
var
|
|
2499
|
-
var { version: PKG_VERSION } =
|
|
2771
|
+
var require3 = createRequire2(import.meta.url);
|
|
2772
|
+
var { version: PKG_VERSION } = require3("../package.json");
|
|
2500
2773
|
var program = new Command();
|
|
2501
2774
|
var EMOJI_REACTION_MAP = {
|
|
2502
2775
|
"\u2764\uFE0F": Reactions.HEART,
|
|
@@ -2656,7 +2929,7 @@ function normalizeCommandAliases(argv) {
|
|
|
2656
2929
|
return normalized;
|
|
2657
2930
|
}
|
|
2658
2931
|
function asThreadType(groupFlag) {
|
|
2659
|
-
return groupFlag ?
|
|
2932
|
+
return groupFlag ? ThreadType3.Group : ThreadType3.User;
|
|
2660
2933
|
}
|
|
2661
2934
|
function parseBooleanFromEnv(name, fallback) {
|
|
2662
2935
|
const raw = process.env[name]?.trim();
|
|
@@ -2866,7 +3139,7 @@ async function startListenerIpcServer(api, profile, sessionId, command) {
|
|
|
2866
3139
|
fail(parsed.requestId, "Invalid upload payload.");
|
|
2867
3140
|
return;
|
|
2868
3141
|
}
|
|
2869
|
-
const threadType = parsed.threadType === "group" ?
|
|
3142
|
+
const threadType = parsed.threadType === "group" ? ThreadType3.Group : ThreadType3.User;
|
|
2870
3143
|
const requestTimeoutMs = parsePositiveIntFromUnknown(parsed.uploadTimeoutMs) ?? uploadTimeoutMs;
|
|
2871
3144
|
writeDebugLine(
|
|
2872
3145
|
"listen.ipc.upload.start",
|
|
@@ -3023,7 +3296,7 @@ async function tryUploadViaListenerIpc(profile, threadId, threadType, attachment
|
|
|
3023
3296
|
{
|
|
3024
3297
|
profile,
|
|
3025
3298
|
threadId,
|
|
3026
|
-
threadType: threadType ===
|
|
3299
|
+
threadType: threadType === ThreadType3.Group ? "group" : "user",
|
|
3027
3300
|
attachmentCount: attachments.length,
|
|
3028
3301
|
socketPath,
|
|
3029
3302
|
requestId,
|
|
@@ -3067,7 +3340,7 @@ async function tryUploadViaListenerIpc(profile, threadId, threadType, attachment
|
|
|
3067
3340
|
requestId,
|
|
3068
3341
|
profile,
|
|
3069
3342
|
threadId,
|
|
3070
|
-
threadType: threadType ===
|
|
3343
|
+
threadType: threadType === ThreadType3.Group ? "group" : "user",
|
|
3071
3344
|
attachments
|
|
3072
3345
|
};
|
|
3073
3346
|
socket.write(`${JSON.stringify(payload)}
|
|
@@ -3129,21 +3402,21 @@ async function tryUploadViaListenerIpc(profile, threadId, threadType, attachment
|
|
|
3129
3402
|
}
|
|
3130
3403
|
async function resolveUploadThreadType(api, profile, threadId, groupFlag, command) {
|
|
3131
3404
|
if (groupFlag) {
|
|
3132
|
-
return { type:
|
|
3405
|
+
return { type: ThreadType3.Group, reason: "explicit_group_flag" };
|
|
3133
3406
|
}
|
|
3134
3407
|
const autoDetectEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_AUTO_THREAD_TYPE", false);
|
|
3135
3408
|
if (!autoDetectEnabled) {
|
|
3136
|
-
return { type:
|
|
3409
|
+
return { type: ThreadType3.User, reason: "auto_detect_disabled" };
|
|
3137
3410
|
}
|
|
3138
3411
|
try {
|
|
3139
3412
|
const cache = await readCache(profile);
|
|
3140
3413
|
const groupIds = collectIdsFromCacheEntries(cache.groups, ["groupId", "grid", "threadId", "id"]);
|
|
3141
3414
|
if (groupIds.has(threadId)) {
|
|
3142
|
-
return { type:
|
|
3415
|
+
return { type: ThreadType3.Group, reason: "cache_group_match" };
|
|
3143
3416
|
}
|
|
3144
3417
|
const friendIds = collectIdsFromCacheEntries(cache.friends, ["userId", "uid", "id", "threadId"]);
|
|
3145
3418
|
if (friendIds.has(threadId)) {
|
|
3146
|
-
return { type:
|
|
3419
|
+
return { type: ThreadType3.User, reason: "cache_friend_match" };
|
|
3147
3420
|
}
|
|
3148
3421
|
} catch (error) {
|
|
3149
3422
|
writeDebugLine(
|
|
@@ -3158,7 +3431,7 @@ async function resolveUploadThreadType(api, profile, threadId, groupFlag, comman
|
|
|
3158
3431
|
}
|
|
3159
3432
|
const probeEnabled = parseBooleanFromEnv("OPENZCA_UPLOAD_GROUP_PROBE", true);
|
|
3160
3433
|
if (!probeEnabled) {
|
|
3161
|
-
return { type:
|
|
3434
|
+
return { type: ThreadType3.User, reason: "probe_disabled" };
|
|
3162
3435
|
}
|
|
3163
3436
|
const probeTimeoutMs = parsePositiveIntFromEnv("OPENZCA_UPLOAD_GROUP_PROBE_TIMEOUT_MS", 5e3);
|
|
3164
3437
|
try {
|
|
@@ -3168,7 +3441,7 @@ async function resolveUploadThreadType(api, profile, threadId, groupFlag, comman
|
|
|
3168
3441
|
`Timed out waiting ${probeTimeoutMs}ms while probing group thread type.`
|
|
3169
3442
|
);
|
|
3170
3443
|
if (groupInfo?.gridInfoMap?.[threadId]) {
|
|
3171
|
-
return { type:
|
|
3444
|
+
return { type: ThreadType3.Group, reason: "probe_group_match" };
|
|
3172
3445
|
}
|
|
3173
3446
|
} catch (error) {
|
|
3174
3447
|
writeDebugLine(
|
|
@@ -3181,7 +3454,7 @@ async function resolveUploadThreadType(api, profile, threadId, groupFlag, comman
|
|
|
3181
3454
|
command
|
|
3182
3455
|
);
|
|
3183
3456
|
}
|
|
3184
|
-
return { type:
|
|
3457
|
+
return { type: ThreadType3.User, reason: "default_user" };
|
|
3185
3458
|
}
|
|
3186
3459
|
function parseReaction(input) {
|
|
3187
3460
|
const normalized = input.trim();
|
|
@@ -3279,6 +3552,62 @@ async function shouldWriteToDb(profile, override) {
|
|
|
3279
3552
|
}
|
|
3280
3553
|
return isDbEnabled(profile);
|
|
3281
3554
|
}
|
|
3555
|
+
async function resolveSendReplyQuote(params) {
|
|
3556
|
+
const replyId = params.replyId?.trim();
|
|
3557
|
+
const replyMessage = params.replyMessage?.trim();
|
|
3558
|
+
if (replyId && replyMessage) {
|
|
3559
|
+
throw new Error("Use either --reply-id or --reply-message, not both.");
|
|
3560
|
+
}
|
|
3561
|
+
if (!replyId && !replyMessage) {
|
|
3562
|
+
return void 0;
|
|
3563
|
+
}
|
|
3564
|
+
if (replyId) {
|
|
3565
|
+
if (!await shouldWriteToDb(params.profile)) {
|
|
3566
|
+
throw new Error("`--reply-id` requires the local DB. Enable DB/listen sync first.");
|
|
3567
|
+
}
|
|
3568
|
+
const row = await getMessageById({
|
|
3569
|
+
profile: params.profile,
|
|
3570
|
+
id: replyId
|
|
3571
|
+
});
|
|
3572
|
+
if (!row) {
|
|
3573
|
+
throw new Error(`Reply source not found in DB: ${replyId}`);
|
|
3574
|
+
}
|
|
3575
|
+
if (row.threadType === "group" !== (params.threadType === ThreadType3.Group)) {
|
|
3576
|
+
throw new Error("Reply source thread type does not match --group.");
|
|
3577
|
+
}
|
|
3578
|
+
if (row.threadId !== params.threadId) {
|
|
3579
|
+
throw new Error("Reply source belongs to a different thread.");
|
|
3580
|
+
}
|
|
3581
|
+
if (!row.rawMessage || typeof row.rawMessage !== "object") {
|
|
3582
|
+
if (!row.rawPayload || typeof row.rawPayload !== "object") {
|
|
3583
|
+
throw new Error(
|
|
3584
|
+
"Reply source found in DB but has no reusable raw message payload. Re-sync or capture it via listener first."
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
return prepareStoredReplyMessage(row, {
|
|
3589
|
+
threadId: params.threadId,
|
|
3590
|
+
threadType: params.threadType,
|
|
3591
|
+
selfId: params.api.getOwnId()
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
let parsedReplyMessage;
|
|
3595
|
+
try {
|
|
3596
|
+
parsedReplyMessage = JSON.parse(replyMessage);
|
|
3597
|
+
} catch (error) {
|
|
3598
|
+
throw new Error(
|
|
3599
|
+
`Invalid JSON for --reply-message: ${error instanceof Error ? error.message : String(error)}`
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3602
|
+
const preparedReply = prepareReplyMessage(parsedReplyMessage, {
|
|
3603
|
+
threadType: params.threadType,
|
|
3604
|
+
selfId: params.api.getOwnId()
|
|
3605
|
+
});
|
|
3606
|
+
if (preparedReply.inferredThreadId && preparedReply.inferredThreadId !== params.threadId) {
|
|
3607
|
+
throw new Error("Reply message belongs to a different thread.");
|
|
3608
|
+
}
|
|
3609
|
+
return preparedReply.quote;
|
|
3610
|
+
}
|
|
3282
3611
|
function scheduleDbWrite(profile, command, event, task) {
|
|
3283
3612
|
enqueueDbWrite(profile, async () => {
|
|
3284
3613
|
try {
|
|
@@ -4259,7 +4588,7 @@ function normalizeGroupHistoryMessages(messages, fallbackThreadId) {
|
|
|
4259
4588
|
const threadIdRaw = String(raw.idTo ?? "").trim();
|
|
4260
4589
|
normalized.push({
|
|
4261
4590
|
threadId: threadIdRaw || fallbackThreadId,
|
|
4262
|
-
type:
|
|
4591
|
+
type: ThreadType3.Group,
|
|
4263
4592
|
data: {
|
|
4264
4593
|
actionId: typeof raw.actionId === "string" && raw.actionId.trim() ? raw.actionId : void 0,
|
|
4265
4594
|
msgId: String(raw.msgId ?? ""),
|
|
@@ -4364,7 +4693,7 @@ async function crawlGroupHistoryViaListener(api, options) {
|
|
|
4364
4693
|
requestedCursors.add(cursor);
|
|
4365
4694
|
}
|
|
4366
4695
|
pagesRequested += 1;
|
|
4367
|
-
api.listener.requestOldMessages(
|
|
4696
|
+
api.listener.requestOldMessages(ThreadType3.Group, cursor || null);
|
|
4368
4697
|
return true;
|
|
4369
4698
|
};
|
|
4370
4699
|
const armIdleTimer = () => {
|
|
@@ -4419,7 +4748,7 @@ async function crawlGroupHistoryViaListener(api, options) {
|
|
|
4419
4748
|
}
|
|
4420
4749
|
};
|
|
4421
4750
|
const onOldMessages = (messages, type) => {
|
|
4422
|
-
if (type !==
|
|
4751
|
+
if (type !== ThreadType3.Group) return;
|
|
4423
4752
|
armIdleTimer();
|
|
4424
4753
|
const typedMessages = messages;
|
|
4425
4754
|
processing = processing.then(async () => {
|
|
@@ -4508,7 +4837,7 @@ async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
|
4508
4837
|
requestedCursors.add(cursor);
|
|
4509
4838
|
}
|
|
4510
4839
|
pagesRequested += 1;
|
|
4511
|
-
api.listener.requestOldMessages(
|
|
4840
|
+
api.listener.requestOldMessages(ThreadType3.User, cursor || null);
|
|
4512
4841
|
return true;
|
|
4513
4842
|
};
|
|
4514
4843
|
const cleanup = () => {
|
|
@@ -4540,7 +4869,7 @@ async function fetchRecentUserMessagesViaListener(api, threadId, count) {
|
|
|
4540
4869
|
}
|
|
4541
4870
|
};
|
|
4542
4871
|
const onOldMessages = (messages, type) => {
|
|
4543
|
-
if (type !==
|
|
4872
|
+
if (type !== ThreadType3.User) return;
|
|
4544
4873
|
const typedMessages = messages;
|
|
4545
4874
|
for (const message of typedMessages) {
|
|
4546
4875
|
if (message.threadId === threadId) {
|
|
@@ -4616,7 +4945,7 @@ async function fetchRecentUserMessagesAcrossThreads(api, maxMessages) {
|
|
|
4616
4945
|
requestedCursors.add(cursor);
|
|
4617
4946
|
}
|
|
4618
4947
|
pagesRequested += 1;
|
|
4619
|
-
api.listener.requestOldMessages(
|
|
4948
|
+
api.listener.requestOldMessages(ThreadType3.User, cursor || null);
|
|
4620
4949
|
return true;
|
|
4621
4950
|
};
|
|
4622
4951
|
const cleanup = () => {
|
|
@@ -4648,7 +4977,7 @@ async function fetchRecentUserMessagesAcrossThreads(api, maxMessages) {
|
|
|
4648
4977
|
}
|
|
4649
4978
|
};
|
|
4650
4979
|
const onOldMessages = (messages, type) => {
|
|
4651
|
-
if (type !==
|
|
4980
|
+
if (type !== ThreadType3.User) return;
|
|
4652
4981
|
const typedMessages = messages;
|
|
4653
4982
|
for (const message of typedMessages) {
|
|
4654
4983
|
const key = toKey(message);
|
|
@@ -4731,7 +5060,7 @@ function toDbRecordFromRecentMessage(params) {
|
|
|
4731
5060
|
const quote = params.message.data?.quote;
|
|
4732
5061
|
return normalizeInboundListenRecord({
|
|
4733
5062
|
profile: params.profile,
|
|
4734
|
-
threadType: params.message.type ===
|
|
5063
|
+
threadType: params.message.type === ThreadType3.Group ? "group" : "user",
|
|
4735
5064
|
rawThreadId: params.message.threadId,
|
|
4736
5065
|
senderId: params.message.data?.uidFrom,
|
|
4737
5066
|
senderName: params.message.data?.dName,
|
|
@@ -6083,17 +6412,29 @@ dbSync.command("chat <chatId>").option("-n, --count <count>", "Recent messages t
|
|
|
6083
6412
|
})
|
|
6084
6413
|
);
|
|
6085
6414
|
var msg = program.command("msg").description("Messaging commands");
|
|
6086
|
-
msg.command("send <threadId> <message>").option("-g, --group", "Send to group").option("--raw", "Send raw text without parsing formatting markers").description("Send text message with formatting (**bold** *italic* __bold__ ~~strike~~ {underline}text{/underline} {red}color{/red} {big}size{/big} lists indents). Group sends also resolve unique @Name/@userId mentions.").action(
|
|
6415
|
+
msg.command("send <threadId> <message>").option("-g, --group", "Send to group").option("--raw", "Send raw text without parsing formatting markers").option("--reply-id <id>", "Reply using a stored DB message id/msgId/cliMsgId").option("--reply-message <json>", "Reply using a raw message.data JSON object").description("Send text message with formatting (**bold** *italic* __bold__ ~~strike~~ {underline}text{/underline} {red}color{/red} {big}size{/big} lists indents). Group sends also resolve unique @Name/@userId mentions.").action(
|
|
6087
6416
|
wrapAction(async (threadId, message, opts, command) => {
|
|
6088
6417
|
const { api, profile } = await requireApi(command);
|
|
6089
6418
|
const threadType = asThreadType(opts.group);
|
|
6090
|
-
const
|
|
6419
|
+
const textPayload = await buildTextSendPayload({
|
|
6091
6420
|
message,
|
|
6092
6421
|
raw: opts.raw,
|
|
6093
6422
|
threadType,
|
|
6094
6423
|
threadId,
|
|
6095
|
-
listGroupMembers: threadType ===
|
|
6424
|
+
listGroupMembers: threadType === ThreadType3.Group ? (groupId) => listGroupMentionMembers(api, groupId) : void 0
|
|
6425
|
+
});
|
|
6426
|
+
const quote = await resolveSendReplyQuote({
|
|
6427
|
+
profile,
|
|
6428
|
+
api,
|
|
6429
|
+
threadId,
|
|
6430
|
+
threadType,
|
|
6431
|
+
replyId: opts.replyId,
|
|
6432
|
+
replyMessage: opts.replyMessage
|
|
6096
6433
|
});
|
|
6434
|
+
const payload = quote || typeof textPayload !== "string" ? {
|
|
6435
|
+
...typeof textPayload === "string" ? { msg: textPayload } : textPayload,
|
|
6436
|
+
...quote ? { quote } : {}
|
|
6437
|
+
} : textPayload;
|
|
6097
6438
|
const response = await api.sendMessage(payload, threadId, threadType);
|
|
6098
6439
|
output(response, false);
|
|
6099
6440
|
if (await shouldWriteToDb(profile)) {
|
|
@@ -6582,8 +6923,8 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
6582
6923
|
{
|
|
6583
6924
|
threadId,
|
|
6584
6925
|
explicitGroupFlag: Boolean(opts.group),
|
|
6585
|
-
isGroup: threadResolution.type ===
|
|
6586
|
-
threadType: threadResolution.type ===
|
|
6926
|
+
isGroup: threadResolution.type === ThreadType3.Group,
|
|
6927
|
+
threadType: threadResolution.type === ThreadType3.Group ? "group" : "user",
|
|
6587
6928
|
threadTypeReason: threadResolution.reason,
|
|
6588
6929
|
localFiles,
|
|
6589
6930
|
urlInputs
|
|
@@ -6611,7 +6952,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
6611
6952
|
"msg.upload.ipc.done",
|
|
6612
6953
|
{
|
|
6613
6954
|
threadId,
|
|
6614
|
-
threadType: threadResolution.type ===
|
|
6955
|
+
threadType: threadResolution.type === ThreadType3.Group ? "group" : "user"
|
|
6615
6956
|
},
|
|
6616
6957
|
command
|
|
6617
6958
|
);
|
|
@@ -6622,7 +6963,7 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
|
|
|
6622
6963
|
"msg.upload.ipc.fallback",
|
|
6623
6964
|
{
|
|
6624
6965
|
threadId,
|
|
6625
|
-
threadType: threadResolution.type ===
|
|
6966
|
+
threadType: threadResolution.type === ThreadType3.Group ? "group" : "user",
|
|
6626
6967
|
reason: ipcResult.reason
|
|
6627
6968
|
},
|
|
6628
6969
|
command
|
|
@@ -6661,7 +7002,7 @@ msg.command("recent <threadId>").option("-g, --group", "List recent messages for
|
|
|
6661
7002
|
const { api, profile } = await requireApi(command);
|
|
6662
7003
|
const parsedCount = Number(opts.count);
|
|
6663
7004
|
const count = Number.isFinite(parsedCount) ? Math.min(Math.max(Math.trunc(parsedCount), 1), 200) : 20;
|
|
6664
|
-
const threadType = opts.group ?
|
|
7005
|
+
const threadType = opts.group ? ThreadType3.Group : ThreadType3.User;
|
|
6665
7006
|
const source = (opts.source ?? "live").trim().toLowerCase();
|
|
6666
7007
|
if (!["live", "db", "auto"].includes(source)) {
|
|
6667
7008
|
throw new Error("--source must be one of: live, db, auto");
|
|
@@ -6682,7 +7023,7 @@ msg.command("recent <threadId>").option("-g, --group", "List recent messages for
|
|
|
6682
7023
|
msgId: message.data.msgId,
|
|
6683
7024
|
cliMsgId: message.data.cliMsgId,
|
|
6684
7025
|
threadId: message.threadId || threadId,
|
|
6685
|
-
threadType: message.type ===
|
|
7026
|
+
threadType: message.type === ThreadType3.Group ? "group" : "user",
|
|
6686
7027
|
senderId: message.data.uidFrom,
|
|
6687
7028
|
senderName: message.data.dName ?? "",
|
|
6688
7029
|
ts: message.data.ts,
|
|
@@ -6691,7 +7032,7 @@ msg.command("recent <threadId>").option("-g, --group", "List recent messages for
|
|
|
6691
7032
|
msgId: message.data.msgId,
|
|
6692
7033
|
cliMsgId: message.data.cliMsgId,
|
|
6693
7034
|
threadId: message.threadId || threadId,
|
|
6694
|
-
group: message.type ===
|
|
7035
|
+
group: message.type === ThreadType3.Group
|
|
6695
7036
|
},
|
|
6696
7037
|
content: typeof message.data.content === "string" ? message.data.content : JSON.stringify(message.data.content)
|
|
6697
7038
|
}));
|
|
@@ -6700,7 +7041,7 @@ msg.command("recent <threadId>").option("-g, --group", "List recent messages for
|
|
|
6700
7041
|
output(
|
|
6701
7042
|
{
|
|
6702
7043
|
threadId,
|
|
6703
|
-
threadType: threadType ===
|
|
7044
|
+
threadType: threadType === ThreadType3.Group ? "group" : "user",
|
|
6704
7045
|
count: rows.length,
|
|
6705
7046
|
messages: rows
|
|
6706
7047
|
},
|
|
@@ -6720,7 +7061,7 @@ msg.command("pin <threadId>").option("-g, --group", "Pin group conversation").de
|
|
|
6720
7061
|
output(
|
|
6721
7062
|
{
|
|
6722
7063
|
threadId,
|
|
6723
|
-
threadType: type ===
|
|
7064
|
+
threadType: type === ThreadType3.Group ? "group" : "user",
|
|
6724
7065
|
pinned: true,
|
|
6725
7066
|
response
|
|
6726
7067
|
},
|
|
@@ -6736,7 +7077,7 @@ msg.command("unpin <threadId>").option("-g, --group", "Unpin group conversation"
|
|
|
6736
7077
|
output(
|
|
6737
7078
|
{
|
|
6738
7079
|
threadId,
|
|
6739
|
-
threadType: type ===
|
|
7080
|
+
threadType: type === ThreadType3.Group ? "group" : "user",
|
|
6740
7081
|
pinned: false,
|
|
6741
7082
|
response
|
|
6742
7083
|
},
|
|
@@ -7576,7 +7917,7 @@ ${replyMediaText}` : replyMediaText;
|
|
|
7576
7917
|
processedText = processedText.trim() ? `${processedText}
|
|
7577
7918
|
${replyContextText}` : replyContextText;
|
|
7578
7919
|
}
|
|
7579
|
-
const chatType = message.type ===
|
|
7920
|
+
const chatType = message.type === ThreadType3.Group ? "group" : "user";
|
|
7580
7921
|
const senderId = getStringCandidate(messageData, ["uidFrom"]) || message.data.uidFrom;
|
|
7581
7922
|
const senderDisplayNameRaw = getStringCandidate(messageData, [
|
|
7582
7923
|
"dName",
|
|
@@ -7585,7 +7926,7 @@ ${replyContextText}` : replyContextText;
|
|
|
7585
7926
|
"displayName"
|
|
7586
7927
|
]);
|
|
7587
7928
|
const senderDisplayName = senderDisplayNameRaw || void 0;
|
|
7588
|
-
const senderNameForMetadata = message.type ===
|
|
7929
|
+
const senderNameForMetadata = message.type === ThreadType3.Group ? senderDisplayName : void 0;
|
|
7589
7930
|
const toId = getStringCandidate(messageData, ["idTo"]) || void 0;
|
|
7590
7931
|
const threadName = typeof messageData.threadName === "string" ? messageData.threadName : typeof messageData.tName === "string" ? messageData.tName : void 0;
|
|
7591
7932
|
const mentions = extractInboundMentions({
|
|
@@ -7623,7 +7964,7 @@ ${replyContextText}` : replyContextText;
|
|
|
7623
7964
|
mentions: mentions.length > 0 ? mentions : void 0,
|
|
7624
7965
|
mentionIds: mentionIds.length > 0 ? mentionIds : void 0,
|
|
7625
7966
|
metadata: {
|
|
7626
|
-
isGroup: message.type ===
|
|
7967
|
+
isGroup: message.type === ThreadType3.Group,
|
|
7627
7968
|
chatType,
|
|
7628
7969
|
threadId: message.threadId,
|
|
7629
7970
|
targetId: message.threadId,
|