@whanext/core 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -370,7 +370,7 @@ var CommandConcurrencyController = class {
370
370
  recoverable: true
371
371
  });
372
372
  } else if (strategy === "queue" && state.active >= max) {
373
- await new Promise((resolve2) => state.queue.push(resolve2));
373
+ await new Promise((resolve3) => state.queue.push(resolve3));
374
374
  }
375
375
  const controller = new AbortController();
376
376
  state.controllers.add(controller);
@@ -392,6 +392,8 @@ var CommandConcurrencyController = class {
392
392
  var CommandContextImplementation = class {
393
393
  message;
394
394
  user;
395
+ account;
396
+ isOwner;
395
397
  chat;
396
398
  group;
397
399
  command;
@@ -413,6 +415,8 @@ var CommandContextImplementation = class {
413
415
  constructor(options) {
414
416
  this.message = options.message;
415
417
  this.user = options.message.sender;
418
+ this.account = options.services.account;
419
+ this.isOwner = this.account.isOwner(options.message);
416
420
  this.chat = { id: options.message.chatId, isGroup: options.message.isGroup };
417
421
  this.command = options.command;
418
422
  this.commands = options.commands;
@@ -979,6 +983,12 @@ _Nenhum comando dispon\xEDvel._`;
979
983
  }
980
984
  }
981
985
  async #authorizeLegacy(command, context) {
986
+ if (command.onlyOwner && !context.isOwner) {
987
+ throw new WhaNextError(
988
+ "COMMAND_NOT_ALLOWED",
989
+ "This command can only be used by the connected WhatsApp account."
990
+ );
991
+ }
982
992
  if (command.onlyGroup && !context.isGroup) {
983
993
  throw new WhaNextError("COMMAND_NOT_ALLOWED", "This command can only be used in groups.");
984
994
  }
@@ -1158,6 +1168,15 @@ function createLegacyServices(group) {
1158
1168
  }
1159
1169
  });
1160
1170
  return {
1171
+ account: {
1172
+ id: void 0,
1173
+ get ids() {
1174
+ return [];
1175
+ },
1176
+ isOwner(message) {
1177
+ return message.keys.fromMe;
1178
+ }
1179
+ },
1161
1180
  groups: group,
1162
1181
  users: new UserService(group),
1163
1182
  messages: unavailable,
@@ -1696,6 +1715,45 @@ var TypedEventEmitter = class {
1696
1715
  }
1697
1716
  };
1698
1717
 
1718
+ // src/services/account-service.ts
1719
+ var AccountService = class {
1720
+ id;
1721
+ #provider;
1722
+ constructor(provider, id) {
1723
+ this.#provider = provider;
1724
+ this.id = id;
1725
+ }
1726
+ get ids() {
1727
+ return uniqueIdentities(this.#provider.getCurrentUserIds());
1728
+ }
1729
+ get jid() {
1730
+ const identity = this.ids.find((candidate) => candidate.endsWith("@s.whatsapp.net") || candidate.endsWith("@c.us"));
1731
+ if (identity) {
1732
+ return normalizeIdentity(identity);
1733
+ }
1734
+ const phone = this.ids.map((candidate) => identityPhoneNumber(candidate)).find((candidate) => candidate !== void 0);
1735
+ return phone ? `${phone}@s.whatsapp.net` : void 0;
1736
+ }
1737
+ get lid() {
1738
+ const identity = this.ids.find((candidate) => candidate.endsWith("@lid"));
1739
+ return identity ? normalizeIdentity(identity) : void 0;
1740
+ }
1741
+ get phoneNumber() {
1742
+ const jid = this.jid;
1743
+ return jid ? identityPhoneNumber(jid) : void 0;
1744
+ }
1745
+ get selfChatId() {
1746
+ return this.jid ?? this.lid ?? this.ids[0];
1747
+ }
1748
+ isOwner(message) {
1749
+ if (message.keys.fromMe) {
1750
+ return true;
1751
+ }
1752
+ const currentIds = this.ids;
1753
+ return message.senderIds.some((senderId) => currentIds.some((currentId) => identitiesMatch(senderId, currentId)));
1754
+ }
1755
+ };
1756
+
1699
1757
  // src/services/chat-service.ts
1700
1758
  var ChatService = class {
1701
1759
  #provider;
@@ -1861,7 +1919,7 @@ var MediaService = class {
1861
1919
  return this.#provider.sendMessage(chatId, content);
1862
1920
  }
1863
1921
  download(message) {
1864
- const key = "keys" in message ? message.keys : message;
1922
+ const key = "keys" in message ? message.keys : "key" in message ? message.key : message;
1865
1923
  return this.#provider.downloadMedia(key);
1866
1924
  }
1867
1925
  };
@@ -1982,6 +2040,7 @@ var MessageService = class {
1982
2040
 
1983
2041
  // src/app/whanext-app.ts
1984
2042
  var WhaNextApp = class {
2043
+ account;
1985
2044
  message;
1986
2045
  media;
1987
2046
  group;
@@ -2000,6 +2059,7 @@ var WhaNextApp = class {
2000
2059
  constructor(provider, options = {}, logger = new Logger(options.logger)) {
2001
2060
  this.#provider = provider;
2002
2061
  this.#phone = options.phone;
2062
+ this.account = new AccountService(provider, options.accountId);
2003
2063
  this.logger = logger;
2004
2064
  const cache = options.cache?.store ?? new MemoryCache(
2005
2065
  options.cache?.memoryMaxEntries === void 0 ? void 0 : { maxEntries: options.cache.memoryMaxEntries }
@@ -2014,6 +2074,7 @@ var WhaNextApp = class {
2014
2074
  const muteStore = muteEnabled ? options.mute?.store ?? new SqliteMuteStore(options.mute?.database) : void 0;
2015
2075
  this.mute = new MuteService(provider, muteStore);
2016
2076
  this.commands = new CommandRouter({
2077
+ account: this.account,
2017
2078
  messages: this.message,
2018
2079
  media: this.media,
2019
2080
  groups: this.group,
@@ -2062,9 +2123,9 @@ var WhaNextApp = class {
2062
2123
  this.logger.info("Login started");
2063
2124
  let unsubscribe = () => void 0;
2064
2125
  let timer;
2065
- const connected = new Promise((resolve2, reject) => {
2126
+ const connected = new Promise((resolve3, reject) => {
2066
2127
  unsubscribe = this.#provider.on("connection", (update) => {
2067
- if (update.state === "connected") resolve2();
2128
+ if (update.state === "connected") resolve3();
2068
2129
  if (update.state === "closed") {
2069
2130
  reject(
2070
2131
  new WhaNextError(
@@ -2289,6 +2350,13 @@ function unwrapMessageContent(input) {
2289
2350
  const nested = content.ephemeralMessage?.message ?? content.viewOnceMessage?.message ?? content.viewOnceMessageV2?.message ?? content.viewOnceMessageV2Extension?.message;
2290
2351
  return nested ? unwrapMessageContent(nested) : content;
2291
2352
  }
2353
+ function isViewOnceContent(input) {
2354
+ if (!input) return false;
2355
+ if (input.viewOnceMessage?.message || input.viewOnceMessageV2?.message || input.viewOnceMessageV2Extension?.message) {
2356
+ return true;
2357
+ }
2358
+ return isViewOnceContent(input.ephemeralMessage?.message);
2359
+ }
2292
2360
  function extractQuotedBaileysMessage(input) {
2293
2361
  const chatId = input.key.remoteJid;
2294
2362
  const content = unwrapMessageContent(input.message);
@@ -2334,7 +2402,8 @@ function normalizeBaileysMessage(input) {
2334
2402
  ...input.pushName ? { name: input.pushName } : {}
2335
2403
  });
2336
2404
  const mentionedUsers = (context?.mentionedJid ?? []).map((identity) => User.fromIdentities([identity]));
2337
- const media = getMedia(type, node, Boolean(input.key.isViewOnce));
2405
+ const viewOnce = Boolean(input.key.isViewOnce) || isViewOnceContent(input.message);
2406
+ const media = getMedia(type, node, viewOnce);
2338
2407
  const contentKind = getContentKind(type);
2339
2408
  const text = getText(content);
2340
2409
  const caption = getCaption(content);
@@ -2454,21 +2523,26 @@ function getQuoted(context, chatId) {
2454
2523
  return void 0;
2455
2524
  }
2456
2525
  const quoted = context.quotedMessage;
2526
+ const quotedIsViewOnce = isViewOnceContent(quoted);
2457
2527
  const content = unwrapMessageContent(quoted);
2528
+ const type = content ? getContentType(content) : void 0;
2529
+ const node = type && content ? content[type] : void 0;
2530
+ const media = getMedia(type, node, quotedIsViewOnce);
2458
2531
  const result = {
2459
2532
  key: {
2460
2533
  id: context.stanzaId,
2461
2534
  chatId: context.remoteJid ?? chatId,
2462
2535
  fromMe: false
2463
2536
  },
2464
- hasMedia: Boolean(
2465
- content && getMedia(
2466
- getContentType(content),
2467
- content[getContentType(content) ?? "conversation"],
2468
- false
2469
- )
2470
- )
2537
+ hasMedia: media !== void 0,
2538
+ isViewOnce: media?.viewOnce ?? quotedIsViewOnce
2471
2539
  };
2540
+ if (content) {
2541
+ result.contentKind = getContentKind(type);
2542
+ }
2543
+ if (media) {
2544
+ result.media = media;
2545
+ }
2472
2546
  if (context.participant) {
2473
2547
  result.senderId = context.participant;
2474
2548
  result.sender = User.fromIdentities([context.participant]);
@@ -2828,9 +2902,9 @@ var BaileysProvider = class {
2828
2902
  );
2829
2903
  }
2830
2904
  #browserDescription() {
2831
- if (this.#options.browser === "macos" /* MacOS */) return Browsers.macOS("Chrome");
2905
+ if (this.#options.browser === "macos" /* MacOS */) return Browsers.macOS("Safari");
2832
2906
  if (this.#options.browser === "ubuntu" /* Ubuntu */) return Browsers.ubuntu("Chrome");
2833
- return Browsers.windows("Chrome");
2907
+ return Browsers.windows("Brave");
2834
2908
  }
2835
2909
  #isTerminal(error) {
2836
2910
  const statusCode = this.#statusCode(error);
@@ -3021,6 +3095,7 @@ async function create(options = {}) {
3021
3095
  ...options.reconnect ? { reconnect: options.reconnect } : {}
3022
3096
  });
3023
3097
  return new WhaNextApp(provider, {
3098
+ ...options.accountId ? { accountId: options.accountId } : {},
3024
3099
  ...options.phone ? { phone: options.phone } : {},
3025
3100
  ...options.prefix !== void 0 ? { prefix: options.prefix } : {},
3026
3101
  ...options.cache ? { cache: options.cache } : {},
@@ -3030,8 +3105,243 @@ async function create(options = {}) {
3030
3105
  }, logger);
3031
3106
  }
3032
3107
 
3108
+ // src/app/multi-app.ts
3109
+ import {
3110
+ join,
3111
+ resolve as resolve2
3112
+ } from "path";
3113
+ var MultiCommandRouter = class {
3114
+ #apps;
3115
+ constructor(apps) {
3116
+ this.#apps = apps;
3117
+ }
3118
+ command(definition) {
3119
+ for (const app of this.#apps.values()) {
3120
+ app.commands.command(definition);
3121
+ }
3122
+ return this;
3123
+ }
3124
+ use(middleware) {
3125
+ for (const app of this.#apps.values()) {
3126
+ app.commands.use(middleware);
3127
+ }
3128
+ return this;
3129
+ }
3130
+ onError(handler) {
3131
+ const unsubscribe = [...this.#apps.values()].map((app) => app.commands.onError(handler));
3132
+ return () => {
3133
+ for (const remove of unsubscribe) remove();
3134
+ };
3135
+ }
3136
+ async load(dirPath, options = {}) {
3137
+ return Promise.all([...this.#apps].map(async ([accountId, app]) => ({
3138
+ accountId,
3139
+ result: await app.commands.load(dirPath, options)
3140
+ })));
3141
+ }
3142
+ };
3143
+ var WhaNextMultiApp = class {
3144
+ commands;
3145
+ #apps;
3146
+ constructor(apps) {
3147
+ this.#apps = apps;
3148
+ this.commands = new MultiCommandRouter(apps);
3149
+ }
3150
+ get size() {
3151
+ return this.#apps.size;
3152
+ }
3153
+ get isReady() {
3154
+ return this.#apps.size > 0 && [...this.#apps.values()].every((app) => app.isReady);
3155
+ }
3156
+ ids() {
3157
+ return [...this.#apps.keys()];
3158
+ }
3159
+ has(accountId) {
3160
+ return this.#apps.has(accountId);
3161
+ }
3162
+ get(accountId) {
3163
+ return this.#apps.get(accountId);
3164
+ }
3165
+ values() {
3166
+ return [...this.#apps.values()];
3167
+ }
3168
+ router() {
3169
+ return this.commands;
3170
+ }
3171
+ on(event, listener) {
3172
+ const unsubscribe = [...this.#apps].map(([accountId, app]) => app.on(event, (payload) => listener({ accountId, app, payload })));
3173
+ return () => {
3174
+ for (const remove of unsubscribe) remove();
3175
+ };
3176
+ }
3177
+ health() {
3178
+ return [...this.#apps].map(([accountId, app]) => ({
3179
+ accountId,
3180
+ ...app.health()
3181
+ }));
3182
+ }
3183
+ async login(options = {}) {
3184
+ const results = await Promise.allSettled(
3185
+ [...this.#apps].map(async ([accountId, app]) => {
3186
+ await app.login({
3187
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
3188
+ ...options.onCode ? { onCode: (code) => options.onCode?.(accountId, code) } : {}
3189
+ });
3190
+ })
3191
+ );
3192
+ const failures = results.map((result, index) => ({ result, accountId: this.ids()[index] })).filter((entry) => entry.result.status === "rejected" && entry.accountId !== void 0);
3193
+ if (failures.length === 0) {
3194
+ return;
3195
+ }
3196
+ throw new WhaNextError(
3197
+ "CONNECTION_FAILED",
3198
+ "One or more WhatsApp accounts could not complete login.",
3199
+ {
3200
+ cause: new AggregateError(
3201
+ failures.map(({ result }) => result.reason),
3202
+ "Multi-account login failed."
3203
+ ),
3204
+ context: {
3205
+ accounts: failures.map(({ accountId }) => accountId)
3206
+ },
3207
+ recoverable: true
3208
+ }
3209
+ );
3210
+ }
3211
+ async disconnect() {
3212
+ const results = await Promise.allSettled(
3213
+ [...this.#apps.values()].map((app) => app.disconnect())
3214
+ );
3215
+ const failures = results.filter((result) => result.status === "rejected");
3216
+ if (failures.length > 0) {
3217
+ throw new WhaNextError(
3218
+ "CONNECTION_FAILED",
3219
+ "One or more WhatsApp accounts could not disconnect cleanly.",
3220
+ {
3221
+ cause: new AggregateError(
3222
+ failures.map((result) => result.reason),
3223
+ "Multi-account disconnect failed."
3224
+ ),
3225
+ recoverable: true
3226
+ }
3227
+ );
3228
+ }
3229
+ }
3230
+ };
3231
+ async function createMulti(options) {
3232
+ if (options.accounts.length === 0) {
3233
+ throw new WhaNextError(
3234
+ "ARGUMENT_INVALID",
3235
+ "At least one WhatsApp account is required."
3236
+ );
3237
+ }
3238
+ const {
3239
+ accounts,
3240
+ authRoot = "./sessions",
3241
+ ...shared
3242
+ } = options;
3243
+ const normalizedIds = /* @__PURE__ */ new Set();
3244
+ const authPaths = /* @__PURE__ */ new Set();
3245
+ for (const account of accounts) {
3246
+ validateAccountId(account.id);
3247
+ const normalizedId = account.id.toLowerCase();
3248
+ if (normalizedIds.has(normalizedId)) {
3249
+ throw new WhaNextError(
3250
+ "ARGUMENT_INVALID",
3251
+ `The multi-account id "${account.id}" is duplicated.`
3252
+ );
3253
+ }
3254
+ normalizedIds.add(normalizedId);
3255
+ if (account.provider === void 0) {
3256
+ const authPath = resolve2(account.auth ?? join(authRoot, account.id));
3257
+ if (authPaths.has(authPath)) {
3258
+ throw new WhaNextError(
3259
+ "ARGUMENT_INVALID",
3260
+ "Each WhatsApp account must use a different auth directory.",
3261
+ { context: { auth: authPath } }
3262
+ );
3263
+ }
3264
+ authPaths.add(authPath);
3265
+ }
3266
+ }
3267
+ const apps = /* @__PURE__ */ new Map();
3268
+ for (const account of accounts) {
3269
+ const {
3270
+ id,
3271
+ ...overrides
3272
+ } = account;
3273
+ const merged = mergeCreateOptions(shared, overrides);
3274
+ if (merged.mute?.enabled === true && merged.mute.store === void 0 && merged.mute.database === void 0) {
3275
+ merged.mute = {
3276
+ ...merged.mute,
3277
+ database: join("./data", `whanext-${id}.sqlite`)
3278
+ };
3279
+ }
3280
+ const app = await create({
3281
+ ...merged,
3282
+ accountId: id,
3283
+ auth: overrides.auth ?? join(authRoot, id)
3284
+ });
3285
+ apps.set(id, app);
3286
+ }
3287
+ return new WhaNextMultiApp(apps);
3288
+ }
3289
+ function validateAccountId(accountId) {
3290
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(accountId)) {
3291
+ throw new WhaNextError(
3292
+ "ARGUMENT_INVALID",
3293
+ "Multi-account ids must use lowercase letters, numbers, underscores or hyphens.",
3294
+ { context: { accountId } }
3295
+ );
3296
+ }
3297
+ }
3298
+ function mergeCreateOptions(shared, account) {
3299
+ const merged = {
3300
+ ...shared,
3301
+ ...account
3302
+ };
3303
+ if (shared.cache || account.cache) {
3304
+ merged.cache = {
3305
+ ...shared.cache,
3306
+ ...account.cache
3307
+ };
3308
+ }
3309
+ if (shared.mute || account.mute) {
3310
+ merged.mute = {
3311
+ ...shared.mute,
3312
+ ...account.mute
3313
+ };
3314
+ }
3315
+ if (shared.router || account.router) {
3316
+ merged.router = {
3317
+ ...shared.router,
3318
+ ...account.router
3319
+ };
3320
+ }
3321
+ if (shared.reconnect || account.reconnect) {
3322
+ merged.reconnect = {
3323
+ ...shared.reconnect,
3324
+ ...account.reconnect
3325
+ };
3326
+ }
3327
+ if (shared.logger && account.logger && typeof shared.logger === "object" && typeof account.logger === "object") {
3328
+ merged.logger = {
3329
+ ...shared.logger,
3330
+ ...account.logger
3331
+ };
3332
+ }
3333
+ return merged;
3334
+ }
3335
+
3033
3336
  // src/commands/guards.ts
3034
3337
  var guards = {
3338
+ owner() {
3339
+ return (context) => context.isOwner || {
3340
+ allowed: false,
3341
+ code: "COMMAND_NOT_ALLOWED",
3342
+ message: "This command can only be used by the connected WhatsApp account."
3343
+ };
3344
+ },
3035
3345
  group() {
3036
3346
  return (context) => context.isGroup || {
3037
3347
  allowed: false,
@@ -3072,19 +3382,23 @@ var guards = {
3072
3382
  }
3073
3383
  };
3074
3384
  export {
3385
+ AccountService,
3075
3386
  ArgsParser,
3076
3387
  Browser,
3077
3388
  CommandRouter,
3078
3389
  DeferredReply,
3079
3390
  Logger,
3080
3391
  MemoryCache,
3392
+ MultiCommandRouter,
3081
3393
  MuteService,
3082
3394
  ParsedCommandOptions,
3083
3395
  SqliteMuteStore,
3084
3396
  User,
3085
3397
  WhaNextApp,
3086
3398
  WhaNextError,
3399
+ WhaNextMultiApp,
3087
3400
  create,
3401
+ createMulti,
3088
3402
  defineCommand,
3089
3403
  defineCommandGroup,
3090
3404
  defineCommands,