@deckasoft/waify 0.3.4 → 0.3.5
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 +170 -128
- package/package.json +1 -3
package/dist/cli/index.js
CHANGED
|
@@ -963,40 +963,46 @@ 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
966
|
import { z as z5 } from "zod";
|
|
968
|
-
import qrcode from "qrcode-terminal";
|
|
969
967
|
var SessionResponseSchema = z5.object({
|
|
970
968
|
id: z5.string().optional(),
|
|
971
969
|
name: z5.string().optional()
|
|
972
970
|
});
|
|
973
971
|
var QrResponseSchema = z5.object({
|
|
974
|
-
|
|
972
|
+
qrCode: z5.string().optional()
|
|
975
973
|
});
|
|
976
974
|
var StatusResponseSchema = z5.object({
|
|
977
|
-
status: z5.string().optional()
|
|
978
|
-
connected: z5.boolean().optional()
|
|
975
|
+
status: z5.string().optional()
|
|
979
976
|
});
|
|
980
977
|
var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
981
|
-
var
|
|
978
|
+
var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
|
|
979
|
+
var composeTemplate = () => `services:
|
|
982
980
|
openwa-api:
|
|
983
981
|
image: ghcr.io/deckasoft/openwa:latest
|
|
984
982
|
ports:
|
|
985
|
-
- '2785:
|
|
983
|
+
- '2785:2785'
|
|
986
984
|
environment:
|
|
987
985
|
- NODE_ENV=production
|
|
988
|
-
-
|
|
986
|
+
- PORT=2785
|
|
987
|
+
- DATABASE_TYPE=sqlite
|
|
988
|
+
- DATABASE_SYNCHRONIZE=true
|
|
989
|
+
- ENGINE_TYPE=whatsapp-web.js
|
|
990
|
+
- SESSION_DATA_PATH=/app/data/sessions
|
|
991
|
+
- PUPPETEER_HEADLESS=true
|
|
992
|
+
- PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage,--disable-gpu
|
|
993
|
+
- STORAGE_TYPE=local
|
|
994
|
+
- STORAGE_LOCAL_PATH=/app/data/media
|
|
995
|
+
- QUEUE_ENABLED=false
|
|
996
|
+
- REDIS_ENABLED=false
|
|
997
|
+
- REDIS_BUILTIN=false
|
|
989
998
|
volumes:
|
|
990
999
|
- openwa-data:/app/data
|
|
991
1000
|
restart: unless-stopped
|
|
992
1001
|
|
|
993
1002
|
openwa-dashboard:
|
|
994
|
-
image: ghcr.io/deckasoft/openwa:latest
|
|
1003
|
+
image: ghcr.io/deckasoft/openwa-dashboard:latest
|
|
995
1004
|
ports:
|
|
996
|
-
- '2886:
|
|
997
|
-
environment:
|
|
998
|
-
- NODE_ENV=production
|
|
999
|
-
- OPENWA_API_KEY=${openwaApiKey}
|
|
1005
|
+
- '2886:80'
|
|
1000
1006
|
restart: unless-stopped
|
|
1001
1007
|
|
|
1002
1008
|
volumes:
|
|
@@ -1005,131 +1011,167 @@ volumes:
|
|
|
1005
1011
|
var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
|
|
1006
1012
|
var registerSetup = (program2) => {
|
|
1007
1013
|
program2.command("setup").description("Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify").action(async () => {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1014
|
+
try {
|
|
1015
|
+
console.warn("Checking Docker...");
|
|
1016
|
+
const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
|
|
1017
|
+
if (dockerCheck.status !== 0) {
|
|
1018
|
+
console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
console.warn("Creating config directory...");
|
|
1022
|
+
mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
|
|
1023
|
+
console.warn("Writing docker-compose.yml...");
|
|
1024
|
+
writeFileSync5(composePath(), composeTemplate(), "utf-8");
|
|
1025
|
+
console.warn("Starting OpenWA containers (this may take a minute on first run)...");
|
|
1026
|
+
const upResult = spawnSync("docker", ["compose", "-f", composePath(), "up", "-d", "--no-deps", "openwa-api"], {
|
|
1027
|
+
stdio: "inherit"
|
|
1028
|
+
});
|
|
1029
|
+
if (upResult.status !== 0) {
|
|
1030
|
+
console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
|
|
1031
|
+
process.exit(1);
|
|
1032
|
+
}
|
|
1033
|
+
console.warn("Waiting for OpenWA API to start...");
|
|
1034
|
+
let apiReady = false;
|
|
1035
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1036
|
+
try {
|
|
1037
|
+
const res = await fetchWithTimeout("http://localhost:2785/api/health");
|
|
1038
|
+
if (res.status >= 200 && res.status < 300) {
|
|
1039
|
+
apiReady = true;
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
} catch {
|
|
1036
1043
|
}
|
|
1037
|
-
|
|
1044
|
+
await wait(2e3);
|
|
1038
1045
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1046
|
+
if (!apiReady) {
|
|
1047
|
+
console.error(
|
|
1048
|
+
"OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
|
|
1049
|
+
);
|
|
1050
|
+
process.exit(1);
|
|
1051
|
+
}
|
|
1052
|
+
console.warn("Reading API credentials...");
|
|
1053
|
+
const keyResult = spawnSync(
|
|
1054
|
+
"docker",
|
|
1055
|
+
["compose", "-f", composePath(), "exec", "-T", "openwa-api", "cat", "/app/data/.api-key"],
|
|
1056
|
+
{ encoding: "utf-8", stdio: "pipe" }
|
|
1044
1057
|
);
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
console.warn("
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1058
|
+
const openwaApiKey = keyResult.stdout?.trim();
|
|
1059
|
+
if (keyResult.status !== 0 || !openwaApiKey) {
|
|
1060
|
+
console.error(
|
|
1061
|
+
"Could not read API key from container. Check logs: docker compose -f " + composePath() + " logs openwa-api"
|
|
1062
|
+
);
|
|
1063
|
+
if (keyResult.stderr) console.error(keyResult.stderr);
|
|
1064
|
+
process.exit(1);
|
|
1065
|
+
}
|
|
1066
|
+
console.warn("Creating WhatsApp session...");
|
|
1067
|
+
const sessionRes = await fetchWithTimeout("http://localhost:2785/api/sessions", {
|
|
1068
|
+
method: "POST",
|
|
1069
|
+
headers: {
|
|
1070
|
+
"X-API-Key": openwaApiKey,
|
|
1071
|
+
"Content-Type": "application/json"
|
|
1072
|
+
},
|
|
1073
|
+
body: JSON.stringify({ name: "waify" })
|
|
1074
|
+
}, 1e4);
|
|
1075
|
+
if (!sessionRes.ok) {
|
|
1076
|
+
throw new Error(`Failed to create session: ${sessionRes.status} ${sessionRes.statusText}`);
|
|
1077
|
+
}
|
|
1078
|
+
const sessionData = SessionResponseSchema.parse(await sessionRes.json());
|
|
1079
|
+
const sessionId = sessionData.id ?? sessionData.name ?? "waify";
|
|
1080
|
+
saveConfig({ ...loadConfig(), openwaApiKey, openwaSessionId: sessionId });
|
|
1081
|
+
console.warn("Starting WhatsApp engine...");
|
|
1082
|
+
const startRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}/start`, {
|
|
1083
|
+
method: "POST",
|
|
1084
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1085
|
+
}, 1e4);
|
|
1086
|
+
if (!startRes.ok) {
|
|
1087
|
+
throw new Error(`Failed to start session: ${startRes.status} ${startRes.statusText}`);
|
|
1088
|
+
}
|
|
1089
|
+
console.warn("Waiting for QR code...");
|
|
1090
|
+
let qrReady = false;
|
|
1091
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1092
|
+
try {
|
|
1093
|
+
const qrRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}/qr`, {
|
|
1094
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1095
|
+
});
|
|
1096
|
+
if (qrRes.ok) {
|
|
1097
|
+
const qrData = QrResponseSchema.parse(await qrRes.json());
|
|
1098
|
+
if (qrData.qrCode) {
|
|
1099
|
+
qrReady = true;
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1087
1104
|
}
|
|
1088
|
-
|
|
1105
|
+
await wait(2e3);
|
|
1089
1106
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
+
console.warn("\n\u{1F4F1} Scan the QR code with WhatsApp to link your device:");
|
|
1108
|
+
console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device");
|
|
1109
|
+
console.warn(" View QR at: http://localhost:2785/api/docs (Sessions \u2192 GET /sessions/{id}/qr)");
|
|
1110
|
+
if (!qrReady) {
|
|
1111
|
+
console.warn(" (QR not yet ready \u2014 Chromium may still be initializing, check the Swagger link above)");
|
|
1112
|
+
}
|
|
1113
|
+
console.warn(" Waiting up to 2 minutes for you to scan...\n");
|
|
1114
|
+
let connected = false;
|
|
1115
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
1116
|
+
try {
|
|
1117
|
+
const statusRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}`, {
|
|
1118
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1119
|
+
});
|
|
1120
|
+
const parsed = StatusResponseSchema.safeParse(await statusRes.json());
|
|
1121
|
+
if (!parsed.success) continue;
|
|
1122
|
+
if (parsed.data.status === "ready") {
|
|
1123
|
+
connected = true;
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
} catch {
|
|
1107
1127
|
}
|
|
1128
|
+
await wait(2e3);
|
|
1108
1129
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1130
|
+
if (!connected) {
|
|
1131
|
+
console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
|
|
1132
|
+
process.exit(1);
|
|
1133
|
+
}
|
|
1134
|
+
console.warn("\u2713 WhatsApp connected!");
|
|
1135
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1136
|
+
try {
|
|
1137
|
+
let geminiKey = "";
|
|
1138
|
+
while (!geminiKey.trim()) {
|
|
1139
|
+
geminiKey = await promptLine(
|
|
1140
|
+
rl,
|
|
1141
|
+
"Enter your Gemini API key (get one free at https://aistudio.google.com/apikey):\n> "
|
|
1142
|
+
);
|
|
1143
|
+
if (!geminiKey.trim()) {
|
|
1144
|
+
console.warn("Gemini API key cannot be empty. Please try again.");
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
|
|
1148
|
+
let recipientNumber = "";
|
|
1149
|
+
const phoneRegex = /^\d{8,15}$/;
|
|
1150
|
+
while (!phoneRegex.test(recipientNumber.trim())) {
|
|
1151
|
+
recipientNumber = await promptLine(
|
|
1152
|
+
rl,
|
|
1153
|
+
"Enter your recipient's WhatsApp number (e.g. 5511999998888 \u2014 digits only, no + or spaces):\n> "
|
|
1154
|
+
);
|
|
1155
|
+
if (!phoneRegex.test(recipientNumber.trim())) {
|
|
1156
|
+
console.warn("Invalid number format. Use digits only, 8\u201315 characters. Please try again.");
|
|
1157
|
+
}
|
|
1119
1158
|
}
|
|
1159
|
+
const chatId = `${recipientNumber.trim()}@c.us`;
|
|
1160
|
+
saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
|
|
1161
|
+
} finally {
|
|
1162
|
+
rl.close();
|
|
1120
1163
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1164
|
+
if (!existsSync6(promptPath())) {
|
|
1165
|
+
savePrompt(defaultPrompt);
|
|
1166
|
+
}
|
|
1167
|
+
if (!existsSync6(scheduleJsonPath())) {
|
|
1168
|
+
saveSchedule(defaultSchedule);
|
|
1169
|
+
}
|
|
1170
|
+
console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1173
|
+
process.exit(1);
|
|
1131
1174
|
}
|
|
1132
|
-
console.warn("\u2713 All done! Run `waify send` to send your first message.");
|
|
1133
1175
|
});
|
|
1134
1176
|
};
|
|
1135
1177
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deckasoft/waify",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "AI-powered daily message sender for WhatsApp, powered by OpenWA",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"whatsapp",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"ink-select-input": "^6.2.0",
|
|
42
42
|
"ink-spinner": "^5.0.0",
|
|
43
43
|
"ink-text-input": "^6.0.0",
|
|
44
|
-
"qrcode-terminal": "^0.12.0",
|
|
45
44
|
"react": "^19.2.6",
|
|
46
45
|
"zod": "^3.24.0"
|
|
47
46
|
},
|
|
@@ -49,7 +48,6 @@
|
|
|
49
48
|
"@semantic-release/changelog": "^6.0.3",
|
|
50
49
|
"@semantic-release/git": "^10.0.1",
|
|
51
50
|
"@types/node": "^22.0.0",
|
|
52
|
-
"@types/qrcode-terminal": "^0.12.2",
|
|
53
51
|
"@types/react": "^19.2.15",
|
|
54
52
|
"semantic-release": "^25.0.3",
|
|
55
53
|
"tsup": "^8.5.1",
|