@deckasoft/waify 0.4.0 → 0.4.1
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/index.js +99 -39
- package/package.json +3 -1
package/dist/cli/index.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/core/paths.ts
|
|
13
13
|
import { join } from "path";
|
|
14
14
|
import { homedir } from "os";
|
|
15
|
-
var dataDir, configPath, promptPath, scheduleJsonPath, schedulePath, logPath, envPath, composePath;
|
|
15
|
+
var dataDir, configPath, promptPath, scheduleJsonPath, schedulePath, logPath, envPath, composePath, qrImagePath;
|
|
16
16
|
var init_paths = __esm({
|
|
17
17
|
"src/core/paths.ts"() {
|
|
18
18
|
"use strict";
|
|
@@ -24,6 +24,7 @@ var init_paths = __esm({
|
|
|
24
24
|
logPath = () => join(dataDir(), "messages.log");
|
|
25
25
|
envPath = () => process.env["WAIFY_ENV_PATH"] ?? join(dataDir(), ".env");
|
|
26
26
|
composePath = () => join(dataDir(), "docker-compose.yml");
|
|
27
|
+
qrImagePath = () => join(dataDir(), "qr.png");
|
|
27
28
|
}
|
|
28
29
|
});
|
|
29
30
|
|
|
@@ -203,7 +204,7 @@ var init_schedule = __esm({
|
|
|
203
204
|
});
|
|
204
205
|
|
|
205
206
|
// src/core/secrets.ts
|
|
206
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as
|
|
207
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
207
208
|
import { z as z4 } from "zod";
|
|
208
209
|
var SecretsSchema, parseEnvFile, loadSecrets, tryLoadSecrets, saveSecrets;
|
|
209
210
|
var init_secrets = __esm({
|
|
@@ -231,7 +232,7 @@ var init_secrets = __esm({
|
|
|
231
232
|
const existing = existsSync5(path) ? parseEnvFile(readFileSync4(path, "utf-8")) : {};
|
|
232
233
|
const merged = { ...existing, ...next };
|
|
233
234
|
const body = Object.entries(merged).filter(([, v]) => typeof v === "string" && v.length > 0).map(([k, v]) => `${k}=${v}`).join("\n");
|
|
234
|
-
|
|
235
|
+
writeFileSync5(path, body + "\n", "utf-8");
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
});
|
|
@@ -307,8 +308,8 @@ var init_sender = __esm({
|
|
|
307
308
|
});
|
|
308
309
|
|
|
309
310
|
// src/core/logger.ts
|
|
310
|
-
import { appendFileSync, existsSync as existsSync7, mkdirSync as
|
|
311
|
-
import { dirname as
|
|
311
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5 } from "fs";
|
|
312
|
+
import { dirname as dirname5 } from "path";
|
|
312
313
|
var log, LINE_RE, parseLine, readHistory;
|
|
313
314
|
var init_logger = __esm({
|
|
314
315
|
"src/core/logger.ts"() {
|
|
@@ -316,7 +317,7 @@ var init_logger = __esm({
|
|
|
316
317
|
init_paths();
|
|
317
318
|
log = (status, detail) => {
|
|
318
319
|
const path = logPath();
|
|
319
|
-
|
|
320
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
320
321
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
321
322
|
const line = `[${timestamp}] ${status.toUpperCase()} | ${detail}
|
|
322
323
|
`;
|
|
@@ -953,19 +954,50 @@ var registerInit = (program2) => {
|
|
|
953
954
|
};
|
|
954
955
|
|
|
955
956
|
// src/cli/commands/setup.ts
|
|
956
|
-
init_paths();
|
|
957
|
-
init_config();
|
|
958
|
-
init_secrets();
|
|
959
|
-
init_schedule();
|
|
960
|
-
init_prompt();
|
|
961
957
|
import { spawnSync } from "child_process";
|
|
962
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
958
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
963
959
|
import { homedir as homedir2 } from "os";
|
|
964
960
|
import { join as join2 } from "path";
|
|
965
961
|
import { createInterface } from "readline";
|
|
966
962
|
import qrcode from "qrcode-terminal";
|
|
963
|
+
|
|
964
|
+
// src/core/qr.ts
|
|
965
|
+
init_paths();
|
|
966
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
967
|
+
import { dirname as dirname4 } from "path";
|
|
967
968
|
import { PNG } from "pngjs";
|
|
968
969
|
import jsQR from "jsqr";
|
|
970
|
+
var stripDataUrlPrefix = (dataUrl) => dataUrl.replace(/^data:image\/\w+;base64,/, "");
|
|
971
|
+
var decodeQrDataUrl = (dataUrl) => {
|
|
972
|
+
try {
|
|
973
|
+
const buffer = Buffer.from(stripDataUrlPrefix(dataUrl), "base64");
|
|
974
|
+
const png = PNG.sync.read(buffer);
|
|
975
|
+
const result = jsQR(new Uint8ClampedArray(png.data), png.width, png.height);
|
|
976
|
+
return result?.data ?? null;
|
|
977
|
+
} catch {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
var saveQrImage = (dataUrl) => {
|
|
982
|
+
try {
|
|
983
|
+
const buffer = Buffer.from(stripDataUrlPrefix(dataUrl), "base64");
|
|
984
|
+
const path = qrImagePath();
|
|
985
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
986
|
+
writeFileSync4(path, buffer);
|
|
987
|
+
return path;
|
|
988
|
+
} catch (err) {
|
|
989
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
990
|
+
console.warn(`Could not save QR image: ${msg}`);
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
// src/cli/commands/setup.ts
|
|
996
|
+
init_paths();
|
|
997
|
+
init_config();
|
|
998
|
+
init_secrets();
|
|
999
|
+
init_schedule();
|
|
1000
|
+
init_prompt();
|
|
969
1001
|
import { z as z5 } from "zod";
|
|
970
1002
|
var SessionResponseSchema = z5.object({
|
|
971
1003
|
id: z5.string().optional(),
|
|
@@ -1020,24 +1052,34 @@ var createSpinner = (message) => {
|
|
|
1020
1052
|
};
|
|
1021
1053
|
var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1022
1054
|
var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
|
|
1023
|
-
var
|
|
1024
|
-
const base64 = dataUrl.replace(/^data:image\/\w+;base64,/, "");
|
|
1025
|
-
const buffer = Buffer.from(base64, "base64");
|
|
1026
|
-
const png = PNG.sync.read(buffer);
|
|
1027
|
-
const result = jsQR(new Uint8ClampedArray(png.data), png.width, png.height);
|
|
1028
|
-
return result?.data ?? null;
|
|
1029
|
-
};
|
|
1030
|
-
var renderQrInTerminal = (dataUrl) => new Promise((resolve2) => {
|
|
1055
|
+
var renderQrInTerminal = (dataUrl) => {
|
|
1031
1056
|
const raw = decodeQrDataUrl(dataUrl);
|
|
1032
|
-
if (raw)
|
|
1033
|
-
|
|
1034
|
-
|
|
1057
|
+
if (!raw) return false;
|
|
1058
|
+
qrcode.generate(raw, { small: true });
|
|
1059
|
+
return true;
|
|
1060
|
+
};
|
|
1061
|
+
var presentQr = (dataUrl, sessionId, baseUrl, apiKey) => {
|
|
1062
|
+
const rendered = renderQrInTerminal(dataUrl);
|
|
1063
|
+
if (!rendered) {
|
|
1035
1064
|
console.warn(
|
|
1036
|
-
" (Could not decode QR image \u2014
|
|
1065
|
+
" (Could not decode QR image \u2014 use the saved PNG or curl command below)"
|
|
1037
1066
|
);
|
|
1038
|
-
resolve2();
|
|
1039
1067
|
}
|
|
1040
|
-
|
|
1068
|
+
const savedPath = saveQrImage(dataUrl);
|
|
1069
|
+
if (savedPath) {
|
|
1070
|
+
console.warn(`
|
|
1071
|
+
QR also saved to: ${savedPath}`);
|
|
1072
|
+
console.warn(` Open it with: open ${savedPath}`);
|
|
1073
|
+
}
|
|
1074
|
+
console.warn("\n To re-fetch the QR if it expires:");
|
|
1075
|
+
console.warn(
|
|
1076
|
+
` curl -s -H "X-API-Key: ${apiKey}" ${baseUrl}/api/sessions/${sessionId}/qr \\`
|
|
1077
|
+
);
|
|
1078
|
+
console.warn(
|
|
1079
|
+
` | sed 's/.*"qrCode":"data:image\\/png;base64,//;s/".*//' \\`
|
|
1080
|
+
);
|
|
1081
|
+
console.warn(` | base64 -d > waify-qr.png`);
|
|
1082
|
+
};
|
|
1041
1083
|
var composeTemplate = () => `services:
|
|
1042
1084
|
openwa-api:
|
|
1043
1085
|
image: ghcr.io/deckasoft/openwa:latest
|
|
@@ -1064,6 +1106,16 @@ var composeTemplate = () => `services:
|
|
|
1064
1106
|
volumes:
|
|
1065
1107
|
openwa-data:
|
|
1066
1108
|
`;
|
|
1109
|
+
var finalizeSetup = (sessionId) => {
|
|
1110
|
+
saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
|
|
1111
|
+
if (!existsSync6(promptPath())) {
|
|
1112
|
+
savePrompt(defaultPrompt);
|
|
1113
|
+
}
|
|
1114
|
+
if (!existsSync6(scheduleJsonPath())) {
|
|
1115
|
+
saveSchedule(defaultSchedule);
|
|
1116
|
+
}
|
|
1117
|
+
console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
|
|
1118
|
+
};
|
|
1067
1119
|
var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
|
|
1068
1120
|
var registerSetup = (program2) => {
|
|
1069
1121
|
program2.command("setup").description(
|
|
@@ -1084,7 +1136,7 @@ var registerSetup = (program2) => {
|
|
|
1084
1136
|
return;
|
|
1085
1137
|
}
|
|
1086
1138
|
console.warn("Creating config directory...");
|
|
1087
|
-
|
|
1139
|
+
mkdirSync5(join2(homedir2(), ".config", "waify"), { recursive: true });
|
|
1088
1140
|
const baseUrl = loadConfig().openwaBaseUrl;
|
|
1089
1141
|
let geminiKey = "";
|
|
1090
1142
|
while (!geminiKey.trim()) {
|
|
@@ -1113,7 +1165,7 @@ var registerSetup = (program2) => {
|
|
|
1113
1165
|
saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: "" });
|
|
1114
1166
|
saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
|
|
1115
1167
|
console.warn("Writing docker-compose.yml...");
|
|
1116
|
-
|
|
1168
|
+
writeFileSync6(composePath(), composeTemplate(), "utf-8");
|
|
1117
1169
|
console.warn(
|
|
1118
1170
|
"Starting OpenWA containers (this may take a minute on first run)..."
|
|
1119
1171
|
);
|
|
@@ -1261,6 +1313,23 @@ var registerSetup = (program2) => {
|
|
|
1261
1313
|
`Failed to start session: ${startRes.status} ${startRes.statusText}`
|
|
1262
1314
|
);
|
|
1263
1315
|
}
|
|
1316
|
+
try {
|
|
1317
|
+
const preStatusRes = await fetchWithTimeout(
|
|
1318
|
+
`${baseUrl}/api/sessions/${sessionId}`,
|
|
1319
|
+
{ headers: { "X-API-Key": openwaApiKey } }
|
|
1320
|
+
);
|
|
1321
|
+
if (preStatusRes.ok) {
|
|
1322
|
+
const preStatus = StatusResponseSchema.safeParse(
|
|
1323
|
+
await preStatusRes.json()
|
|
1324
|
+
);
|
|
1325
|
+
if (preStatus.success && preStatus.data.status === "ready") {
|
|
1326
|
+
console.warn("\u2713 WhatsApp already linked \u2014 skipping QR scan");
|
|
1327
|
+
finalizeSetup(sessionId);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1264
1333
|
const qrSpinner = createSpinner("Waiting for QR code (Chromium is starting)...");
|
|
1265
1334
|
let qrCode;
|
|
1266
1335
|
const qrStart = Date.now();
|
|
@@ -1291,7 +1360,7 @@ var registerSetup = (program2) => {
|
|
|
1291
1360
|
);
|
|
1292
1361
|
console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device\n");
|
|
1293
1362
|
if (qrCode) {
|
|
1294
|
-
|
|
1363
|
+
presentQr(qrCode, sessionId, baseUrl, openwaApiKey);
|
|
1295
1364
|
console.warn(
|
|
1296
1365
|
"\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)"
|
|
1297
1366
|
);
|
|
@@ -1332,16 +1401,7 @@ var registerSetup = (program2) => {
|
|
|
1332
1401
|
return;
|
|
1333
1402
|
}
|
|
1334
1403
|
connectSpinner.succeed("WhatsApp connected!");
|
|
1335
|
-
|
|
1336
|
-
if (!existsSync6(promptPath())) {
|
|
1337
|
-
savePrompt(defaultPrompt);
|
|
1338
|
-
}
|
|
1339
|
-
if (!existsSync6(scheduleJsonPath())) {
|
|
1340
|
-
saveSchedule(defaultSchedule);
|
|
1341
|
-
}
|
|
1342
|
-
console.warn(
|
|
1343
|
-
"\n\u2713 All done! Run `waify send` to send your first message."
|
|
1344
|
-
);
|
|
1404
|
+
finalizeSetup(sessionId);
|
|
1345
1405
|
} catch (err) {
|
|
1346
1406
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1347
1407
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deckasoft/waify",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "AI-powered daily message sender for WhatsApp, powered by OpenWA",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whatsapp",
|
|
@@ -52,8 +52,10 @@
|
|
|
52
52
|
"@semantic-release/git": "^10.0.1",
|
|
53
53
|
"@types/node": "^22.0.0",
|
|
54
54
|
"@types/pngjs": "^6.0.5",
|
|
55
|
+
"@types/qrcode": "^1.5.6",
|
|
55
56
|
"@types/qrcode-terminal": "^0.12.2",
|
|
56
57
|
"@types/react": "^19.2.15",
|
|
58
|
+
"qrcode": "^1.5.4",
|
|
57
59
|
"semantic-release": "^25.0.3",
|
|
58
60
|
"tsup": "^8.5.1",
|
|
59
61
|
"tsx": "^4.19.0",
|