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
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// src/lib/db-worker.ts
|
|
2
|
+
import { parentPort, workerData } from "worker_threads";
|
|
3
|
+
var INIT_SQL = `
|
|
4
|
+
PRAGMA journal_mode = WAL;
|
|
5
|
+
PRAGMA synchronous = FULL;
|
|
6
|
+
PRAGMA busy_timeout = 5000;
|
|
7
|
+
PRAGMA foreign_keys = ON;
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
10
|
+
profile TEXT NOT NULL,
|
|
11
|
+
scope_thread_id TEXT NOT NULL,
|
|
12
|
+
raw_thread_id TEXT NOT NULL,
|
|
13
|
+
thread_type TEXT NOT NULL,
|
|
14
|
+
peer_id TEXT,
|
|
15
|
+
title TEXT,
|
|
16
|
+
is_pinned INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
is_hidden INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
raw_json TEXT,
|
|
20
|
+
created_at TEXT NOT NULL,
|
|
21
|
+
updated_at TEXT NOT NULL,
|
|
22
|
+
PRIMARY KEY (profile, scope_thread_id)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS thread_members (
|
|
26
|
+
profile TEXT NOT NULL,
|
|
27
|
+
scope_thread_id TEXT NOT NULL,
|
|
28
|
+
user_id TEXT NOT NULL,
|
|
29
|
+
display_name TEXT,
|
|
30
|
+
zalo_name TEXT,
|
|
31
|
+
avatar TEXT,
|
|
32
|
+
account_status INTEGER,
|
|
33
|
+
member_type INTEGER,
|
|
34
|
+
raw_json TEXT,
|
|
35
|
+
snapshot_at_ms INTEGER NOT NULL,
|
|
36
|
+
created_at TEXT NOT NULL,
|
|
37
|
+
updated_at TEXT NOT NULL,
|
|
38
|
+
PRIMARY KEY (profile, scope_thread_id, user_id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS friends (
|
|
42
|
+
profile TEXT NOT NULL,
|
|
43
|
+
user_id TEXT NOT NULL,
|
|
44
|
+
display_name TEXT,
|
|
45
|
+
zalo_name TEXT,
|
|
46
|
+
avatar TEXT,
|
|
47
|
+
account_status INTEGER,
|
|
48
|
+
raw_json TEXT,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL,
|
|
51
|
+
PRIMARY KEY (profile, user_id)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS self_profiles (
|
|
55
|
+
profile TEXT NOT NULL,
|
|
56
|
+
user_id TEXT NOT NULL,
|
|
57
|
+
display_name TEXT,
|
|
58
|
+
info_json TEXT,
|
|
59
|
+
created_at TEXT NOT NULL,
|
|
60
|
+
updated_at TEXT NOT NULL,
|
|
61
|
+
PRIMARY KEY (profile)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
65
|
+
profile TEXT NOT NULL,
|
|
66
|
+
message_uid TEXT NOT NULL,
|
|
67
|
+
scope_thread_id TEXT NOT NULL,
|
|
68
|
+
raw_thread_id TEXT NOT NULL,
|
|
69
|
+
thread_type TEXT NOT NULL,
|
|
70
|
+
msg_id TEXT,
|
|
71
|
+
cli_msg_id TEXT,
|
|
72
|
+
action_id TEXT,
|
|
73
|
+
sender_id TEXT,
|
|
74
|
+
sender_name TEXT,
|
|
75
|
+
to_id TEXT,
|
|
76
|
+
timestamp_ms INTEGER NOT NULL,
|
|
77
|
+
msg_type TEXT,
|
|
78
|
+
content_text TEXT,
|
|
79
|
+
content_json TEXT,
|
|
80
|
+
quote_msg_id TEXT,
|
|
81
|
+
quote_cli_msg_id TEXT,
|
|
82
|
+
quote_owner_id TEXT,
|
|
83
|
+
quote_text TEXT,
|
|
84
|
+
source TEXT NOT NULL,
|
|
85
|
+
raw_message_json TEXT,
|
|
86
|
+
raw_payload_json TEXT,
|
|
87
|
+
created_at TEXT NOT NULL,
|
|
88
|
+
updated_at TEXT NOT NULL,
|
|
89
|
+
PRIMARY KEY (profile, message_uid)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS message_media (
|
|
93
|
+
profile TEXT NOT NULL,
|
|
94
|
+
message_uid TEXT NOT NULL,
|
|
95
|
+
item_index INTEGER NOT NULL,
|
|
96
|
+
media_kind TEXT,
|
|
97
|
+
media_url TEXT,
|
|
98
|
+
media_path TEXT,
|
|
99
|
+
media_type TEXT,
|
|
100
|
+
raw_json TEXT,
|
|
101
|
+
created_at TEXT NOT NULL,
|
|
102
|
+
updated_at TEXT NOT NULL,
|
|
103
|
+
PRIMARY KEY (profile, message_uid, item_index)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS message_mentions (
|
|
107
|
+
profile TEXT NOT NULL,
|
|
108
|
+
message_uid TEXT NOT NULL,
|
|
109
|
+
item_index INTEGER NOT NULL,
|
|
110
|
+
target_user_id TEXT NOT NULL,
|
|
111
|
+
pos INTEGER,
|
|
112
|
+
len INTEGER,
|
|
113
|
+
mention_type INTEGER,
|
|
114
|
+
raw_json TEXT,
|
|
115
|
+
created_at TEXT NOT NULL,
|
|
116
|
+
updated_at TEXT NOT NULL,
|
|
117
|
+
PRIMARY KEY (profile, message_uid, item_index)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
121
|
+
profile TEXT NOT NULL,
|
|
122
|
+
scope TEXT NOT NULL,
|
|
123
|
+
scope_thread_id TEXT NOT NULL,
|
|
124
|
+
thread_type TEXT NOT NULL,
|
|
125
|
+
status TEXT NOT NULL,
|
|
126
|
+
completeness TEXT,
|
|
127
|
+
cursor TEXT,
|
|
128
|
+
last_sync_at TEXT,
|
|
129
|
+
error TEXT,
|
|
130
|
+
created_at TEXT NOT NULL,
|
|
131
|
+
updated_at TEXT NOT NULL,
|
|
132
|
+
PRIMARY KEY (profile, scope)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_messages_thread_time
|
|
136
|
+
ON messages (profile, scope_thread_id, timestamp_ms DESC);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_messages_msg_id
|
|
138
|
+
ON messages (profile, msg_id);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_messages_cli_msg_id
|
|
140
|
+
ON messages (profile, cli_msg_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_threads_type
|
|
142
|
+
ON threads (profile, thread_type, updated_at DESC);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_members_thread
|
|
144
|
+
ON thread_members (profile, scope_thread_id);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_friends_name
|
|
146
|
+
ON friends (profile, display_name, zalo_name, user_id);
|
|
147
|
+
`;
|
|
148
|
+
if (!parentPort) {
|
|
149
|
+
throw new Error("DB worker requires parentPort");
|
|
150
|
+
}
|
|
151
|
+
var port = parentPort;
|
|
152
|
+
function serializeError(error) {
|
|
153
|
+
if (error instanceof Error) {
|
|
154
|
+
return {
|
|
155
|
+
name: error.name,
|
|
156
|
+
message: error.message,
|
|
157
|
+
stack: error.stack,
|
|
158
|
+
code: typeof error.code === "string" ? error.code : void 0
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
name: "Error",
|
|
163
|
+
message: String(error)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function loadSqliteModule() {
|
|
167
|
+
const originalEmitWarning = process.emitWarning;
|
|
168
|
+
const importDynamic = new Function("specifier", "return import(specifier);");
|
|
169
|
+
process.emitWarning = ((warning, options, ...args) => {
|
|
170
|
+
const type = typeof options === "string" ? options : void 0;
|
|
171
|
+
const message = warning instanceof Error ? warning.message : String(warning);
|
|
172
|
+
if (type === "ExperimentalWarning" && message.includes("SQLite")) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
Reflect.apply(originalEmitWarning, process, [warning, options, ...args]);
|
|
176
|
+
});
|
|
177
|
+
try {
|
|
178
|
+
return await importDynamic("node:sqlite");
|
|
179
|
+
} finally {
|
|
180
|
+
process.emitWarning = originalEmitWarning;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function setDefensiveMode(db) {
|
|
184
|
+
const maybeDb = db;
|
|
185
|
+
if (typeof maybeDb.enableDefensive === "function") {
|
|
186
|
+
maybeDb.enableDefensive(true);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function runStatement(db, statement) {
|
|
190
|
+
return db.prepare(statement.sql).run(...statement.params ?? []);
|
|
191
|
+
}
|
|
192
|
+
function getStatement(db, statement) {
|
|
193
|
+
return db.prepare(statement.sql).get(...statement.params ?? []);
|
|
194
|
+
}
|
|
195
|
+
function allStatement(db, statement) {
|
|
196
|
+
return db.prepare(statement.sql).all(...statement.params ?? []);
|
|
197
|
+
}
|
|
198
|
+
async function main() {
|
|
199
|
+
const { DatabaseSync } = await loadSqliteModule();
|
|
200
|
+
const { filename } = workerData;
|
|
201
|
+
const db = new DatabaseSync(filename);
|
|
202
|
+
db.exec(INIT_SQL);
|
|
203
|
+
setDefensiveMode(db);
|
|
204
|
+
port.postMessage({ type: "ready" });
|
|
205
|
+
port.on("message", (message) => {
|
|
206
|
+
try {
|
|
207
|
+
switch (message.type) {
|
|
208
|
+
case "exec":
|
|
209
|
+
db.exec(message.payload.sql);
|
|
210
|
+
port.postMessage({
|
|
211
|
+
type: "result",
|
|
212
|
+
id: message.id,
|
|
213
|
+
result: null
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
case "run":
|
|
217
|
+
port.postMessage({
|
|
218
|
+
type: "result",
|
|
219
|
+
id: message.id,
|
|
220
|
+
result: runStatement(db, message.payload)
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
case "get":
|
|
224
|
+
port.postMessage({
|
|
225
|
+
type: "result",
|
|
226
|
+
id: message.id,
|
|
227
|
+
result: getStatement(db, message.payload) ?? null
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
case "all":
|
|
231
|
+
port.postMessage({
|
|
232
|
+
type: "result",
|
|
233
|
+
id: message.id,
|
|
234
|
+
result: allStatement(db, message.payload)
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
case "batch":
|
|
238
|
+
if (message.payload.transactional) {
|
|
239
|
+
db.exec("BEGIN");
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
for (const command of message.payload.commands) {
|
|
243
|
+
if ((command.params ?? []).length === 0) {
|
|
244
|
+
db.exec(command.sql);
|
|
245
|
+
} else {
|
|
246
|
+
runStatement(db, command);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (message.payload.transactional) {
|
|
250
|
+
db.exec("COMMIT");
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (message.payload.transactional) {
|
|
254
|
+
try {
|
|
255
|
+
db.exec("ROLLBACK");
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
port.postMessage({
|
|
262
|
+
type: "result",
|
|
263
|
+
id: message.id,
|
|
264
|
+
result: null
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
case "close":
|
|
268
|
+
db.close();
|
|
269
|
+
port.postMessage({
|
|
270
|
+
type: "result",
|
|
271
|
+
id: message.id,
|
|
272
|
+
result: null
|
|
273
|
+
});
|
|
274
|
+
setImmediate(() => process.exit(0));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
port.postMessage({
|
|
279
|
+
type: "error",
|
|
280
|
+
id: message.id,
|
|
281
|
+
error: serializeError(error)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
void main().catch((error) => {
|
|
287
|
+
port.postMessage({
|
|
288
|
+
type: "fatal",
|
|
289
|
+
error: serializeError(error)
|
|
290
|
+
});
|
|
291
|
+
process.exit(1);
|
|
292
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzca",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "Open-source zca-compatible CLI to integrate Zalo with OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "
|
|
17
|
+
"build": "npm run build:cli && npm run build:worker",
|
|
18
|
+
"build:cli": "tsup src/cli.ts --format esm --target node22 --out-dir dist --clean",
|
|
19
|
+
"build:worker": "tsup src/lib/db-worker.ts --format esm --target node22 --out-dir dist",
|
|
18
20
|
"dev": "tsx src/cli.ts",
|
|
19
21
|
"test": "tsx --test tests/*.test.ts",
|
|
20
22
|
"typecheck": "tsc -p tsconfig.json",
|
|
21
23
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"prepare": "npm run build",
|
|
22
25
|
"prepublishOnly": "npm run build"
|
|
23
26
|
},
|
|
24
27
|
"keywords": [
|
|
@@ -39,15 +42,13 @@
|
|
|
39
42
|
"url": "https://github.com/darkamenosa/openzca/issues"
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
45
|
+
"node": ">=22.13.0"
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|
|
45
48
|
"@types/qrcode-terminal": "^0.12.2",
|
|
46
49
|
"commander": "^14.0.3",
|
|
47
50
|
"image-size": "^2.0.2",
|
|
48
51
|
"qrcode-terminal": "^0.12.0",
|
|
49
|
-
"sqlite": "^5.1.1",
|
|
50
|
-
"sqlite3": "^6.0.1",
|
|
51
52
|
"zca-js": "^2.1.2"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|