@deckasoft/waify 0.4.3 → 0.5.0
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 +94 -25
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -119,16 +119,42 @@ var init_prompt = __esm({
|
|
|
119
119
|
|
|
120
120
|
// src/core/schedule.ts
|
|
121
121
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
122
|
-
import { dirname as dirname3
|
|
122
|
+
import { dirname as dirname3 } from "path";
|
|
123
123
|
import { z as z3 } from "zod";
|
|
124
|
-
var ScheduledJobSchema, ScheduleSchema, defaultSchedule, scheduleJsonPath2, loadSchedule, saveSchedule, ofeliaRuntime, renderJob, renderOfeliaIni, regenerateOfeliaIni, addJob, removeJob;
|
|
124
|
+
var CRON_RANGES, STEP_RE, RANGE_RE, isValidCronField, isValidCron, ScheduledJobSchema, ScheduleSchema, defaultSchedule, scheduleJsonPath2, loadSchedule, saveSchedule, ofeliaRuntime, renderJob, renderOfeliaIni, regenerateOfeliaIni, addJob, removeJob;
|
|
125
125
|
var init_schedule = __esm({
|
|
126
126
|
"src/core/schedule.ts"() {
|
|
127
127
|
"use strict";
|
|
128
128
|
init_paths();
|
|
129
|
+
CRON_RANGES = [
|
|
130
|
+
[0, 59],
|
|
131
|
+
// seconds
|
|
132
|
+
[0, 59],
|
|
133
|
+
// minutes
|
|
134
|
+
[0, 23],
|
|
135
|
+
// hours
|
|
136
|
+
[1, 31],
|
|
137
|
+
// day-of-month
|
|
138
|
+
[1, 12],
|
|
139
|
+
// month
|
|
140
|
+
[0, 7]
|
|
141
|
+
// day-of-week
|
|
142
|
+
];
|
|
143
|
+
STEP_RE = /^(\*|\d+)\/\d+$/;
|
|
144
|
+
RANGE_RE = /^\d+-\d+$/;
|
|
145
|
+
isValidCronField = (field, [min, max]) => {
|
|
146
|
+
if (field === "*") return true;
|
|
147
|
+
if (STEP_RE.test(field) || RANGE_RE.test(field)) return true;
|
|
148
|
+
const n = Number(field);
|
|
149
|
+
return Number.isInteger(n) && n >= min && n <= max;
|
|
150
|
+
};
|
|
151
|
+
isValidCron = (value) => {
|
|
152
|
+
const fields = value.trim().split(/\s+/);
|
|
153
|
+
return fields.length === 6 && fields.every((f, i) => isValidCronField(f, CRON_RANGES[i]));
|
|
154
|
+
};
|
|
129
155
|
ScheduledJobSchema = z3.object({
|
|
130
156
|
name: z3.string().min(1).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with dashes"),
|
|
131
|
-
schedule: z3.string().min(1),
|
|
157
|
+
schedule: z3.string().min(1).refine(isValidCron, { message: "schedule must be a 6-field cron expression (e.g. 0 0 9 * * *)" }),
|
|
132
158
|
command: z3.string().min(1).default("send")
|
|
133
159
|
});
|
|
134
160
|
ScheduleSchema = z3.object({
|
|
@@ -156,8 +182,8 @@ var init_schedule = __esm({
|
|
|
156
182
|
ofeliaRuntime = () => ({
|
|
157
183
|
image: process.env["WAIFY_SENDER_IMAGE"] ?? "openwa-scripts-sender:latest",
|
|
158
184
|
network: process.env["WAIFY_NETWORK"] ?? "waify-network",
|
|
159
|
-
hostDataDir: process.env["WAIFY_HOST_DATA_DIR"] ??
|
|
160
|
-
hostEnvFile: process.env["WAIFY_HOST_ENV_FILE"] ??
|
|
185
|
+
hostDataDir: process.env["WAIFY_HOST_DATA_DIR"] ?? dataDir(),
|
|
186
|
+
hostEnvFile: process.env["WAIFY_HOST_ENV_FILE"] ?? envPath()
|
|
161
187
|
});
|
|
162
188
|
renderJob = (job, runtime) => [
|
|
163
189
|
`[job-run "${job.name}"]`,
|
|
@@ -887,8 +913,12 @@ var init_start = __esm({
|
|
|
887
913
|
}
|
|
888
914
|
});
|
|
889
915
|
|
|
916
|
+
// src/cli/env.ts
|
|
917
|
+
init_paths();
|
|
918
|
+
import { config as dotenvConfig } from "dotenv";
|
|
919
|
+
dotenvConfig({ path: envPath() });
|
|
920
|
+
|
|
890
921
|
// src/cli/index.ts
|
|
891
|
-
import "dotenv/config";
|
|
892
922
|
import { Command } from "commander";
|
|
893
923
|
|
|
894
924
|
// src/cli/commands/init.ts
|
|
@@ -1050,7 +1080,7 @@ var createSpinner = (message) => {
|
|
|
1050
1080
|
}
|
|
1051
1081
|
};
|
|
1052
1082
|
};
|
|
1053
|
-
var wait = (ms) => new Promise((
|
|
1083
|
+
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1054
1084
|
var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
|
|
1055
1085
|
var renderQrInTerminal = (dataUrl) => {
|
|
1056
1086
|
const raw = decodeQrDataUrl(dataUrl);
|
|
@@ -1106,17 +1136,45 @@ var composeTemplate = () => `services:
|
|
|
1106
1136
|
volumes:
|
|
1107
1137
|
openwa-data:
|
|
1108
1138
|
`;
|
|
1109
|
-
var finalizeSetup = (sessionId) => {
|
|
1139
|
+
var finalizeSetup = (sessionId, jobs) => {
|
|
1110
1140
|
saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
|
|
1111
1141
|
if (!existsSync6(promptPath())) {
|
|
1112
1142
|
savePrompt(defaultPrompt);
|
|
1113
1143
|
}
|
|
1114
|
-
|
|
1115
|
-
saveSchedule(defaultSchedule);
|
|
1116
|
-
}
|
|
1144
|
+
saveSchedule({ jobs });
|
|
1117
1145
|
console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
|
|
1118
1146
|
};
|
|
1119
|
-
var promptLine = (rl, question) => new Promise((
|
|
1147
|
+
var promptLine = (rl, question) => new Promise((resolve) => rl.question(question, resolve));
|
|
1148
|
+
var promptUntilValid = async (promptFn, question, validate, errorMsg) => {
|
|
1149
|
+
const answer = (await promptFn(question)).trim();
|
|
1150
|
+
if (validate(answer)) return answer;
|
|
1151
|
+
process.stderr.write(errorMsg + "\n");
|
|
1152
|
+
return promptUntilValid(promptFn, question, validate, errorMsg);
|
|
1153
|
+
};
|
|
1154
|
+
var collectJobs = async (promptFn, accumulated = []) => {
|
|
1155
|
+
const name = await promptUntilValid(
|
|
1156
|
+
promptFn,
|
|
1157
|
+
"Job name: ",
|
|
1158
|
+
(v) => /^[a-z0-9-]+$/.test(v),
|
|
1159
|
+
"Name must be lowercase letters, numbers, and dashes only."
|
|
1160
|
+
);
|
|
1161
|
+
const schedule = await promptUntilValid(
|
|
1162
|
+
promptFn,
|
|
1163
|
+
"Cron pattern (e.g. 0 0 9 * * *): ",
|
|
1164
|
+
isValidCron,
|
|
1165
|
+
"Invalid cron pattern. Use 6 space-separated fields, e.g. 0 0 9 * * *"
|
|
1166
|
+
);
|
|
1167
|
+
const job = ScheduledJobSchema.parse({ name, schedule, command: "send" });
|
|
1168
|
+
const jobs = [...accumulated, job];
|
|
1169
|
+
const more = (await promptFn("Add another schedule? (y/N) ")).trim().toLowerCase();
|
|
1170
|
+
return more === "y" ? collectJobs(promptFn, jobs) : jobs;
|
|
1171
|
+
};
|
|
1172
|
+
var promptScheduleJobs = async (promptFn) => {
|
|
1173
|
+
process.stderr.write(
|
|
1174
|
+
"\nConfigure your message schedule (at least one job required).\nJob names: lowercase letters, numbers, and dashes only.\nCron pattern: 6 fields, e.g. 0 0 9 * * * (sec min hour dom month dow)\n\n"
|
|
1175
|
+
);
|
|
1176
|
+
return collectJobs(promptFn);
|
|
1177
|
+
};
|
|
1120
1178
|
var registerSetup = (program2) => {
|
|
1121
1179
|
program2.command("setup").description(
|
|
1122
1180
|
"Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify"
|
|
@@ -1164,6 +1222,7 @@ var registerSetup = (program2) => {
|
|
|
1164
1222
|
const chatId = `${recipientNumber.trim()}@c.us`;
|
|
1165
1223
|
saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: "" });
|
|
1166
1224
|
saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
|
|
1225
|
+
const jobs = await promptScheduleJobs((q) => promptLine(rl, q));
|
|
1167
1226
|
console.warn("Writing docker-compose.yml...");
|
|
1168
1227
|
writeFileSync6(composePath(), composeTemplate(), "utf-8");
|
|
1169
1228
|
console.warn(
|
|
@@ -1300,18 +1359,28 @@ var registerSetup = (program2) => {
|
|
|
1300
1359
|
{ encoding: "utf-8" }
|
|
1301
1360
|
);
|
|
1302
1361
|
console.warn("Starting WhatsApp engine...");
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (!startRes.ok && startRes.status !== 400) {
|
|
1312
|
-
throw new Error(
|
|
1313
|
-
`Failed to start session: ${startRes.status} ${startRes.statusText}`
|
|
1362
|
+
try {
|
|
1363
|
+
const startRes = await fetchWithTimeout(
|
|
1364
|
+
`${baseUrl}/api/sessions/${sessionId}/start`,
|
|
1365
|
+
{
|
|
1366
|
+
method: "POST",
|
|
1367
|
+
headers: { "X-API-Key": openwaApiKey }
|
|
1368
|
+
},
|
|
1369
|
+
6e4
|
|
1314
1370
|
);
|
|
1371
|
+
if (!startRes.ok && startRes.status !== 400) {
|
|
1372
|
+
throw new Error(
|
|
1373
|
+
`Failed to start session: ${startRes.status} ${startRes.statusText}`
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
|
1378
|
+
console.warn(
|
|
1379
|
+
" (Engine start is taking longer than expected \u2014 Chromium may still be loading, continuing\u2026)"
|
|
1380
|
+
);
|
|
1381
|
+
} else {
|
|
1382
|
+
throw err;
|
|
1383
|
+
}
|
|
1315
1384
|
}
|
|
1316
1385
|
try {
|
|
1317
1386
|
const preStatusRes = await fetchWithTimeout(
|
|
@@ -1324,7 +1393,7 @@ var registerSetup = (program2) => {
|
|
|
1324
1393
|
);
|
|
1325
1394
|
if (preStatus.success && preStatus.data.status === "ready") {
|
|
1326
1395
|
console.warn("\u2713 WhatsApp already linked \u2014 skipping QR scan");
|
|
1327
|
-
finalizeSetup(sessionId);
|
|
1396
|
+
finalizeSetup(sessionId, jobs);
|
|
1328
1397
|
return;
|
|
1329
1398
|
}
|
|
1330
1399
|
}
|
|
@@ -1401,7 +1470,7 @@ var registerSetup = (program2) => {
|
|
|
1401
1470
|
return;
|
|
1402
1471
|
}
|
|
1403
1472
|
connectSpinner.succeed("WhatsApp connected!");
|
|
1404
|
-
finalizeSetup(sessionId);
|
|
1473
|
+
finalizeSetup(sessionId, jobs);
|
|
1405
1474
|
} catch (err) {
|
|
1406
1475
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1407
1476
|
process.exitCode = 1;
|