@whanext/core 0.3.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/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +36 -0
- package/LICENSE +21 -0
- package/README.md +507 -0
- package/SECURITY.md +16 -0
- package/dist/index.d.ts +500 -0
- package/dist/index.js +1988 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1988 @@
|
|
|
1
|
+
// src/cache/memory-cache.ts
|
|
2
|
+
var MemoryCache = class {
|
|
3
|
+
#entries = /* @__PURE__ */ new Map();
|
|
4
|
+
async get(key) {
|
|
5
|
+
const entry = this.#entries.get(key);
|
|
6
|
+
if (!entry) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
|
|
10
|
+
this.#entries.delete(key);
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
return entry.value;
|
|
14
|
+
}
|
|
15
|
+
async set(key, value, ttlMs) {
|
|
16
|
+
const entry = { value };
|
|
17
|
+
if (ttlMs !== void 0) {
|
|
18
|
+
entry.expiresAt = Date.now() + ttlMs;
|
|
19
|
+
}
|
|
20
|
+
this.#entries.set(key, entry);
|
|
21
|
+
}
|
|
22
|
+
async delete(key) {
|
|
23
|
+
this.#entries.delete(key);
|
|
24
|
+
}
|
|
25
|
+
async clear() {
|
|
26
|
+
this.#entries.clear();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/errors/error.ts
|
|
31
|
+
var WhaNextError = class extends Error {
|
|
32
|
+
code;
|
|
33
|
+
context;
|
|
34
|
+
recoverable;
|
|
35
|
+
constructor(code, message, options = {}) {
|
|
36
|
+
super(message, { cause: options.cause });
|
|
37
|
+
this.name = "WhaNextError";
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.context = options.context ?? {};
|
|
40
|
+
this.recoverable = options.recoverable ?? false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function toWhaNextError(error, context) {
|
|
44
|
+
if (error instanceof WhaNextError) {
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
const message = error instanceof Error ? error.message : "An unknown error occurred.";
|
|
48
|
+
return new WhaNextError("UNKNOWN_ERROR", message, {
|
|
49
|
+
cause: error,
|
|
50
|
+
...context ? { context } : {}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/models/identity.ts
|
|
55
|
+
function normalizeIdentity(identity) {
|
|
56
|
+
const value = identity.trim().toLowerCase();
|
|
57
|
+
const separator = value.lastIndexOf("@");
|
|
58
|
+
if (separator === -1) {
|
|
59
|
+
return value.replace(/\D/g, "");
|
|
60
|
+
}
|
|
61
|
+
const user = value.slice(0, separator).replace(/:\d+$/, "");
|
|
62
|
+
const rawServer = value.slice(separator + 1);
|
|
63
|
+
const server = rawServer === "c.us" ? "s.whatsapp.net" : rawServer;
|
|
64
|
+
return `${user}@${server}`;
|
|
65
|
+
}
|
|
66
|
+
function identityUsername(identity) {
|
|
67
|
+
const normalized = normalizeIdentity(identity);
|
|
68
|
+
const separator = normalized.lastIndexOf("@");
|
|
69
|
+
return separator === -1 ? normalized : normalized.slice(0, separator);
|
|
70
|
+
}
|
|
71
|
+
function identityPhoneNumber(identity) {
|
|
72
|
+
const normalized = normalizeIdentity(identity);
|
|
73
|
+
if (normalized.endsWith("@lid")) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const username = identityUsername(normalized);
|
|
77
|
+
return /^\d+$/.test(username) ? username : void 0;
|
|
78
|
+
}
|
|
79
|
+
function identitiesMatch(left, right) {
|
|
80
|
+
return normalizeIdentity(left) === normalizeIdentity(right);
|
|
81
|
+
}
|
|
82
|
+
function uniqueIdentities(identities) {
|
|
83
|
+
const values = identities.filter((identity) => Boolean(identity));
|
|
84
|
+
return [...new Map(values.map((identity) => [normalizeIdentity(identity), identity])).values()];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/models/user.ts
|
|
88
|
+
var User = class _User {
|
|
89
|
+
id;
|
|
90
|
+
identities;
|
|
91
|
+
jid;
|
|
92
|
+
lid;
|
|
93
|
+
phoneNumber;
|
|
94
|
+
name;
|
|
95
|
+
constructor(data) {
|
|
96
|
+
const identities = uniqueIdentities([
|
|
97
|
+
data.id,
|
|
98
|
+
data.jid,
|
|
99
|
+
data.lid,
|
|
100
|
+
data.phoneNumber,
|
|
101
|
+
...data.identities ?? []
|
|
102
|
+
]);
|
|
103
|
+
this.jid = data.jid ?? identities.find((identity) => identity.endsWith("@s.whatsapp.net") || identity.endsWith("@c.us"));
|
|
104
|
+
this.lid = data.lid ?? identities.find((identity) => identity.endsWith("@lid"));
|
|
105
|
+
this.phoneNumber = data.phoneNumber ?? this.jid;
|
|
106
|
+
this.name = data.name;
|
|
107
|
+
this.id = this.jid ?? this.phoneNumber ?? this.lid ?? data.id;
|
|
108
|
+
this.identities = identities;
|
|
109
|
+
}
|
|
110
|
+
get mentionId() {
|
|
111
|
+
return this.jid ?? this.phoneNumber ?? this.lid ?? this.id;
|
|
112
|
+
}
|
|
113
|
+
get mention() {
|
|
114
|
+
return `@${this.username}`;
|
|
115
|
+
}
|
|
116
|
+
get phone() {
|
|
117
|
+
return this.phoneNumber ? identityPhoneNumber(this.phoneNumber) : void 0;
|
|
118
|
+
}
|
|
119
|
+
get username() {
|
|
120
|
+
return identityUsername(this.mentionId);
|
|
121
|
+
}
|
|
122
|
+
get displayName() {
|
|
123
|
+
return this.name ?? this.mention;
|
|
124
|
+
}
|
|
125
|
+
matches(identity) {
|
|
126
|
+
const candidates = typeof identity === "string" ? [identity] : identity.identities;
|
|
127
|
+
return this.identities.some((ownIdentity) => candidates.some((candidate) => identitiesMatch(ownIdentity, candidate)));
|
|
128
|
+
}
|
|
129
|
+
toJSON() {
|
|
130
|
+
return {
|
|
131
|
+
id: this.id,
|
|
132
|
+
identities: this.identities,
|
|
133
|
+
...this.jid ? { jid: this.jid } : {},
|
|
134
|
+
...this.lid ? { lid: this.lid } : {},
|
|
135
|
+
...this.phoneNumber ? { phoneNumber: this.phoneNumber } : {},
|
|
136
|
+
...this.name ? { name: this.name } : {}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
static fromIdentities(identities) {
|
|
140
|
+
const [id] = identities;
|
|
141
|
+
if (!id) {
|
|
142
|
+
throw new TypeError("A user requires at least one identity.");
|
|
143
|
+
}
|
|
144
|
+
return new _User({ id, identities });
|
|
145
|
+
}
|
|
146
|
+
static fromPhoneNumber(phoneNumber) {
|
|
147
|
+
const normalized = phoneNumber.replace(/\D/g, "");
|
|
148
|
+
if (normalized.length < 8) {
|
|
149
|
+
throw new TypeError("A user phone number must include country and area codes.");
|
|
150
|
+
}
|
|
151
|
+
return _User.fromIdentities([`${normalized}@s.whatsapp.net`]);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/commands/args-parser.ts
|
|
156
|
+
var ArgsParser = class {
|
|
157
|
+
#values;
|
|
158
|
+
#cursor = 0;
|
|
159
|
+
constructor(values) {
|
|
160
|
+
this.#values = [...values];
|
|
161
|
+
}
|
|
162
|
+
get remaining() {
|
|
163
|
+
return this.#values.length - this.#cursor;
|
|
164
|
+
}
|
|
165
|
+
peek() {
|
|
166
|
+
return this.#values[this.#cursor];
|
|
167
|
+
}
|
|
168
|
+
skip(count = 1) {
|
|
169
|
+
this.#cursor = Math.min(this.#values.length, this.#cursor + Math.max(0, count));
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
string(name = "argument", options = {}) {
|
|
173
|
+
return this.#consume(name, options);
|
|
174
|
+
}
|
|
175
|
+
number(name = "number", options = {}) {
|
|
176
|
+
const raw = this.#consume(name, options);
|
|
177
|
+
if (raw === void 0) {
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
const value = Number(raw);
|
|
181
|
+
if (!Number.isFinite(value)) {
|
|
182
|
+
throw new WhaNextError("ARGUMENT_INVALID", `The argument "${name}" must be a number.`, {
|
|
183
|
+
context: { name, received: raw }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
boolean(name = "boolean", options = {}) {
|
|
189
|
+
const raw = this.#consume(name, options);
|
|
190
|
+
if (raw === void 0) {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
const normalized = raw.toLowerCase();
|
|
194
|
+
const truthy = /* @__PURE__ */ new Set(["true", "on", "yes", "sim", "1"]);
|
|
195
|
+
const falsy = /* @__PURE__ */ new Set(["false", "off", "no", "n\xE3o", "nao", "0"]);
|
|
196
|
+
if (truthy.has(normalized)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (falsy.has(normalized)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
throw new WhaNextError("ARGUMENT_INVALID", `The argument "${name}" must be a boolean.`, {
|
|
203
|
+
context: { name, received: raw }
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
enum(values, name = "option", options = {}) {
|
|
207
|
+
const raw = this.#consume(name, options);
|
|
208
|
+
if (raw === void 0) {
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
if (!values.includes(raw)) {
|
|
212
|
+
throw new WhaNextError(
|
|
213
|
+
"ARGUMENT_INVALID",
|
|
214
|
+
`The argument "${name}" must be one of: ${values.join(", ")}.`,
|
|
215
|
+
{
|
|
216
|
+
context: { name, received: raw, expected: values }
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return raw;
|
|
221
|
+
}
|
|
222
|
+
user(name = "user", options = {}) {
|
|
223
|
+
const raw = this.#consume(name, options);
|
|
224
|
+
if (raw === void 0) {
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
227
|
+
const normalized = raw.replace(/^@/, "").replace(/\D/g, "");
|
|
228
|
+
if (normalized.length < 8) {
|
|
229
|
+
throw new WhaNextError(
|
|
230
|
+
"ARGUMENT_INVALID",
|
|
231
|
+
`The argument "${name}" must be a mention or phone number.`,
|
|
232
|
+
{
|
|
233
|
+
context: { name, received: raw }
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return User.fromPhoneNumber(normalized);
|
|
238
|
+
}
|
|
239
|
+
duration(name = "duration", options = {}) {
|
|
240
|
+
const raw = this.#consume(name, options);
|
|
241
|
+
if (raw === void 0) {
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
const normalized = raw.toLowerCase();
|
|
245
|
+
const permanent = /* @__PURE__ */ new Set([
|
|
246
|
+
"forever",
|
|
247
|
+
"indefinido",
|
|
248
|
+
"permanent",
|
|
249
|
+
"permanente",
|
|
250
|
+
"sempre"
|
|
251
|
+
]);
|
|
252
|
+
if (permanent.has(normalized)) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
const match = /^(\d+)(ms|s|m|h|d)$/.exec(normalized);
|
|
256
|
+
if (!match) {
|
|
257
|
+
throw new WhaNextError(
|
|
258
|
+
"ARGUMENT_INVALID",
|
|
259
|
+
`The argument "${name}" must be a duration such as 30s or 5m.`,
|
|
260
|
+
{
|
|
261
|
+
context: { name, received: raw }
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const amount = Number(match[1]);
|
|
266
|
+
const unit = match[2];
|
|
267
|
+
const multiplier = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 }[unit];
|
|
268
|
+
return amount * multiplier;
|
|
269
|
+
}
|
|
270
|
+
rest() {
|
|
271
|
+
const value = this.#values.slice(this.#cursor).join(" ");
|
|
272
|
+
this.#cursor = this.#values.length;
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
#consume(name, options) {
|
|
276
|
+
const value = this.#values[this.#cursor];
|
|
277
|
+
if (value === void 0) {
|
|
278
|
+
if (options.optional) {
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
throw new WhaNextError("ARGUMENT_MISSING", `The argument "${name}" is required.`, {
|
|
282
|
+
context: { name, position: this.#cursor }
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
this.#cursor += 1;
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// src/commands/router.ts
|
|
291
|
+
var CommandRouter = class {
|
|
292
|
+
#commands = /* @__PURE__ */ new Map();
|
|
293
|
+
#prefix;
|
|
294
|
+
#group;
|
|
295
|
+
#onError;
|
|
296
|
+
constructor(group, options = {}) {
|
|
297
|
+
this.#group = group;
|
|
298
|
+
this.#prefix = options.prefix ?? "!";
|
|
299
|
+
this.#onError = options.onError;
|
|
300
|
+
if (this.#prefix.length === 0 || /\s/.test(this.#prefix)) {
|
|
301
|
+
throw new WhaNextError(
|
|
302
|
+
"ARGUMENT_INVALID",
|
|
303
|
+
"The command prefix cannot be empty or contain whitespace.",
|
|
304
|
+
{
|
|
305
|
+
context: { prefix: this.#prefix }
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
command(definition) {
|
|
311
|
+
const names = [definition.name, ...definition.aliases ?? []];
|
|
312
|
+
for (const name of names) {
|
|
313
|
+
const normalized = name.toLowerCase();
|
|
314
|
+
if (this.#commands.has(normalized)) {
|
|
315
|
+
throw new WhaNextError(
|
|
316
|
+
"ARGUMENT_INVALID",
|
|
317
|
+
`The command "${normalized}" is already registered.`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
this.#commands.set(normalized, definition);
|
|
321
|
+
}
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
async dispatch(message) {
|
|
325
|
+
const text = message.text?.trim();
|
|
326
|
+
if (!text?.startsWith(this.#prefix)) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
const tokens = tokenize(text.slice(this.#prefix.length));
|
|
330
|
+
const name = tokens.shift()?.toLowerCase();
|
|
331
|
+
if (!name) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
const command = this.#commands.get(name);
|
|
335
|
+
if (!command) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
await this.#authorize(command, message);
|
|
340
|
+
await command.execute(message, new ArgsParser(tokens));
|
|
341
|
+
return true;
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const normalized = toWhaNextError(error, { command: command.name, messageId: message.id });
|
|
344
|
+
if (this.#onError) {
|
|
345
|
+
await this.#onError(normalized, message);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
throw normalized;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async #authorize(command, message) {
|
|
352
|
+
if (command.onlyGroup && !message.isGroup) {
|
|
353
|
+
throw new WhaNextError("COMMAND_NOT_ALLOWED", "This command can only be used in groups.");
|
|
354
|
+
}
|
|
355
|
+
if (command.onlyPrivate && message.isGroup) {
|
|
356
|
+
throw new WhaNextError(
|
|
357
|
+
"COMMAND_NOT_ALLOWED",
|
|
358
|
+
"This command can only be used in private chats."
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
if (command.onlyAdmin && !await this.#group.isAdmin(message.chatId, message.senderIds)) {
|
|
362
|
+
throw new WhaNextError(
|
|
363
|
+
"COMMAND_NOT_ALLOWED",
|
|
364
|
+
"This command can only be used by group administrators."
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (command.botMustBeAdmin && !await this.#group.isCurrentUserAdmin(message.chatId)) {
|
|
368
|
+
throw new WhaNextError(
|
|
369
|
+
"BOT_NOT_ADMIN",
|
|
370
|
+
"The connected WhatsApp account must be a group administrator."
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
function tokenize(input) {
|
|
376
|
+
return input.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((token) => token.replace(/^(["'])(.*)\1$/, "$2")) ?? [];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/logger/logger.ts
|
|
380
|
+
var priorities = {
|
|
381
|
+
debug: 10,
|
|
382
|
+
info: 20,
|
|
383
|
+
warn: 30,
|
|
384
|
+
error: 40,
|
|
385
|
+
silent: Number.POSITIVE_INFINITY
|
|
386
|
+
};
|
|
387
|
+
var defaultRedactions = [
|
|
388
|
+
"auth",
|
|
389
|
+
"pairingcode",
|
|
390
|
+
"password",
|
|
391
|
+
"phone",
|
|
392
|
+
"secret",
|
|
393
|
+
"session",
|
|
394
|
+
"token"
|
|
395
|
+
];
|
|
396
|
+
var Logger = class _Logger {
|
|
397
|
+
#state;
|
|
398
|
+
#scope;
|
|
399
|
+
#writer;
|
|
400
|
+
#redactions;
|
|
401
|
+
constructor(config = "info") {
|
|
402
|
+
const options = typeof config === "string" ? { level: config } : config;
|
|
403
|
+
const level = options.level ?? "info";
|
|
404
|
+
assertLevel(level);
|
|
405
|
+
if (options.writer !== void 0 && typeof options.writer !== "function") {
|
|
406
|
+
throw new WhaNextError(
|
|
407
|
+
"ARGUMENT_INVALID",
|
|
408
|
+
"Logger writer must be a function."
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (options.format !== void 0 && !["pretty", "json"].includes(options.format)) {
|
|
412
|
+
throw new WhaNextError(
|
|
413
|
+
"ARGUMENT_INVALID",
|
|
414
|
+
'Logger format must be "pretty" or "json".',
|
|
415
|
+
{ context: { format: options.format } }
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
this.#state = { level };
|
|
419
|
+
this.#scope = options.scope ?? "whanext";
|
|
420
|
+
this.#redactions = new Set(
|
|
421
|
+
[...defaultRedactions, ...options.redact ?? []].map((key) => key.toLowerCase())
|
|
422
|
+
);
|
|
423
|
+
this.#writer = options.writer ?? consoleWriter(options.format ?? "pretty");
|
|
424
|
+
}
|
|
425
|
+
get level() {
|
|
426
|
+
return this.#state.level;
|
|
427
|
+
}
|
|
428
|
+
setLevel(level) {
|
|
429
|
+
assertLevel(level);
|
|
430
|
+
this.#state.level = level;
|
|
431
|
+
return this;
|
|
432
|
+
}
|
|
433
|
+
isEnabled(level) {
|
|
434
|
+
return priorities[level] >= priorities[this.#state.level];
|
|
435
|
+
}
|
|
436
|
+
child(scope) {
|
|
437
|
+
const childScope = this.#scope ? `${this.#scope}:${scope}` : scope;
|
|
438
|
+
const child = new _Logger({
|
|
439
|
+
level: this.#state.level,
|
|
440
|
+
scope: childScope,
|
|
441
|
+
writer: this.#writer,
|
|
442
|
+
redact: [...this.#redactions]
|
|
443
|
+
});
|
|
444
|
+
child.#state = this.#state;
|
|
445
|
+
return child;
|
|
446
|
+
}
|
|
447
|
+
debug(message, context = {}) {
|
|
448
|
+
this.#write("debug", message, context);
|
|
449
|
+
}
|
|
450
|
+
info(message, context = {}) {
|
|
451
|
+
this.#write("info", message, context);
|
|
452
|
+
}
|
|
453
|
+
warn(message, context = {}) {
|
|
454
|
+
this.#write("warn", message, context);
|
|
455
|
+
}
|
|
456
|
+
error(message, context = {}) {
|
|
457
|
+
this.#write("error", message, context);
|
|
458
|
+
}
|
|
459
|
+
#write(level, message, context) {
|
|
460
|
+
if (!this.isEnabled(level)) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
const entry = {
|
|
465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
466
|
+
level,
|
|
467
|
+
scope: this.#scope,
|
|
468
|
+
message,
|
|
469
|
+
context: normalizeContext(context, this.#redactions)
|
|
470
|
+
};
|
|
471
|
+
const result = this.#writer(entry);
|
|
472
|
+
if (result instanceof Promise) {
|
|
473
|
+
void result.catch(() => void 0);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
function assertLevel(level) {
|
|
481
|
+
if (!(level in priorities)) {
|
|
482
|
+
throw new WhaNextError(
|
|
483
|
+
"ARGUMENT_INVALID",
|
|
484
|
+
"Logger level must be debug, info, warn, error or silent.",
|
|
485
|
+
{ context: { level } }
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function consoleWriter(format) {
|
|
490
|
+
return (entry) => {
|
|
491
|
+
const method = entry.level === "debug" ? console.debug : entry.level === "info" ? console.info : entry.level === "warn" ? console.warn : console.error;
|
|
492
|
+
if (format === "json") {
|
|
493
|
+
method(JSON.stringify(entry));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const details = Object.keys(entry.context).length > 0 ? ` ${JSON.stringify(entry.context)}` : "";
|
|
497
|
+
method(
|
|
498
|
+
`[${entry.timestamp}] ${entry.level.toUpperCase()} ${entry.scope} ${entry.message}${details}`
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function normalizeContext(context, redactions) {
|
|
503
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
504
|
+
return Object.fromEntries(
|
|
505
|
+
Object.entries(context).map(([key, value]) => [
|
|
506
|
+
key,
|
|
507
|
+
normalizeValue(value, key, redactions, seen)
|
|
508
|
+
])
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
function normalizeValue(value, key, redactions, seen) {
|
|
512
|
+
if (isRedactedKey(key, redactions)) {
|
|
513
|
+
return "[REDACTED]";
|
|
514
|
+
}
|
|
515
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === void 0) {
|
|
516
|
+
return value;
|
|
517
|
+
}
|
|
518
|
+
if (typeof value === "bigint") {
|
|
519
|
+
return value.toString();
|
|
520
|
+
}
|
|
521
|
+
if (value instanceof Date) {
|
|
522
|
+
return value.toISOString();
|
|
523
|
+
}
|
|
524
|
+
if (value instanceof Error) {
|
|
525
|
+
return {
|
|
526
|
+
name: value.name,
|
|
527
|
+
message: value.message,
|
|
528
|
+
..."code" in value ? { code: value.code } : {}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (typeof value !== "object") {
|
|
532
|
+
return String(value);
|
|
533
|
+
}
|
|
534
|
+
if (seen.has(value)) {
|
|
535
|
+
return "[Circular]";
|
|
536
|
+
}
|
|
537
|
+
seen.add(value);
|
|
538
|
+
if (Array.isArray(value)) {
|
|
539
|
+
return value.map((item) => normalizeValue(item, "", redactions, seen));
|
|
540
|
+
}
|
|
541
|
+
return Object.fromEntries(
|
|
542
|
+
Object.entries(value).map(([nestedKey, nestedValue]) => [
|
|
543
|
+
nestedKey,
|
|
544
|
+
normalizeValue(nestedValue, nestedKey, redactions, seen)
|
|
545
|
+
])
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
function isRedactedKey(key, redactions) {
|
|
549
|
+
const normalized = key.toLowerCase();
|
|
550
|
+
return [...redactions].some((redaction) => normalized === redaction || normalized.startsWith(redaction) || normalized.endsWith(redaction));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/mute/mute-service.ts
|
|
554
|
+
var MuteService = class {
|
|
555
|
+
#provider;
|
|
556
|
+
#store;
|
|
557
|
+
constructor(provider, store) {
|
|
558
|
+
this.#provider = provider;
|
|
559
|
+
this.#store = store;
|
|
560
|
+
}
|
|
561
|
+
get enabled() {
|
|
562
|
+
return this.#store !== void 0;
|
|
563
|
+
}
|
|
564
|
+
async add(groupId, user, options = {}) {
|
|
565
|
+
const store = this.#requireStore();
|
|
566
|
+
const now = Date.now();
|
|
567
|
+
if (options.durationMs !== void 0 && options.durationMs <= 0) {
|
|
568
|
+
throw new WhaNextError("ARGUMENT_INVALID", "Mute duration must be greater than zero.");
|
|
569
|
+
}
|
|
570
|
+
const current = await this.#findActive(groupId, user.identities);
|
|
571
|
+
if (current && current.expiresAt === null && options.durationMs === void 0) {
|
|
572
|
+
return {
|
|
573
|
+
ok: true,
|
|
574
|
+
changed: false,
|
|
575
|
+
state: "already_muted",
|
|
576
|
+
record: this.#record(current)
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const stored = {
|
|
580
|
+
key: normalizeIdentity(user.id),
|
|
581
|
+
groupId,
|
|
582
|
+
user: user.toJSON(),
|
|
583
|
+
identities: user.identities,
|
|
584
|
+
createdAt: now,
|
|
585
|
+
expiresAt: options.durationMs === void 0 ? null : now + options.durationMs
|
|
586
|
+
};
|
|
587
|
+
await this.#storage(
|
|
588
|
+
"upsert",
|
|
589
|
+
() => store.upsert(stored),
|
|
590
|
+
{ groupId, userId: user.id }
|
|
591
|
+
);
|
|
592
|
+
return {
|
|
593
|
+
ok: true,
|
|
594
|
+
changed: true,
|
|
595
|
+
state: current ? "updated" : "muted",
|
|
596
|
+
record: this.#record(stored)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async remove(groupId, user) {
|
|
600
|
+
const store = this.#requireStore();
|
|
601
|
+
const changed = await this.#storage(
|
|
602
|
+
"delete",
|
|
603
|
+
() => store.delete(groupId, user.identities),
|
|
604
|
+
{ groupId, userId: user.id }
|
|
605
|
+
);
|
|
606
|
+
if (changed) {
|
|
607
|
+
return {
|
|
608
|
+
ok: true,
|
|
609
|
+
changed: true,
|
|
610
|
+
state: "unmuted"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
ok: true,
|
|
615
|
+
changed: false,
|
|
616
|
+
state: "already_unmuted"
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
async get(groupId, user) {
|
|
620
|
+
this.#requireStore();
|
|
621
|
+
const stored = await this.#findActive(groupId, user.identities);
|
|
622
|
+
return stored ? this.#record(stored) : void 0;
|
|
623
|
+
}
|
|
624
|
+
async isMuted(groupId, user) {
|
|
625
|
+
return await this.get(groupId, user) !== void 0;
|
|
626
|
+
}
|
|
627
|
+
async enforce(message) {
|
|
628
|
+
if (!this.#store || !message.isGroup || message.keys.fromMe) {
|
|
629
|
+
return void 0;
|
|
630
|
+
}
|
|
631
|
+
const stored = await this.#findActive(message.chatId, message.sender.identities);
|
|
632
|
+
if (!stored) {
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
await this.#provider.deleteMessage(message.keys);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
throw new WhaNextError(
|
|
639
|
+
"PROVIDER_ERROR",
|
|
640
|
+
"Could not delete a message sent by a muted member.",
|
|
641
|
+
{
|
|
642
|
+
cause: error,
|
|
643
|
+
context: {
|
|
644
|
+
groupId: message.chatId,
|
|
645
|
+
messageId: message.id,
|
|
646
|
+
userId: message.sender.id
|
|
647
|
+
},
|
|
648
|
+
recoverable: true
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
message,
|
|
654
|
+
record: this.#record(stored)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
async purgeExpired() {
|
|
658
|
+
const store = this.#requireStore();
|
|
659
|
+
return this.#storage(
|
|
660
|
+
"purge",
|
|
661
|
+
() => store.purgeExpired(Date.now())
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
async close() {
|
|
665
|
+
if (this.#store?.close) {
|
|
666
|
+
await this.#storage("close", () => this.#store?.close?.());
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
#record(stored) {
|
|
670
|
+
return {
|
|
671
|
+
groupId: stored.groupId,
|
|
672
|
+
user: new User(stored.user),
|
|
673
|
+
createdAt: new Date(stored.createdAt),
|
|
674
|
+
expiresAt: stored.expiresAt === null ? null : new Date(stored.expiresAt)
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
async #findActive(groupId, identities) {
|
|
678
|
+
const stored = await this.#storage(
|
|
679
|
+
"find",
|
|
680
|
+
() => this.#store?.find(groupId, identities),
|
|
681
|
+
{ groupId }
|
|
682
|
+
);
|
|
683
|
+
if (!stored) {
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
if (stored.expiresAt !== null && stored.expiresAt <= Date.now()) {
|
|
687
|
+
await this.#storage(
|
|
688
|
+
"delete_expired",
|
|
689
|
+
() => this.#store?.delete(groupId, stored.identities),
|
|
690
|
+
{ groupId }
|
|
691
|
+
);
|
|
692
|
+
return void 0;
|
|
693
|
+
}
|
|
694
|
+
return stored;
|
|
695
|
+
}
|
|
696
|
+
#requireStore() {
|
|
697
|
+
if (!this.#store) {
|
|
698
|
+
throw new WhaNextError(
|
|
699
|
+
"MUTE_DISABLED",
|
|
700
|
+
"Mute is disabled. Enable it in create({ mute: { enabled: true } })."
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
return this.#store;
|
|
704
|
+
}
|
|
705
|
+
async #storage(action, operation, context = {}) {
|
|
706
|
+
try {
|
|
707
|
+
return await operation();
|
|
708
|
+
} catch (error) {
|
|
709
|
+
throw new WhaNextError(
|
|
710
|
+
"STORAGE_ERROR",
|
|
711
|
+
`Mute storage operation "${action}" failed.`,
|
|
712
|
+
{
|
|
713
|
+
cause: error,
|
|
714
|
+
context: { action, ...context },
|
|
715
|
+
recoverable: true
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/mute/sqlite-mute-store.ts
|
|
723
|
+
import { mkdirSync } from "fs";
|
|
724
|
+
import { createRequire } from "module";
|
|
725
|
+
import {
|
|
726
|
+
dirname,
|
|
727
|
+
resolve
|
|
728
|
+
} from "path";
|
|
729
|
+
var SqliteMuteStore = class {
|
|
730
|
+
#database;
|
|
731
|
+
constructor(path = "./data/whanext.sqlite") {
|
|
732
|
+
const databasePath = path === ":memory:" ? path : resolve(path);
|
|
733
|
+
if (databasePath !== ":memory:") {
|
|
734
|
+
mkdirSync(dirname(databasePath), { recursive: true });
|
|
735
|
+
}
|
|
736
|
+
let database;
|
|
737
|
+
try {
|
|
738
|
+
const require2 = createRequire(import.meta.url);
|
|
739
|
+
const sqlite = require2("node:sqlite");
|
|
740
|
+
database = new sqlite.DatabaseSync(databasePath);
|
|
741
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
742
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
743
|
+
database.exec(`
|
|
744
|
+
CREATE TABLE IF NOT EXISTS whanext_mutes (
|
|
745
|
+
group_id TEXT NOT NULL,
|
|
746
|
+
identity TEXT NOT NULL,
|
|
747
|
+
mute_key TEXT NOT NULL,
|
|
748
|
+
user_json TEXT NOT NULL,
|
|
749
|
+
created_at INTEGER NOT NULL,
|
|
750
|
+
expires_at INTEGER,
|
|
751
|
+
PRIMARY KEY (group_id, identity)
|
|
752
|
+
) STRICT;
|
|
753
|
+
CREATE INDEX IF NOT EXISTS whanext_mutes_expiration
|
|
754
|
+
ON whanext_mutes (expires_at);
|
|
755
|
+
CREATE INDEX IF NOT EXISTS whanext_mutes_key
|
|
756
|
+
ON whanext_mutes (group_id, mute_key);
|
|
757
|
+
`);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
database?.close();
|
|
760
|
+
throw new WhaNextError(
|
|
761
|
+
"STORAGE_ERROR",
|
|
762
|
+
"Could not initialize the SQLite mute store.",
|
|
763
|
+
{
|
|
764
|
+
cause: error,
|
|
765
|
+
context: { database: databasePath }
|
|
766
|
+
}
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
this.#database = database;
|
|
770
|
+
}
|
|
771
|
+
upsert(mute) {
|
|
772
|
+
const identities = this.#identities(mute.identities);
|
|
773
|
+
const existingKeys = this.#matchingKeys(mute.groupId, identities);
|
|
774
|
+
this.#transaction(() => {
|
|
775
|
+
for (const key of existingKeys) {
|
|
776
|
+
this.#database.prepare(
|
|
777
|
+
"DELETE FROM whanext_mutes WHERE group_id = ? AND mute_key = ?"
|
|
778
|
+
).run(mute.groupId, key);
|
|
779
|
+
}
|
|
780
|
+
const insert = this.#database.prepare(`
|
|
781
|
+
INSERT OR REPLACE INTO whanext_mutes (
|
|
782
|
+
group_id,
|
|
783
|
+
identity,
|
|
784
|
+
mute_key,
|
|
785
|
+
user_json,
|
|
786
|
+
created_at,
|
|
787
|
+
expires_at
|
|
788
|
+
) VALUES (?, ?, ?, ?, ?, ?)
|
|
789
|
+
`);
|
|
790
|
+
for (const identity of identities) {
|
|
791
|
+
insert.run(
|
|
792
|
+
mute.groupId,
|
|
793
|
+
identity,
|
|
794
|
+
mute.key,
|
|
795
|
+
JSON.stringify(mute.user),
|
|
796
|
+
mute.createdAt,
|
|
797
|
+
mute.expiresAt
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
find(groupId, identities) {
|
|
803
|
+
const normalized = this.#identities(identities);
|
|
804
|
+
if (normalized.length === 0) {
|
|
805
|
+
return void 0;
|
|
806
|
+
}
|
|
807
|
+
const placeholders = normalized.map(() => "?").join(", ");
|
|
808
|
+
const row = this.#database.prepare(`
|
|
809
|
+
SELECT mute_key, group_id, user_json, created_at, expires_at
|
|
810
|
+
FROM whanext_mutes
|
|
811
|
+
WHERE group_id = ? AND identity IN (${placeholders})
|
|
812
|
+
LIMIT 1
|
|
813
|
+
`).get(groupId, ...normalized);
|
|
814
|
+
if (!row) {
|
|
815
|
+
return void 0;
|
|
816
|
+
}
|
|
817
|
+
if (row.expires_at !== null && row.expires_at <= Date.now()) {
|
|
818
|
+
this.#database.prepare(
|
|
819
|
+
"DELETE FROM whanext_mutes WHERE group_id = ? AND mute_key = ?"
|
|
820
|
+
).run(groupId, row.mute_key);
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
const identityRows = this.#database.prepare(`
|
|
824
|
+
SELECT identity
|
|
825
|
+
FROM whanext_mutes
|
|
826
|
+
WHERE group_id = ? AND mute_key = ?
|
|
827
|
+
`).all(groupId, row.mute_key);
|
|
828
|
+
return {
|
|
829
|
+
key: row.mute_key,
|
|
830
|
+
groupId: row.group_id,
|
|
831
|
+
user: JSON.parse(row.user_json),
|
|
832
|
+
identities: identityRows.map(({ identity }) => identity),
|
|
833
|
+
createdAt: row.created_at,
|
|
834
|
+
expiresAt: row.expires_at
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
delete(groupId, identities) {
|
|
838
|
+
const keys = this.#matchingKeys(groupId, this.#identities(identities));
|
|
839
|
+
if (keys.length === 0) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
this.#transaction(() => {
|
|
843
|
+
const statement = this.#database.prepare(
|
|
844
|
+
"DELETE FROM whanext_mutes WHERE group_id = ? AND mute_key = ?"
|
|
845
|
+
);
|
|
846
|
+
for (const key of keys) {
|
|
847
|
+
statement.run(groupId, key);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
purgeExpired(now) {
|
|
853
|
+
const result = this.#database.prepare(`
|
|
854
|
+
DELETE FROM whanext_mutes
|
|
855
|
+
WHERE expires_at IS NOT NULL AND expires_at <= ?
|
|
856
|
+
`).run(now);
|
|
857
|
+
return Number(result.changes);
|
|
858
|
+
}
|
|
859
|
+
close() {
|
|
860
|
+
this.#database.close();
|
|
861
|
+
}
|
|
862
|
+
#matchingKeys(groupId, identities) {
|
|
863
|
+
if (identities.length === 0) {
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
const placeholders = identities.map(() => "?").join(", ");
|
|
867
|
+
const rows = this.#database.prepare(`
|
|
868
|
+
SELECT DISTINCT mute_key
|
|
869
|
+
FROM whanext_mutes
|
|
870
|
+
WHERE group_id = ? AND identity IN (${placeholders})
|
|
871
|
+
`).all(groupId, ...identities);
|
|
872
|
+
return rows.map(({ mute_key }) => mute_key);
|
|
873
|
+
}
|
|
874
|
+
#identities(identities) {
|
|
875
|
+
return [...new Set(identities.map(normalizeIdentity).filter(Boolean))];
|
|
876
|
+
}
|
|
877
|
+
#transaction(operation) {
|
|
878
|
+
this.#database.exec("BEGIN IMMEDIATE");
|
|
879
|
+
try {
|
|
880
|
+
operation();
|
|
881
|
+
this.#database.exec("COMMIT");
|
|
882
|
+
} catch (error) {
|
|
883
|
+
this.#database.exec("ROLLBACK");
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/provider/event-emitter.ts
|
|
890
|
+
var TypedEventEmitter = class {
|
|
891
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
892
|
+
on(event, listener) {
|
|
893
|
+
const listeners = this.#listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
894
|
+
listeners.add(listener);
|
|
895
|
+
this.#listeners.set(event, listeners);
|
|
896
|
+
return () => listeners.delete(listener);
|
|
897
|
+
}
|
|
898
|
+
async emit(event, payload) {
|
|
899
|
+
const listeners = this.#listeners.get(event);
|
|
900
|
+
if (!listeners) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
await Promise.all([...listeners].map((listener) => listener(payload)));
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// src/services/chat-service.ts
|
|
908
|
+
var ChatService = class {
|
|
909
|
+
#provider;
|
|
910
|
+
constructor(provider) {
|
|
911
|
+
this.#provider = provider;
|
|
912
|
+
}
|
|
913
|
+
typing(chatId) {
|
|
914
|
+
return this.#provider.setPresence(chatId, "typing");
|
|
915
|
+
}
|
|
916
|
+
recording(chatId) {
|
|
917
|
+
return this.#provider.setPresence(chatId, "recording");
|
|
918
|
+
}
|
|
919
|
+
stop(chatId) {
|
|
920
|
+
return this.#provider.setPresence(chatId, "paused");
|
|
921
|
+
}
|
|
922
|
+
stopTyping(chatId) {
|
|
923
|
+
return this.stop(chatId);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// src/services/group-service.ts
|
|
928
|
+
var GroupService = class {
|
|
929
|
+
#provider;
|
|
930
|
+
#cache;
|
|
931
|
+
#ttlMs;
|
|
932
|
+
constructor(provider, cache, ttlMs = 3e5) {
|
|
933
|
+
this.#provider = provider;
|
|
934
|
+
this.#cache = cache;
|
|
935
|
+
this.#ttlMs = ttlMs;
|
|
936
|
+
}
|
|
937
|
+
async open(groupId) {
|
|
938
|
+
const group = await this.metadata(groupId);
|
|
939
|
+
if (group.access === "open") {
|
|
940
|
+
return { ok: true, changed: false, state: "already_open" };
|
|
941
|
+
}
|
|
942
|
+
await this.#provider.setGroupAccess(groupId, "open");
|
|
943
|
+
await this.invalidate(groupId);
|
|
944
|
+
return { ok: true, changed: true, state: "open" };
|
|
945
|
+
}
|
|
946
|
+
async close(groupId) {
|
|
947
|
+
const group = await this.metadata(groupId);
|
|
948
|
+
if (group.access === "closed") {
|
|
949
|
+
return { ok: true, changed: false, state: "already_closed" };
|
|
950
|
+
}
|
|
951
|
+
await this.#provider.setGroupAccess(groupId, "closed");
|
|
952
|
+
await this.invalidate(groupId);
|
|
953
|
+
return { ok: true, changed: true, state: "closed" };
|
|
954
|
+
}
|
|
955
|
+
async invite(groupId) {
|
|
956
|
+
const code = await this.#provider.getGroupInviteCode(groupId);
|
|
957
|
+
return { ok: true, code, url: `https://chat.whatsapp.com/${code}` };
|
|
958
|
+
}
|
|
959
|
+
async revokeInvite(groupId) {
|
|
960
|
+
const code = await this.#provider.revokeGroupInvite(groupId);
|
|
961
|
+
return { ok: true, code, url: `https://chat.whatsapp.com/${code}` };
|
|
962
|
+
}
|
|
963
|
+
async pin(groupId, key) {
|
|
964
|
+
await this.#provider.setMessagePin(groupId, key, true);
|
|
965
|
+
return { ok: true, changed: true, state: "pinned" };
|
|
966
|
+
}
|
|
967
|
+
async unpin(groupId, key) {
|
|
968
|
+
await this.#provider.setMessagePin(groupId, key, false);
|
|
969
|
+
return { ok: true, changed: true, state: "unpinned" };
|
|
970
|
+
}
|
|
971
|
+
async metadata(groupId, refresh = false) {
|
|
972
|
+
const key = this.#key(groupId);
|
|
973
|
+
if (!refresh) {
|
|
974
|
+
const cached = await this.#cache.get(key);
|
|
975
|
+
if (cached) {
|
|
976
|
+
return cached;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const group = await this.#provider.getGroup(groupId);
|
|
980
|
+
await this.#cache.set(key, group, this.#ttlMs);
|
|
981
|
+
return group;
|
|
982
|
+
}
|
|
983
|
+
async isAdmin(groupId, memberIds) {
|
|
984
|
+
if (!groupId.endsWith("@g.us")) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
const group = await this.metadata(groupId);
|
|
988
|
+
const identities = typeof memberIds === "string" ? [memberIds] : memberIds;
|
|
989
|
+
const participant = group.participants.find((item) => this.#matchesParticipant(item, identities));
|
|
990
|
+
return participant?.role === "admin" || participant?.role === "owner";
|
|
991
|
+
}
|
|
992
|
+
async isCurrentUserAdmin(groupId) {
|
|
993
|
+
const ids = this.#provider.getCurrentUserIds();
|
|
994
|
+
if (ids.length === 0 || !groupId.endsWith("@g.us")) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
const group = await this.metadata(groupId);
|
|
998
|
+
return group.participants.some((participant) => this.#matchesParticipant(participant, ids) && (participant.role === "admin" || participant.role === "owner"));
|
|
999
|
+
}
|
|
1000
|
+
async resolveUser(groupId, user) {
|
|
1001
|
+
if (!groupId.endsWith("@g.us")) {
|
|
1002
|
+
return user;
|
|
1003
|
+
}
|
|
1004
|
+
const group = await this.metadata(groupId);
|
|
1005
|
+
const participant = group.participants.find((item) => this.#matchesParticipant(item, user.identities));
|
|
1006
|
+
if (!participant) {
|
|
1007
|
+
return user;
|
|
1008
|
+
}
|
|
1009
|
+
return new User({
|
|
1010
|
+
id: participant.id,
|
|
1011
|
+
identities: [
|
|
1012
|
+
participant.id,
|
|
1013
|
+
...participant.lid ? [participant.lid] : [],
|
|
1014
|
+
...participant.phoneNumber ? [participant.phoneNumber] : []
|
|
1015
|
+
],
|
|
1016
|
+
...participant.lid ? { lid: participant.lid } : {},
|
|
1017
|
+
...participant.phoneNumber ? {
|
|
1018
|
+
jid: participant.phoneNumber,
|
|
1019
|
+
phoneNumber: participant.phoneNumber
|
|
1020
|
+
} : {},
|
|
1021
|
+
...user.name ? { name: user.name } : {}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
invalidate(groupId) {
|
|
1025
|
+
return this.#cache.delete(this.#key(groupId));
|
|
1026
|
+
}
|
|
1027
|
+
#key(groupId) {
|
|
1028
|
+
return `group:${groupId}`;
|
|
1029
|
+
}
|
|
1030
|
+
#matchesParticipant(participant, identities) {
|
|
1031
|
+
const participantIds = [participant.id, participant.lid, participant.phoneNumber].filter((identity) => identity !== void 0);
|
|
1032
|
+
return identities.some((identity) => participantIds.some((participantId) => identitiesMatch(identity, participantId)));
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// src/services/media-service.ts
|
|
1037
|
+
var MediaService = class {
|
|
1038
|
+
#provider;
|
|
1039
|
+
constructor(provider) {
|
|
1040
|
+
this.#provider = provider;
|
|
1041
|
+
}
|
|
1042
|
+
image(chatId, content) {
|
|
1043
|
+
return this.#provider.sendMessage(chatId, content);
|
|
1044
|
+
}
|
|
1045
|
+
video(chatId, content) {
|
|
1046
|
+
return this.#provider.sendMessage(chatId, content);
|
|
1047
|
+
}
|
|
1048
|
+
audio(chatId, content) {
|
|
1049
|
+
return this.#provider.sendMessage(chatId, content);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
// src/services/member-service.ts
|
|
1054
|
+
var MemberService = class {
|
|
1055
|
+
#provider;
|
|
1056
|
+
#group;
|
|
1057
|
+
constructor(provider, group) {
|
|
1058
|
+
this.#provider = provider;
|
|
1059
|
+
this.#group = group;
|
|
1060
|
+
}
|
|
1061
|
+
async remove(groupId, member) {
|
|
1062
|
+
const { group, participant } = await this.#participant(groupId, member);
|
|
1063
|
+
if (!participant) {
|
|
1064
|
+
return { ok: true, changed: false, state: "already_removed" };
|
|
1065
|
+
}
|
|
1066
|
+
await this.#update(groupId, this.#actionId(group, participant), "remove");
|
|
1067
|
+
await this.#group.invalidate(groupId);
|
|
1068
|
+
return { ok: true, changed: true, state: "removed" };
|
|
1069
|
+
}
|
|
1070
|
+
async promote(groupId, member) {
|
|
1071
|
+
const { group, participant } = await this.#participant(groupId, member);
|
|
1072
|
+
if (!participant) {
|
|
1073
|
+
return { ok: true, changed: false, state: "not_in_group" };
|
|
1074
|
+
}
|
|
1075
|
+
if (participant.role === "admin" || participant.role === "owner") {
|
|
1076
|
+
return { ok: true, changed: false, state: "already_admin" };
|
|
1077
|
+
}
|
|
1078
|
+
await this.#update(groupId, this.#actionId(group, participant), "promote");
|
|
1079
|
+
await this.#group.invalidate(groupId);
|
|
1080
|
+
return { ok: true, changed: true, state: "promoted" };
|
|
1081
|
+
}
|
|
1082
|
+
async demote(groupId, member) {
|
|
1083
|
+
const { group, participant } = await this.#participant(groupId, member);
|
|
1084
|
+
if (!participant) {
|
|
1085
|
+
return { ok: true, changed: false, state: "not_in_group" };
|
|
1086
|
+
}
|
|
1087
|
+
if (participant.role === "member") {
|
|
1088
|
+
return { ok: true, changed: false, state: "not_admin" };
|
|
1089
|
+
}
|
|
1090
|
+
await this.#update(groupId, this.#actionId(group, participant), "demote");
|
|
1091
|
+
await this.#group.invalidate(groupId);
|
|
1092
|
+
return { ok: true, changed: true, state: "demoted" };
|
|
1093
|
+
}
|
|
1094
|
+
async #participant(groupId, member) {
|
|
1095
|
+
const group = await this.#group.metadata(groupId);
|
|
1096
|
+
const memberIds = typeof member === "string" ? [member] : member.identities;
|
|
1097
|
+
const participant = group.participants.find((item) => [item.id, item.lid, item.phoneNumber].filter((identity) => identity !== void 0).some((identity) => memberIds.some((memberId) => identitiesMatch(identity, memberId))));
|
|
1098
|
+
return { group, participant };
|
|
1099
|
+
}
|
|
1100
|
+
#actionId(group, participant) {
|
|
1101
|
+
if (group.addressingMode === "lid") {
|
|
1102
|
+
return participant.lid ?? (participant.id.endsWith("@lid") ? participant.id : participant.phoneNumber ?? participant.id);
|
|
1103
|
+
}
|
|
1104
|
+
return participant.phoneNumber ?? (participant.id.endsWith("@s.whatsapp.net") ? participant.id : participant.lid ?? participant.id);
|
|
1105
|
+
}
|
|
1106
|
+
async #update(groupId, memberId, action) {
|
|
1107
|
+
const result = await this.#provider.updateParticipant(groupId, memberId, action);
|
|
1108
|
+
if (!result.success) {
|
|
1109
|
+
throw new WhaNextError(
|
|
1110
|
+
"PROVIDER_ERROR",
|
|
1111
|
+
`WhatsApp rejected the member action with status ${result.status}.`,
|
|
1112
|
+
{
|
|
1113
|
+
context: { groupId, memberId, action, status: result.status },
|
|
1114
|
+
recoverable: true
|
|
1115
|
+
}
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// src/services/message-service.ts
|
|
1122
|
+
var MessageService = class {
|
|
1123
|
+
#provider;
|
|
1124
|
+
constructor(provider) {
|
|
1125
|
+
this.#provider = provider;
|
|
1126
|
+
}
|
|
1127
|
+
send(chatId, content) {
|
|
1128
|
+
return this.#provider.sendMessage(chatId, content);
|
|
1129
|
+
}
|
|
1130
|
+
reply(message, content) {
|
|
1131
|
+
return this.#provider.sendMessage(message.chatId, content, message.keys);
|
|
1132
|
+
}
|
|
1133
|
+
edit(message, text) {
|
|
1134
|
+
const key = "keys" in message ? message.keys : message;
|
|
1135
|
+
return this.#provider.editMessage(key, text);
|
|
1136
|
+
}
|
|
1137
|
+
delete(message) {
|
|
1138
|
+
const key = "keys" in message ? message.keys : message;
|
|
1139
|
+
return this.#provider.deleteMessage(key);
|
|
1140
|
+
}
|
|
1141
|
+
text(chatId, text, mentions) {
|
|
1142
|
+
const content = { text };
|
|
1143
|
+
if (mentions !== void 0) {
|
|
1144
|
+
content.mentions = mentions;
|
|
1145
|
+
}
|
|
1146
|
+
return this.send(chatId, content);
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/services/user-service.ts
|
|
1151
|
+
var UserService = class {
|
|
1152
|
+
#group;
|
|
1153
|
+
constructor(group) {
|
|
1154
|
+
this.#group = group;
|
|
1155
|
+
}
|
|
1156
|
+
async resolve(message, args) {
|
|
1157
|
+
const mentioned = message.mentionedUsers[0];
|
|
1158
|
+
let user;
|
|
1159
|
+
if (mentioned) {
|
|
1160
|
+
if (args.peek()?.startsWith("@")) {
|
|
1161
|
+
args.skip();
|
|
1162
|
+
}
|
|
1163
|
+
user = mentioned;
|
|
1164
|
+
} else if (message.quoted?.sender) {
|
|
1165
|
+
user = message.quoted.sender;
|
|
1166
|
+
} else {
|
|
1167
|
+
user = args.user("membro");
|
|
1168
|
+
}
|
|
1169
|
+
return this.#group.resolveUser(message.chatId, user);
|
|
1170
|
+
}
|
|
1171
|
+
from(identity) {
|
|
1172
|
+
if (!identity.includes("@")) {
|
|
1173
|
+
return User.fromPhoneNumber(identity);
|
|
1174
|
+
}
|
|
1175
|
+
return User.fromIdentities([identity]);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// src/app/whanext-app.ts
|
|
1180
|
+
var WhaNextApp = class {
|
|
1181
|
+
message;
|
|
1182
|
+
media;
|
|
1183
|
+
group;
|
|
1184
|
+
member;
|
|
1185
|
+
chat;
|
|
1186
|
+
user;
|
|
1187
|
+
mute;
|
|
1188
|
+
logger;
|
|
1189
|
+
#provider;
|
|
1190
|
+
#phone;
|
|
1191
|
+
#events = new TypedEventEmitter();
|
|
1192
|
+
#router;
|
|
1193
|
+
#startedAt = Date.now();
|
|
1194
|
+
#state = "idle";
|
|
1195
|
+
constructor(provider, options = {}, logger = new Logger(options.logger)) {
|
|
1196
|
+
this.#provider = provider;
|
|
1197
|
+
this.#phone = options.phone;
|
|
1198
|
+
this.logger = logger;
|
|
1199
|
+
const cache = options.cache?.store ?? new MemoryCache();
|
|
1200
|
+
this.group = new GroupService(provider, cache, options.cache?.groupTtlMs);
|
|
1201
|
+
this.member = new MemberService(provider, this.group);
|
|
1202
|
+
this.message = new MessageService(provider);
|
|
1203
|
+
this.media = new MediaService(provider);
|
|
1204
|
+
this.chat = new ChatService(provider);
|
|
1205
|
+
this.user = new UserService(this.group);
|
|
1206
|
+
const muteEnabled = options.mute?.enabled === true || options.mute?.store !== void 0;
|
|
1207
|
+
const muteStore = muteEnabled ? options.mute?.store ?? new SqliteMuteStore(options.mute?.database) : void 0;
|
|
1208
|
+
this.mute = new MuteService(provider, muteStore);
|
|
1209
|
+
this.#router = new CommandRouter(this.group, {
|
|
1210
|
+
...options.router,
|
|
1211
|
+
...options.prefix !== void 0 ? { prefix: options.prefix } : {}
|
|
1212
|
+
});
|
|
1213
|
+
this.#bind();
|
|
1214
|
+
this.logger.debug("Application initialized", {
|
|
1215
|
+
muteEnabled: this.mute.enabled,
|
|
1216
|
+
prefix: options.prefix ?? "!"
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
get state() {
|
|
1220
|
+
return this.#state;
|
|
1221
|
+
}
|
|
1222
|
+
get isReady() {
|
|
1223
|
+
return this.#state === "connected";
|
|
1224
|
+
}
|
|
1225
|
+
health() {
|
|
1226
|
+
return {
|
|
1227
|
+
status: this.#healthStatus(),
|
|
1228
|
+
state: this.#state,
|
|
1229
|
+
ready: this.isReady,
|
|
1230
|
+
uptimeMs: Date.now() - this.#startedAt,
|
|
1231
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1232
|
+
muteEnabled: this.mute.enabled,
|
|
1233
|
+
logLevel: this.logger.level
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
router() {
|
|
1237
|
+
return this.#router;
|
|
1238
|
+
}
|
|
1239
|
+
on(event, listener) {
|
|
1240
|
+
return this.#events.on(event, listener);
|
|
1241
|
+
}
|
|
1242
|
+
async login(options = {}) {
|
|
1243
|
+
if (this.#state === "connected") {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
this.logger.info("Login started");
|
|
1247
|
+
let unsubscribe = () => void 0;
|
|
1248
|
+
let timer;
|
|
1249
|
+
const connected = new Promise((resolve2, reject) => {
|
|
1250
|
+
unsubscribe = this.#provider.on("connection", (update) => {
|
|
1251
|
+
if (update.state === "connected") resolve2();
|
|
1252
|
+
if (update.state === "closed") {
|
|
1253
|
+
reject(
|
|
1254
|
+
new WhaNextError(
|
|
1255
|
+
"CONNECTION_FAILED",
|
|
1256
|
+
"WhatsApp closed the connection before login completed.",
|
|
1257
|
+
{
|
|
1258
|
+
cause: update.error,
|
|
1259
|
+
recoverable: true
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
timer = setTimeout(() => {
|
|
1266
|
+
reject(
|
|
1267
|
+
new WhaNextError(
|
|
1268
|
+
"CONNECTION_FAILED",
|
|
1269
|
+
"WhatsApp login timed out.",
|
|
1270
|
+
{ recoverable: true }
|
|
1271
|
+
)
|
|
1272
|
+
);
|
|
1273
|
+
}, options.timeoutMs ?? 3e5);
|
|
1274
|
+
});
|
|
1275
|
+
try {
|
|
1276
|
+
await this.#provider.connect();
|
|
1277
|
+
if (this.#phone) {
|
|
1278
|
+
const code = await this.#provider.requestPairingCode(this.#phone);
|
|
1279
|
+
if (code) {
|
|
1280
|
+
this.logger.info("Pairing code generated");
|
|
1281
|
+
if (options.onCode) {
|
|
1282
|
+
await options.onCode(code);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
await connected;
|
|
1287
|
+
} finally {
|
|
1288
|
+
unsubscribe();
|
|
1289
|
+
if (timer) clearTimeout(timer);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
async disconnect() {
|
|
1293
|
+
this.logger.info("Disconnecting application");
|
|
1294
|
+
try {
|
|
1295
|
+
await this.#provider.disconnect();
|
|
1296
|
+
} finally {
|
|
1297
|
+
await this.mute.close();
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
#bind() {
|
|
1301
|
+
this.#provider.on("connection", async (update) => {
|
|
1302
|
+
this.#state = update.state;
|
|
1303
|
+
this.#logConnection(update);
|
|
1304
|
+
await this.#events.emit("connection", update);
|
|
1305
|
+
});
|
|
1306
|
+
this.#provider.on("groupChanged", ({ groupId }) => this.group.invalidate(groupId));
|
|
1307
|
+
this.#provider.on("message", async (message) => {
|
|
1308
|
+
try {
|
|
1309
|
+
const enforcement = await this.mute.enforce(message);
|
|
1310
|
+
if (enforcement) {
|
|
1311
|
+
this.logger.info("Muted message deleted", {
|
|
1312
|
+
groupId: message.chatId,
|
|
1313
|
+
messageId: message.id,
|
|
1314
|
+
userId: message.sender.id
|
|
1315
|
+
});
|
|
1316
|
+
await this.#events.emit("mute", enforcement);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
await this.#reportError(error, { messageId: message.id });
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
try {
|
|
1324
|
+
this.logger.debug("Message received", {
|
|
1325
|
+
chatId: message.chatId,
|
|
1326
|
+
messageId: message.id,
|
|
1327
|
+
senderId: message.sender.id
|
|
1328
|
+
});
|
|
1329
|
+
await this.#events.emit("message", message);
|
|
1330
|
+
const dispatched = await this.#router.dispatch(message);
|
|
1331
|
+
if (dispatched) {
|
|
1332
|
+
this.logger.debug("Command dispatched", {
|
|
1333
|
+
chatId: message.chatId,
|
|
1334
|
+
messageId: message.id
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
await this.#reportError(error, { messageId: message.id });
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
#healthStatus() {
|
|
1343
|
+
if (this.#state === "connected") {
|
|
1344
|
+
return "ready";
|
|
1345
|
+
}
|
|
1346
|
+
if (this.#state === "closed") {
|
|
1347
|
+
return "stopped";
|
|
1348
|
+
}
|
|
1349
|
+
if (this.#state === "connecting" || this.#state === "reconnecting") {
|
|
1350
|
+
return "starting";
|
|
1351
|
+
}
|
|
1352
|
+
return "idle";
|
|
1353
|
+
}
|
|
1354
|
+
#logConnection(update) {
|
|
1355
|
+
const context = {
|
|
1356
|
+
...update.attempt !== void 0 ? { attempt: update.attempt } : {},
|
|
1357
|
+
...update.error ? { error: update.error } : {}
|
|
1358
|
+
};
|
|
1359
|
+
if (update.state === "connected") {
|
|
1360
|
+
this.logger.info("WhatsApp connected");
|
|
1361
|
+
} else if (update.state === "reconnecting") {
|
|
1362
|
+
this.logger.warn("WhatsApp reconnecting", context);
|
|
1363
|
+
} else if (update.state === "closed" && update.error) {
|
|
1364
|
+
this.logger.warn("WhatsApp connection closed", context);
|
|
1365
|
+
} else if (update.state === "closed") {
|
|
1366
|
+
this.logger.info("WhatsApp connection closed");
|
|
1367
|
+
} else {
|
|
1368
|
+
this.logger.debug("WhatsApp connecting", context);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async #reportError(error, context) {
|
|
1372
|
+
const normalized = toWhaNextError(error, context);
|
|
1373
|
+
this.logger.error(normalized.message, {
|
|
1374
|
+
code: normalized.code,
|
|
1375
|
+
...normalized.context
|
|
1376
|
+
});
|
|
1377
|
+
await this.#events.emit("error", normalized);
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
// src/auth/browser.ts
|
|
1382
|
+
var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
1383
|
+
Browser2["Windows"] = "windows";
|
|
1384
|
+
Browser2["MacOS"] = "macos";
|
|
1385
|
+
Browser2["Ubuntu"] = "ubuntu";
|
|
1386
|
+
return Browser2;
|
|
1387
|
+
})(Browser || {});
|
|
1388
|
+
|
|
1389
|
+
// src/provider/baileys/baileys-provider.ts
|
|
1390
|
+
import {
|
|
1391
|
+
Browsers,
|
|
1392
|
+
DisconnectReason,
|
|
1393
|
+
makeWASocket,
|
|
1394
|
+
proto,
|
|
1395
|
+
useMultiFileAuthState
|
|
1396
|
+
} from "@whiskeysockets/baileys";
|
|
1397
|
+
|
|
1398
|
+
// src/provider/baileys/baileys-logger.ts
|
|
1399
|
+
function createBaileysLogger(logger) {
|
|
1400
|
+
return {
|
|
1401
|
+
level: logger.level,
|
|
1402
|
+
child(context) {
|
|
1403
|
+
const name = typeof context.class === "string" ? context.class : "internal";
|
|
1404
|
+
return createBaileysLogger(logger.child(name));
|
|
1405
|
+
},
|
|
1406
|
+
trace(value, message) {
|
|
1407
|
+
logger.debug(resolveMessage(value, message, "Provider trace"), safeContext(value));
|
|
1408
|
+
},
|
|
1409
|
+
debug(value, message) {
|
|
1410
|
+
logger.debug(resolveMessage(value, message, "Provider debug"), safeContext(value));
|
|
1411
|
+
},
|
|
1412
|
+
info(value, message) {
|
|
1413
|
+
logger.debug(resolveMessage(value, message, "Provider info"), safeContext(value));
|
|
1414
|
+
},
|
|
1415
|
+
warn(value, message) {
|
|
1416
|
+
logger.warn(resolveMessage(value, message, "Provider warning"), safeContext(value));
|
|
1417
|
+
},
|
|
1418
|
+
error(value, message) {
|
|
1419
|
+
logger.error(resolveMessage(value, message, "Provider error"), safeContext(value));
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function resolveMessage(value, message, fallback) {
|
|
1424
|
+
if (message) {
|
|
1425
|
+
return message;
|
|
1426
|
+
}
|
|
1427
|
+
return typeof value === "string" ? value : fallback;
|
|
1428
|
+
}
|
|
1429
|
+
function safeContext(value) {
|
|
1430
|
+
if (value instanceof Error) {
|
|
1431
|
+
return { error: value };
|
|
1432
|
+
}
|
|
1433
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1434
|
+
return {};
|
|
1435
|
+
}
|
|
1436
|
+
const source = value;
|
|
1437
|
+
const allowed = [
|
|
1438
|
+
"location",
|
|
1439
|
+
"reason",
|
|
1440
|
+
"status",
|
|
1441
|
+
"statusCode",
|
|
1442
|
+
"type"
|
|
1443
|
+
];
|
|
1444
|
+
return Object.fromEntries(
|
|
1445
|
+
allowed.filter((key) => source[key] !== void 0).map((key) => [key, source[key]])
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/provider/baileys/normalize-message.ts
|
|
1450
|
+
import {
|
|
1451
|
+
extractMessageContent,
|
|
1452
|
+
getContentType,
|
|
1453
|
+
normalizeMessageContent
|
|
1454
|
+
} from "@whiskeysockets/baileys";
|
|
1455
|
+
function normalizeBaileysMessage(input) {
|
|
1456
|
+
const chatId = input.key.remoteJid;
|
|
1457
|
+
const id = input.key.id;
|
|
1458
|
+
if (!chatId || !id || !input.message) {
|
|
1459
|
+
return void 0;
|
|
1460
|
+
}
|
|
1461
|
+
const normalized = normalizeMessageContent(input.message);
|
|
1462
|
+
const content = extractMessageContent(normalized);
|
|
1463
|
+
if (!content) {
|
|
1464
|
+
return void 0;
|
|
1465
|
+
}
|
|
1466
|
+
const type = getContentType(content);
|
|
1467
|
+
const node = type ? content[type] : void 0;
|
|
1468
|
+
const context = getContextInfo(node);
|
|
1469
|
+
const senderIds = uniqueIdentities([
|
|
1470
|
+
input.key.participant,
|
|
1471
|
+
input.key.participantAlt,
|
|
1472
|
+
input.key.participantUsername?.includes("@") ? input.key.participantUsername : void 0
|
|
1473
|
+
]);
|
|
1474
|
+
const senderJid = senderIds.find((identity) => identity.endsWith("@s.whatsapp.net") || identity.endsWith("@c.us"));
|
|
1475
|
+
const senderLid = senderIds.find((identity) => identity.endsWith("@lid"));
|
|
1476
|
+
const senderId = senderJid ?? senderLid ?? senderIds[0] ?? chatId;
|
|
1477
|
+
const sender = new User({
|
|
1478
|
+
id: senderId,
|
|
1479
|
+
identities: senderIds.length > 0 ? senderIds : [senderId],
|
|
1480
|
+
...input.pushName ? { name: input.pushName } : {}
|
|
1481
|
+
});
|
|
1482
|
+
const mentionedUsers = (context?.mentionedJid ?? []).map((identity) => User.fromIdentities([identity]));
|
|
1483
|
+
const media = getMedia(type, node, Boolean(input.key.isViewOnce));
|
|
1484
|
+
const text = getText(content);
|
|
1485
|
+
const caption = getCaption(content);
|
|
1486
|
+
const quoted = getQuoted(context, chatId);
|
|
1487
|
+
const message = {
|
|
1488
|
+
id,
|
|
1489
|
+
jid: chatId,
|
|
1490
|
+
chatId,
|
|
1491
|
+
senderId,
|
|
1492
|
+
senderIds: senderIds.length > 0 ? senderIds : [senderId],
|
|
1493
|
+
sender,
|
|
1494
|
+
keys: normalizeKey(input.key),
|
|
1495
|
+
mentions: [...context?.mentionedJid ?? []],
|
|
1496
|
+
mentionedUsers,
|
|
1497
|
+
timestamp: toDate(input.messageTimestamp),
|
|
1498
|
+
isGroup: chatId.endsWith("@g.us"),
|
|
1499
|
+
isReply: quoted !== void 0,
|
|
1500
|
+
isViewOnce: media?.viewOnce ?? false,
|
|
1501
|
+
hasMedia: media !== void 0
|
|
1502
|
+
};
|
|
1503
|
+
if (senderJid !== void 0) message.senderJid = senderJid;
|
|
1504
|
+
if (senderLid !== void 0) {
|
|
1505
|
+
message.lid = senderLid;
|
|
1506
|
+
message.senderLid = senderLid;
|
|
1507
|
+
}
|
|
1508
|
+
if (text !== void 0) message.text = text;
|
|
1509
|
+
if (caption !== void 0) message.caption = caption;
|
|
1510
|
+
if (media !== void 0) message.media = media;
|
|
1511
|
+
if (quoted !== void 0) message.quoted = quoted;
|
|
1512
|
+
return message;
|
|
1513
|
+
}
|
|
1514
|
+
function normalizeKey(key) {
|
|
1515
|
+
const normalized = {
|
|
1516
|
+
id: key.id ?? "",
|
|
1517
|
+
chatId: key.remoteJid ?? "",
|
|
1518
|
+
fromMe: key.fromMe ?? false
|
|
1519
|
+
};
|
|
1520
|
+
const participantId = key.participant ?? key.participantAlt;
|
|
1521
|
+
if (participantId !== null && participantId !== void 0) {
|
|
1522
|
+
normalized.participantId = participantId;
|
|
1523
|
+
}
|
|
1524
|
+
return normalized;
|
|
1525
|
+
}
|
|
1526
|
+
function getText(content) {
|
|
1527
|
+
return content.conversation ?? content.extendedTextMessage?.text ?? content.buttonsResponseMessage?.selectedDisplayText ?? content.listResponseMessage?.title ?? void 0;
|
|
1528
|
+
}
|
|
1529
|
+
function getCaption(content) {
|
|
1530
|
+
return content.imageMessage?.caption ?? content.videoMessage?.caption ?? content.documentMessage?.caption ?? void 0;
|
|
1531
|
+
}
|
|
1532
|
+
function getContextInfo(node) {
|
|
1533
|
+
if (typeof node !== "object" || node === null || !("contextInfo" in node)) {
|
|
1534
|
+
return void 0;
|
|
1535
|
+
}
|
|
1536
|
+
return node.contextInfo;
|
|
1537
|
+
}
|
|
1538
|
+
function getMedia(type, node, keyViewOnce) {
|
|
1539
|
+
const mapping = {
|
|
1540
|
+
imageMessage: "image",
|
|
1541
|
+
videoMessage: "video",
|
|
1542
|
+
audioMessage: "audio",
|
|
1543
|
+
documentMessage: "document",
|
|
1544
|
+
stickerMessage: "sticker"
|
|
1545
|
+
};
|
|
1546
|
+
const kind = type ? mapping[type] : void 0;
|
|
1547
|
+
if (!kind || typeof node !== "object" || node === null) {
|
|
1548
|
+
return void 0;
|
|
1549
|
+
}
|
|
1550
|
+
const value = node;
|
|
1551
|
+
const media = {
|
|
1552
|
+
kind,
|
|
1553
|
+
viewOnce: keyViewOnce || value.viewOnce === true
|
|
1554
|
+
};
|
|
1555
|
+
if (value.mimetype) media.mimetype = value.mimetype;
|
|
1556
|
+
if (value.fileName) media.fileName = value.fileName;
|
|
1557
|
+
if (value.seconds !== void 0 && value.seconds !== null) media.seconds = Number(value.seconds);
|
|
1558
|
+
return media;
|
|
1559
|
+
}
|
|
1560
|
+
function getQuoted(context, chatId) {
|
|
1561
|
+
if (!context?.stanzaId) {
|
|
1562
|
+
return void 0;
|
|
1563
|
+
}
|
|
1564
|
+
const quoted = context.quotedMessage;
|
|
1565
|
+
const normalized = normalizeMessageContent(quoted);
|
|
1566
|
+
const content = extractMessageContent(normalized);
|
|
1567
|
+
const result = {
|
|
1568
|
+
key: {
|
|
1569
|
+
id: context.stanzaId,
|
|
1570
|
+
chatId: context.remoteJid ?? chatId,
|
|
1571
|
+
fromMe: false
|
|
1572
|
+
},
|
|
1573
|
+
hasMedia: Boolean(
|
|
1574
|
+
content && getMedia(
|
|
1575
|
+
getContentType(content),
|
|
1576
|
+
content[getContentType(content) ?? "conversation"],
|
|
1577
|
+
false
|
|
1578
|
+
)
|
|
1579
|
+
)
|
|
1580
|
+
};
|
|
1581
|
+
if (context.participant) {
|
|
1582
|
+
result.senderId = context.participant;
|
|
1583
|
+
result.sender = User.fromIdentities([context.participant]);
|
|
1584
|
+
result.key.participantId = context.participant;
|
|
1585
|
+
}
|
|
1586
|
+
if (content) {
|
|
1587
|
+
const text = getText(content) ?? getCaption(content);
|
|
1588
|
+
if (text !== void 0) result.text = text;
|
|
1589
|
+
}
|
|
1590
|
+
return result;
|
|
1591
|
+
}
|
|
1592
|
+
function toDate(value) {
|
|
1593
|
+
const seconds = value === null || value === void 0 ? Date.now() / 1e3 : Number(value);
|
|
1594
|
+
return new Date(seconds * 1e3);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// src/provider/baileys/baileys-provider.ts
|
|
1598
|
+
var BaileysProvider = class {
|
|
1599
|
+
#options;
|
|
1600
|
+
#events = new TypedEventEmitter();
|
|
1601
|
+
#logger;
|
|
1602
|
+
#messageStore = /* @__PURE__ */ new Map();
|
|
1603
|
+
#socket;
|
|
1604
|
+
#saveCredentials;
|
|
1605
|
+
#saveQueue = Promise.resolve();
|
|
1606
|
+
#intentionalClose = false;
|
|
1607
|
+
#reconnectAttempt = 0;
|
|
1608
|
+
#reconnectTimer;
|
|
1609
|
+
#registered = false;
|
|
1610
|
+
constructor(options) {
|
|
1611
|
+
this.#options = options;
|
|
1612
|
+
this.#logger = options.logger ?? new Logger("silent");
|
|
1613
|
+
}
|
|
1614
|
+
on(event, listener) {
|
|
1615
|
+
return this.#events.on(event, listener);
|
|
1616
|
+
}
|
|
1617
|
+
async connect() {
|
|
1618
|
+
if (this.#socket) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
this.#intentionalClose = false;
|
|
1622
|
+
await this.#events.emit("connection", {
|
|
1623
|
+
state: this.#reconnectAttempt > 0 ? "reconnecting" : "connecting",
|
|
1624
|
+
attempt: this.#reconnectAttempt
|
|
1625
|
+
});
|
|
1626
|
+
try {
|
|
1627
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.#options.auth);
|
|
1628
|
+
this.#registered = state.creds.registered;
|
|
1629
|
+
this.#saveCredentials = saveCreds;
|
|
1630
|
+
const socket = makeWASocket({
|
|
1631
|
+
auth: state,
|
|
1632
|
+
browser: this.#browserDescription(),
|
|
1633
|
+
logger: createBaileysLogger(this.#logger.child("baileys")),
|
|
1634
|
+
markOnlineOnConnect: false,
|
|
1635
|
+
enableAutoSessionRecreation: true,
|
|
1636
|
+
enableRecentMessageCache: true,
|
|
1637
|
+
getMessage: async (key) => key.id ? this.#messageStore.get(key.id) : void 0
|
|
1638
|
+
});
|
|
1639
|
+
this.#socket = socket;
|
|
1640
|
+
this.#bind(socket);
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
this.#socket = void 0;
|
|
1643
|
+
throw new WhaNextError("CONNECTION_FAILED", "Could not start the WhatsApp connection.", {
|
|
1644
|
+
cause: error,
|
|
1645
|
+
recoverable: true
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
async disconnect() {
|
|
1650
|
+
this.#intentionalClose = true;
|
|
1651
|
+
if (this.#reconnectTimer) clearTimeout(this.#reconnectTimer);
|
|
1652
|
+
const socket = this.#socket;
|
|
1653
|
+
this.#socket = void 0;
|
|
1654
|
+
if (socket) {
|
|
1655
|
+
await socket.end(void 0);
|
|
1656
|
+
}
|
|
1657
|
+
await this.#events.emit("connection", { state: "closed" });
|
|
1658
|
+
}
|
|
1659
|
+
getCurrentUserIds() {
|
|
1660
|
+
const user = this.#socket?.user;
|
|
1661
|
+
if (!user) {
|
|
1662
|
+
return [];
|
|
1663
|
+
}
|
|
1664
|
+
return [user.id, user.lid, user.phoneNumber].filter((id) => Boolean(id)).filter((id, index, ids) => ids.indexOf(id) === index);
|
|
1665
|
+
}
|
|
1666
|
+
async requestPairingCode(phone) {
|
|
1667
|
+
const normalized = phone.replace(/\D/g, "");
|
|
1668
|
+
if (normalized.length < 10) {
|
|
1669
|
+
throw new WhaNextError(
|
|
1670
|
+
"AUTH_INVALID_PHONE",
|
|
1671
|
+
"The phone number must include its country code."
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
if (this.#registered) {
|
|
1675
|
+
return "";
|
|
1676
|
+
}
|
|
1677
|
+
const socket = this.#requireSocket();
|
|
1678
|
+
try {
|
|
1679
|
+
await socket.waitForConnectionUpdate(async (update) => Boolean(update.qr), 6e4);
|
|
1680
|
+
if (socket !== this.#socket) {
|
|
1681
|
+
throw new WhaNextError(
|
|
1682
|
+
"CONNECTION_CLOSED",
|
|
1683
|
+
"The WhatsApp socket changed while preparing the pairing code.",
|
|
1684
|
+
{
|
|
1685
|
+
recoverable: true
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
return await socket.requestPairingCode(normalized);
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
if (error instanceof WhaNextError) {
|
|
1692
|
+
throw error;
|
|
1693
|
+
}
|
|
1694
|
+
throw new WhaNextError(
|
|
1695
|
+
"CONNECTION_FAILED",
|
|
1696
|
+
"Could not request the pairing code after the authentication challenge.",
|
|
1697
|
+
{
|
|
1698
|
+
cause: error,
|
|
1699
|
+
context: { statusCode: this.#statusCode(error) },
|
|
1700
|
+
recoverable: this.#statusCode(error) === DisconnectReason.connectionClosed
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
async sendMessage(chatId, content, replyTo) {
|
|
1706
|
+
const socket = this.#requireSocket();
|
|
1707
|
+
const options = replyTo ? {
|
|
1708
|
+
quoted: {
|
|
1709
|
+
key: this.#toWaKey(replyTo),
|
|
1710
|
+
message: { conversation: "" }
|
|
1711
|
+
}
|
|
1712
|
+
} : void 0;
|
|
1713
|
+
const result = await socket.sendMessage(chatId, this.#toContent(content), options);
|
|
1714
|
+
return this.#sent(result);
|
|
1715
|
+
}
|
|
1716
|
+
async editMessage(key, content) {
|
|
1717
|
+
const result = await this.#requireSocket().sendMessage(key.chatId, {
|
|
1718
|
+
text: content,
|
|
1719
|
+
edit: this.#toWaKey(key)
|
|
1720
|
+
});
|
|
1721
|
+
return this.#sent(result);
|
|
1722
|
+
}
|
|
1723
|
+
async deleteMessage(key) {
|
|
1724
|
+
await this.#requireSocket().sendMessage(key.chatId, { delete: this.#toWaKey(key) });
|
|
1725
|
+
}
|
|
1726
|
+
async getGroup(groupId) {
|
|
1727
|
+
const metadata = await this.#requireSocket().groupMetadata(groupId);
|
|
1728
|
+
return {
|
|
1729
|
+
id: metadata.id,
|
|
1730
|
+
subject: metadata.subject,
|
|
1731
|
+
access: metadata.announce ? "closed" : "open",
|
|
1732
|
+
addressingMode: metadata.addressingMode === "lid" ? "lid" : "pn",
|
|
1733
|
+
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1734
|
+
participants: metadata.participants.map((participant) => ({
|
|
1735
|
+
id: participant.id,
|
|
1736
|
+
...participant.lid ? { lid: participant.lid } : {},
|
|
1737
|
+
...participant.phoneNumber ? { phoneNumber: participant.phoneNumber } : {},
|
|
1738
|
+
role: participant.admin === "superadmin" || participant.isSuperAdmin ? "owner" : participant.admin === "admin" || participant.isAdmin ? "admin" : "member"
|
|
1739
|
+
}))
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
async setGroupAccess(groupId, access) {
|
|
1743
|
+
const setting = access === "closed" ? "announcement" : "not_announcement";
|
|
1744
|
+
await this.#requireSocket().groupSettingUpdate(groupId, setting);
|
|
1745
|
+
}
|
|
1746
|
+
async getGroupInviteCode(groupId) {
|
|
1747
|
+
const code = await this.#requireSocket().groupInviteCode(groupId);
|
|
1748
|
+
if (!code) {
|
|
1749
|
+
throw new WhaNextError(
|
|
1750
|
+
"PROVIDER_ERROR",
|
|
1751
|
+
"WhatsApp did not return a group invite code."
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
return code;
|
|
1755
|
+
}
|
|
1756
|
+
async revokeGroupInvite(groupId) {
|
|
1757
|
+
const code = await this.#requireSocket().groupRevokeInvite(groupId);
|
|
1758
|
+
if (!code) {
|
|
1759
|
+
throw new WhaNextError(
|
|
1760
|
+
"PROVIDER_ERROR",
|
|
1761
|
+
"WhatsApp did not return a new group invite code."
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
return code;
|
|
1765
|
+
}
|
|
1766
|
+
async setMessagePin(groupId, key, pinned) {
|
|
1767
|
+
await this.#requireSocket().sendMessage(groupId, {
|
|
1768
|
+
pin: this.#toWaKey(key),
|
|
1769
|
+
type: pinned ? proto.PinInChat.Type.PIN_FOR_ALL : proto.PinInChat.Type.UNPIN_FOR_ALL,
|
|
1770
|
+
...pinned ? { time: 604800 } : {}
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
async updateParticipant(groupId, memberId, action) {
|
|
1774
|
+
const [result] = await this.#requireSocket().groupParticipantsUpdate(groupId, [memberId], action);
|
|
1775
|
+
const status = result?.status ?? "unknown";
|
|
1776
|
+
return {
|
|
1777
|
+
success: status === "200",
|
|
1778
|
+
status,
|
|
1779
|
+
...result?.jid ? { memberId: result.jid } : {}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
async setPresence(chatId, state) {
|
|
1783
|
+
const socket = this.#requireSocket();
|
|
1784
|
+
await socket.presenceSubscribe(chatId);
|
|
1785
|
+
const presence = state === "typing" ? "composing" : state === "recording" ? "recording" : "paused";
|
|
1786
|
+
await socket.sendPresenceUpdate(presence, chatId);
|
|
1787
|
+
}
|
|
1788
|
+
#bind(socket) {
|
|
1789
|
+
socket.ev.on("creds.update", (update) => {
|
|
1790
|
+
if (update.registered !== void 0) {
|
|
1791
|
+
this.#registered = update.registered;
|
|
1792
|
+
}
|
|
1793
|
+
this.#saveQueue = this.#saveQueue.then(() => this.#saveCredentials?.()).then(() => void 0);
|
|
1794
|
+
});
|
|
1795
|
+
socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
1796
|
+
if (type !== "notify") return;
|
|
1797
|
+
for (const raw of messages) {
|
|
1798
|
+
if (raw.key.id && raw.message) this.#remember(raw.key.id, raw.message);
|
|
1799
|
+
const message = normalizeBaileysMessage(raw);
|
|
1800
|
+
if (message) void this.#events.emit("message", message);
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
socket.ev.on("groups.update", (groups) => {
|
|
1804
|
+
for (const group of groups) {
|
|
1805
|
+
if (group.id) void this.#events.emit("groupChanged", { groupId: group.id });
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
socket.ev.on("group-participants.update", ({ id }) => {
|
|
1809
|
+
void this.#events.emit("groupChanged", { groupId: id });
|
|
1810
|
+
});
|
|
1811
|
+
socket.ev.on("connection.update", (update) => {
|
|
1812
|
+
void this.#handleConnectionUpdate(socket, update.connection, update.lastDisconnect?.error);
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
async #handleConnectionUpdate(socket, connection, error) {
|
|
1816
|
+
if (socket !== this.#socket || !connection) return;
|
|
1817
|
+
if (connection === "open") {
|
|
1818
|
+
this.#reconnectAttempt = 0;
|
|
1819
|
+
await this.#events.emit("connection", { state: "connected" });
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (connection === "connecting") {
|
|
1823
|
+
await this.#events.emit("connection", {
|
|
1824
|
+
state: "connecting",
|
|
1825
|
+
attempt: this.#reconnectAttempt
|
|
1826
|
+
});
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
this.#socket = void 0;
|
|
1830
|
+
if (this.#intentionalClose || this.#isTerminal(error)) {
|
|
1831
|
+
await this.#events.emit("connection", { state: "closed", ...error ? { error } : {} });
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
await this.#scheduleReconnect(error);
|
|
1835
|
+
}
|
|
1836
|
+
async #scheduleReconnect(error) {
|
|
1837
|
+
const options = this.#options.reconnect;
|
|
1838
|
+
const maxAttempts = options?.maxAttempts ?? 10;
|
|
1839
|
+
if (options?.enabled === false || this.#reconnectAttempt >= maxAttempts) {
|
|
1840
|
+
await this.#events.emit("connection", { state: "closed", ...error ? { error } : {} });
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
this.#reconnectAttempt += 1;
|
|
1844
|
+
await this.#events.emit("connection", {
|
|
1845
|
+
state: "reconnecting",
|
|
1846
|
+
attempt: this.#reconnectAttempt,
|
|
1847
|
+
...error ? { error } : {}
|
|
1848
|
+
});
|
|
1849
|
+
const initial = options?.initialDelayMs ?? 1e3;
|
|
1850
|
+
const maximum = options?.maxDelayMs ?? 3e4;
|
|
1851
|
+
const delay = Math.min(maximum, initial * 2 ** (this.#reconnectAttempt - 1));
|
|
1852
|
+
this.#reconnectTimer = setTimeout(
|
|
1853
|
+
() => void this.connect(),
|
|
1854
|
+
delay + Math.floor(Math.random() * 250)
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
#browserDescription() {
|
|
1858
|
+
if (this.#options.browser === "macos" /* MacOS */) return Browsers.macOS("Chrome");
|
|
1859
|
+
if (this.#options.browser === "ubuntu" /* Ubuntu */) return Browsers.ubuntu("Chrome");
|
|
1860
|
+
return Browsers.windows("Chrome");
|
|
1861
|
+
}
|
|
1862
|
+
#isTerminal(error) {
|
|
1863
|
+
const statusCode = this.#statusCode(error);
|
|
1864
|
+
return statusCode === DisconnectReason.loggedOut || statusCode === DisconnectReason.badSession || statusCode === DisconnectReason.connectionReplaced;
|
|
1865
|
+
}
|
|
1866
|
+
#statusCode(error) {
|
|
1867
|
+
return error?.output?.statusCode;
|
|
1868
|
+
}
|
|
1869
|
+
#requireSocket() {
|
|
1870
|
+
if (!this.#socket) {
|
|
1871
|
+
throw new WhaNextError(
|
|
1872
|
+
"CONNECTION_CLOSED",
|
|
1873
|
+
"WhatsApp is not connected.",
|
|
1874
|
+
{ recoverable: true }
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
return this.#socket;
|
|
1878
|
+
}
|
|
1879
|
+
#toContent(content) {
|
|
1880
|
+
if ("text" in content) {
|
|
1881
|
+
return {
|
|
1882
|
+
text: content.text,
|
|
1883
|
+
...content.mentions ? { mentions: this.#mentions(content.mentions) } : {}
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
if ("image" in content) {
|
|
1887
|
+
return {
|
|
1888
|
+
image: this.#media(content.image),
|
|
1889
|
+
...content.caption !== void 0 ? { caption: content.caption } : {},
|
|
1890
|
+
...content.mentions ? { mentions: this.#mentions(content.mentions) } : {},
|
|
1891
|
+
...content.viewOnce !== void 0 ? { viewOnce: content.viewOnce } : {}
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
if ("video" in content) {
|
|
1895
|
+
return {
|
|
1896
|
+
video: this.#media(content.video),
|
|
1897
|
+
...content.caption !== void 0 ? { caption: content.caption } : {},
|
|
1898
|
+
...content.mentions ? { mentions: this.#mentions(content.mentions) } : {},
|
|
1899
|
+
...content.viewOnce !== void 0 ? { viewOnce: content.viewOnce } : {},
|
|
1900
|
+
...content.gif !== void 0 ? { gifPlayback: content.gif } : {}
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
return {
|
|
1904
|
+
audio: this.#media(content.audio),
|
|
1905
|
+
...content.mimetype ? { mimetype: content.mimetype } : {},
|
|
1906
|
+
...content.voice !== void 0 ? { ptt: content.voice } : {}
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
#media(source) {
|
|
1910
|
+
if (source instanceof Uint8Array) {
|
|
1911
|
+
return Buffer.from(source);
|
|
1912
|
+
}
|
|
1913
|
+
if ("url" in source) {
|
|
1914
|
+
return { url: source.url };
|
|
1915
|
+
}
|
|
1916
|
+
return { url: source.path };
|
|
1917
|
+
}
|
|
1918
|
+
#mentions(mentions) {
|
|
1919
|
+
return mentions.map((mention) => typeof mention === "string" ? mention : mention.mentionId);
|
|
1920
|
+
}
|
|
1921
|
+
#toWaKey(key) {
|
|
1922
|
+
return {
|
|
1923
|
+
id: key.id,
|
|
1924
|
+
remoteJid: key.chatId,
|
|
1925
|
+
fromMe: key.fromMe,
|
|
1926
|
+
...key.participantId ? { participant: key.participantId } : {}
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
#sent(message) {
|
|
1930
|
+
if (!message?.key.id || !message.key.remoteJid) {
|
|
1931
|
+
throw new WhaNextError("PROVIDER_ERROR", "WhatsApp did not confirm the sent message.");
|
|
1932
|
+
}
|
|
1933
|
+
if (message.message) this.#remember(message.key.id, message.message);
|
|
1934
|
+
return {
|
|
1935
|
+
id: message.key.id,
|
|
1936
|
+
chatId: message.key.remoteJid,
|
|
1937
|
+
keys: normalizeKey(message.key),
|
|
1938
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
#remember(id, message) {
|
|
1942
|
+
this.#messageStore.set(id, message);
|
|
1943
|
+
if (this.#messageStore.size > 500) {
|
|
1944
|
+
const oldest = this.#messageStore.keys().next().value;
|
|
1945
|
+
if (oldest) this.#messageStore.delete(oldest);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
// src/app/create.ts
|
|
1951
|
+
async function create(options = {}) {
|
|
1952
|
+
const logger = new Logger(options.logger);
|
|
1953
|
+
const provider = options.provider ?? new BaileysProvider({
|
|
1954
|
+
auth: options.auth ?? "./session",
|
|
1955
|
+
browser: options.browser ?? "windows" /* Windows */,
|
|
1956
|
+
logger: logger.child("provider"),
|
|
1957
|
+
...options.reconnect ? { reconnect: options.reconnect } : {}
|
|
1958
|
+
});
|
|
1959
|
+
return new WhaNextApp(provider, {
|
|
1960
|
+
...options.phone ? { phone: options.phone } : {},
|
|
1961
|
+
...options.prefix !== void 0 ? { prefix: options.prefix } : {},
|
|
1962
|
+
...options.cache ? { cache: options.cache } : {},
|
|
1963
|
+
...options.logger ? { logger: options.logger } : {},
|
|
1964
|
+
...options.mute ? { mute: options.mute } : {},
|
|
1965
|
+
...options.router ? { router: options.router } : {}
|
|
1966
|
+
}, logger);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// src/commands/command.ts
|
|
1970
|
+
function defineCommand(command) {
|
|
1971
|
+
return command;
|
|
1972
|
+
}
|
|
1973
|
+
export {
|
|
1974
|
+
ArgsParser,
|
|
1975
|
+
Browser,
|
|
1976
|
+
CommandRouter,
|
|
1977
|
+
Logger,
|
|
1978
|
+
MemoryCache,
|
|
1979
|
+
MuteService,
|
|
1980
|
+
SqliteMuteStore,
|
|
1981
|
+
User,
|
|
1982
|
+
WhaNextApp,
|
|
1983
|
+
WhaNextError,
|
|
1984
|
+
create,
|
|
1985
|
+
defineCommand,
|
|
1986
|
+
toWhaNextError
|
|
1987
|
+
};
|
|
1988
|
+
//# sourceMappingURL=index.js.map
|