@deckasoft/waify 0.3.4 → 0.3.6
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 +153 -112
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -212,7 +212,7 @@ var init_secrets = __esm({
|
|
|
212
212
|
init_paths();
|
|
213
213
|
SecretsSchema = z4.object({
|
|
214
214
|
GEMINI_API_KEY: z4.string().min(1),
|
|
215
|
-
OPENWA_API_KEY: z4.string().min(1)
|
|
215
|
+
OPENWA_API_KEY: z4.string().min(1).default("dev-admin-key")
|
|
216
216
|
});
|
|
217
217
|
parseEnvFile = (content) => content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).reduce((acc, line) => {
|
|
218
218
|
const eq = line.indexOf("=");
|
|
@@ -963,139 +963,73 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as wr
|
|
|
963
963
|
import { homedir as homedir2 } from "os";
|
|
964
964
|
import { join as join2 } from "path";
|
|
965
965
|
import { createInterface } from "readline";
|
|
966
|
-
import * as crypto from "crypto";
|
|
967
|
-
import { z as z5 } from "zod";
|
|
968
966
|
import qrcode from "qrcode-terminal";
|
|
967
|
+
import { z as z5 } from "zod";
|
|
969
968
|
var SessionResponseSchema = z5.object({
|
|
970
969
|
id: z5.string().optional(),
|
|
971
970
|
name: z5.string().optional()
|
|
972
971
|
});
|
|
973
972
|
var QrResponseSchema = z5.object({
|
|
974
|
-
|
|
973
|
+
qrCode: z5.string().optional()
|
|
975
974
|
});
|
|
976
975
|
var StatusResponseSchema = z5.object({
|
|
977
|
-
status: z5.string().optional()
|
|
978
|
-
connected: z5.boolean().optional()
|
|
976
|
+
status: z5.string().optional()
|
|
979
977
|
});
|
|
980
978
|
var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
981
|
-
var
|
|
979
|
+
var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
|
|
980
|
+
var composeTemplate = () => `services:
|
|
982
981
|
openwa-api:
|
|
983
982
|
image: ghcr.io/deckasoft/openwa:latest
|
|
984
983
|
ports:
|
|
985
|
-
- '2785:
|
|
984
|
+
- '2785:2785'
|
|
986
985
|
environment:
|
|
987
986
|
- NODE_ENV=production
|
|
988
|
-
-
|
|
987
|
+
- PORT=2785
|
|
988
|
+
- DATABASE_TYPE=sqlite
|
|
989
|
+
- DATABASE_SYNCHRONIZE=true
|
|
990
|
+
- ENGINE_TYPE=whatsapp-web.js
|
|
991
|
+
- SESSION_DATA_PATH=/app/data/sessions
|
|
992
|
+
- PUPPETEER_HEADLESS=true
|
|
993
|
+
- PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage,--disable-gpu
|
|
994
|
+
- STORAGE_TYPE=local
|
|
995
|
+
- STORAGE_LOCAL_PATH=/app/data/media
|
|
996
|
+
- QUEUE_ENABLED=false
|
|
997
|
+
- REDIS_ENABLED=false
|
|
998
|
+
- REDIS_BUILTIN=false
|
|
989
999
|
volumes:
|
|
990
1000
|
- openwa-data:/app/data
|
|
991
1001
|
restart: unless-stopped
|
|
992
1002
|
|
|
993
1003
|
openwa-dashboard:
|
|
994
|
-
image: ghcr.io/deckasoft/openwa:latest
|
|
1004
|
+
image: ghcr.io/deckasoft/openwa-dashboard:latest
|
|
995
1005
|
ports:
|
|
996
|
-
- '2886:
|
|
997
|
-
environment:
|
|
998
|
-
- NODE_ENV=production
|
|
999
|
-
- OPENWA_API_KEY=${openwaApiKey}
|
|
1006
|
+
- '2886:80'
|
|
1000
1007
|
restart: unless-stopped
|
|
1001
1008
|
|
|
1002
1009
|
volumes:
|
|
1003
1010
|
openwa-data:
|
|
1004
1011
|
`;
|
|
1005
1012
|
var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
|
|
1013
|
+
var renderQr = (qrString) => new Promise((resolve2) => qrcode.generate(qrString, { small: true }, () => resolve2()));
|
|
1006
1014
|
var registerSetup = (program2) => {
|
|
1007
1015
|
program2.command("setup").description("Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify").action(async () => {
|
|
1008
|
-
console.warn("Checking Docker...");
|
|
1009
|
-
const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
|
|
1010
|
-
if (dockerCheck.status !== 0) {
|
|
1011
|
-
console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
|
|
1012
|
-
process.exit(1);
|
|
1013
|
-
}
|
|
1014
|
-
console.warn("Creating config directory...");
|
|
1015
|
-
mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
|
|
1016
|
-
console.warn("Generating credentials...");
|
|
1017
|
-
const openwaApiKey = crypto.randomUUID();
|
|
1018
|
-
console.warn("Writing docker-compose.yml...");
|
|
1019
|
-
writeFileSync5(composePath(), composeTemplate(openwaApiKey), "utf-8");
|
|
1020
|
-
console.warn("Starting OpenWA containers (this may take a minute on first run)...");
|
|
1021
|
-
const upResult = spawnSync("docker", ["compose", "-f", composePath(), "up", "-d"], {
|
|
1022
|
-
stdio: "inherit"
|
|
1023
|
-
});
|
|
1024
|
-
if (upResult.status !== 0) {
|
|
1025
|
-
console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
|
|
1026
|
-
process.exit(1);
|
|
1027
|
-
}
|
|
1028
|
-
console.warn("Waiting for OpenWA API to start...");
|
|
1029
|
-
let apiReady = false;
|
|
1030
|
-
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1031
|
-
try {
|
|
1032
|
-
const res = await fetch("http://localhost:2785/api");
|
|
1033
|
-
if (res.status >= 200 && res.status < 300) {
|
|
1034
|
-
apiReady = true;
|
|
1035
|
-
break;
|
|
1036
|
-
}
|
|
1037
|
-
} catch {
|
|
1038
|
-
}
|
|
1039
|
-
await wait(2e3);
|
|
1040
|
-
}
|
|
1041
|
-
if (!apiReady) {
|
|
1042
|
-
console.error(
|
|
1043
|
-
"OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
|
|
1044
|
-
);
|
|
1045
|
-
process.exit(1);
|
|
1046
|
-
}
|
|
1047
|
-
console.warn("Creating WhatsApp session...");
|
|
1048
|
-
const sessionRes = await fetch("http://localhost:2785/api/sessions", {
|
|
1049
|
-
method: "POST",
|
|
1050
|
-
headers: {
|
|
1051
|
-
"X-API-Key": openwaApiKey,
|
|
1052
|
-
"Content-Type": "application/json"
|
|
1053
|
-
},
|
|
1054
|
-
body: JSON.stringify({ name: "waify" })
|
|
1055
|
-
});
|
|
1056
|
-
if (!sessionRes.ok) {
|
|
1057
|
-
throw new Error(`Failed to create session: ${sessionRes.status} ${sessionRes.statusText}`);
|
|
1058
|
-
}
|
|
1059
|
-
const sessionData = SessionResponseSchema.parse(await sessionRes.json());
|
|
1060
|
-
const sessionId = sessionData.id ?? sessionData.name ?? "waify";
|
|
1061
|
-
saveConfig({ ...loadConfig(), openwaApiKey, openwaSessionId: sessionId });
|
|
1062
|
-
console.warn("\u{1F4F1} Scan the QR code below with WhatsApp on your phone:");
|
|
1063
|
-
console.warn(" (Settings \u2192 Linked Devices \u2192 Link a Device)");
|
|
1064
|
-
const qrRes = await fetch("http://localhost:2785/api/sessions/waify/qr", {
|
|
1065
|
-
headers: { "X-API-Key": openwaApiKey }
|
|
1066
|
-
});
|
|
1067
|
-
if (!qrRes.ok) {
|
|
1068
|
-
console.warn(" Could not fetch QR code \u2014 use the browser link below.");
|
|
1069
|
-
}
|
|
1070
|
-
const qrData = qrRes.ok ? QrResponseSchema.parse(await qrRes.json()) : { qr: void 0 };
|
|
1071
|
-
const rawQr = qrData.qr ?? "";
|
|
1072
|
-
const qrString = rawQr.startsWith("data:image/png;base64,") ? rawQr.slice("data:image/png;base64,".length) : rawQr;
|
|
1073
|
-
qrcode.generate(qrString, { small: true });
|
|
1074
|
-
console.warn(" Or open in browser: http://localhost:2886");
|
|
1075
|
-
let connected = false;
|
|
1076
|
-
for (let attempt = 0; attempt < 60; attempt++) {
|
|
1077
|
-
try {
|
|
1078
|
-
const statusRes = await fetch("http://localhost:2785/api/sessions/waify", {
|
|
1079
|
-
headers: { "X-API-Key": openwaApiKey }
|
|
1080
|
-
});
|
|
1081
|
-
const parsed = StatusResponseSchema.safeParse(await statusRes.json());
|
|
1082
|
-
if (!parsed.success) continue;
|
|
1083
|
-
const statusData = parsed.data;
|
|
1084
|
-
if (statusData.status === "CONNECTED" || statusData.connected === true) {
|
|
1085
|
-
connected = true;
|
|
1086
|
-
break;
|
|
1087
|
-
}
|
|
1088
|
-
} catch {
|
|
1089
|
-
}
|
|
1090
|
-
await wait(2e3);
|
|
1091
|
-
}
|
|
1092
|
-
if (!connected) {
|
|
1093
|
-
console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
|
|
1094
|
-
process.exit(1);
|
|
1095
|
-
}
|
|
1096
|
-
console.warn("\u2713 WhatsApp connected!");
|
|
1097
1016
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1098
1017
|
try {
|
|
1018
|
+
console.warn("Checking Docker...");
|
|
1019
|
+
const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
|
|
1020
|
+
if (dockerCheck.status !== 0) {
|
|
1021
|
+
console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
|
|
1022
|
+
process.exitCode = 1;
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
console.warn("Creating config directory...");
|
|
1026
|
+
mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
|
|
1027
|
+
const baseUrl = loadConfig().openwaBaseUrl;
|
|
1028
|
+
const rawApiKey = await promptLine(
|
|
1029
|
+
rl,
|
|
1030
|
+
'OpenWA API key (press Enter to use default "dev-admin-key"):\n> '
|
|
1031
|
+
);
|
|
1032
|
+
const openwaApiKey = rawApiKey.trim() || "dev-admin-key";
|
|
1099
1033
|
let geminiKey = "";
|
|
1100
1034
|
while (!geminiKey.trim()) {
|
|
1101
1035
|
geminiKey = await promptLine(
|
|
@@ -1106,7 +1040,6 @@ var registerSetup = (program2) => {
|
|
|
1106
1040
|
console.warn("Gemini API key cannot be empty. Please try again.");
|
|
1107
1041
|
}
|
|
1108
1042
|
}
|
|
1109
|
-
saveSecrets({ GEMINI_API_KEY: geminiKey.trim() });
|
|
1110
1043
|
let recipientNumber = "";
|
|
1111
1044
|
const phoneRegex = /^\d{8,15}$/;
|
|
1112
1045
|
while (!phoneRegex.test(recipientNumber.trim())) {
|
|
@@ -1119,17 +1052,125 @@ var registerSetup = (program2) => {
|
|
|
1119
1052
|
}
|
|
1120
1053
|
}
|
|
1121
1054
|
const chatId = `${recipientNumber.trim()}@c.us`;
|
|
1122
|
-
|
|
1055
|
+
saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
|
|
1056
|
+
saveConfig({ ...loadConfig(), openwaApiKey, recipients: [{ chatId }] });
|
|
1057
|
+
console.warn("Writing docker-compose.yml...");
|
|
1058
|
+
writeFileSync5(composePath(), composeTemplate(), "utf-8");
|
|
1059
|
+
console.warn("Starting OpenWA containers (this may take a minute on first run)...");
|
|
1060
|
+
const upResult = spawnSync("docker", ["compose", "-f", composePath(), "up", "-d", "--no-deps", "openwa-api"], {
|
|
1061
|
+
stdio: "inherit"
|
|
1062
|
+
});
|
|
1063
|
+
if (upResult.status !== 0) {
|
|
1064
|
+
console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
|
|
1065
|
+
process.exitCode = 1;
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
console.warn("Waiting for OpenWA API to start...");
|
|
1069
|
+
let apiReady = false;
|
|
1070
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1071
|
+
try {
|
|
1072
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/health`);
|
|
1073
|
+
if (res.status >= 200 && res.status < 300) {
|
|
1074
|
+
apiReady = true;
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
} catch {
|
|
1078
|
+
}
|
|
1079
|
+
await wait(2e3);
|
|
1080
|
+
}
|
|
1081
|
+
if (!apiReady) {
|
|
1082
|
+
console.error(
|
|
1083
|
+
"OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
|
|
1084
|
+
);
|
|
1085
|
+
process.exitCode = 1;
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
console.warn("Creating WhatsApp session...");
|
|
1089
|
+
const sessionRes = await fetchWithTimeout(`${baseUrl}/api/sessions`, {
|
|
1090
|
+
method: "POST",
|
|
1091
|
+
headers: {
|
|
1092
|
+
"X-API-Key": openwaApiKey,
|
|
1093
|
+
"Content-Type": "application/json"
|
|
1094
|
+
},
|
|
1095
|
+
body: JSON.stringify({ name: "waify" })
|
|
1096
|
+
}, 1e4);
|
|
1097
|
+
if (!sessionRes.ok) {
|
|
1098
|
+
throw new Error(`Failed to create session: ${sessionRes.status} ${sessionRes.statusText}`);
|
|
1099
|
+
}
|
|
1100
|
+
const sessionData = SessionResponseSchema.parse(await sessionRes.json());
|
|
1101
|
+
const sessionId = sessionData.id ?? sessionData.name ?? "waify";
|
|
1102
|
+
console.warn("Starting WhatsApp engine...");
|
|
1103
|
+
const startRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/start`, {
|
|
1104
|
+
method: "POST",
|
|
1105
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1106
|
+
}, 1e4);
|
|
1107
|
+
if (!startRes.ok) {
|
|
1108
|
+
throw new Error(`Failed to start session: ${startRes.status} ${startRes.statusText}`);
|
|
1109
|
+
}
|
|
1110
|
+
console.warn("Waiting for QR code...");
|
|
1111
|
+
let qrCode;
|
|
1112
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1113
|
+
try {
|
|
1114
|
+
const qrRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/qr`, {
|
|
1115
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1116
|
+
});
|
|
1117
|
+
if (qrRes.ok) {
|
|
1118
|
+
const qrData = QrResponseSchema.parse(await qrRes.json());
|
|
1119
|
+
if (qrData.qrCode) {
|
|
1120
|
+
qrCode = qrData.qrCode;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
} catch {
|
|
1125
|
+
}
|
|
1126
|
+
await wait(2e3);
|
|
1127
|
+
}
|
|
1128
|
+
console.warn("\n\u{1F4F1} Scan the QR code with WhatsApp to link your device:");
|
|
1129
|
+
console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device\n");
|
|
1130
|
+
if (qrCode) {
|
|
1131
|
+
await renderQr(qrCode);
|
|
1132
|
+
console.warn("\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)");
|
|
1133
|
+
} else {
|
|
1134
|
+
console.warn(` QR not yet ready. Check: ${baseUrl}/api/sessions/${sessionId}/qr`);
|
|
1135
|
+
console.warn(` (Add header: X-API-Key: ${openwaApiKey})`);
|
|
1136
|
+
}
|
|
1137
|
+
console.warn(" Waiting up to 2 minutes for you to scan...\n");
|
|
1138
|
+
let connected = false;
|
|
1139
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
1140
|
+
try {
|
|
1141
|
+
const statusRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}`, {
|
|
1142
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1143
|
+
});
|
|
1144
|
+
const parsed = StatusResponseSchema.safeParse(await statusRes.json());
|
|
1145
|
+
if (!parsed.success) continue;
|
|
1146
|
+
if (parsed.data.status === "ready") {
|
|
1147
|
+
connected = true;
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
} catch {
|
|
1151
|
+
}
|
|
1152
|
+
await wait(2e3);
|
|
1153
|
+
}
|
|
1154
|
+
if (!connected) {
|
|
1155
|
+
console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
|
|
1156
|
+
process.exitCode = 1;
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
console.warn("\u2713 WhatsApp connected!");
|
|
1160
|
+
saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
|
|
1161
|
+
if (!existsSync6(promptPath())) {
|
|
1162
|
+
savePrompt(defaultPrompt);
|
|
1163
|
+
}
|
|
1164
|
+
if (!existsSync6(scheduleJsonPath())) {
|
|
1165
|
+
saveSchedule(defaultSchedule);
|
|
1166
|
+
}
|
|
1167
|
+
console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1170
|
+
process.exitCode = 1;
|
|
1123
1171
|
} finally {
|
|
1124
1172
|
rl.close();
|
|
1125
1173
|
}
|
|
1126
|
-
if (!existsSync6(promptPath())) {
|
|
1127
|
-
savePrompt(defaultPrompt);
|
|
1128
|
-
}
|
|
1129
|
-
if (!existsSync6(scheduleJsonPath())) {
|
|
1130
|
-
saveSchedule(defaultSchedule);
|
|
1131
|
-
}
|
|
1132
|
-
console.warn("\u2713 All done! Run `waify send` to send your first message.");
|
|
1133
1174
|
});
|
|
1134
1175
|
};
|
|
1135
1176
|
|