@whanext/core 0.5.1 → 0.8.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 +31 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +43 -5
- package/SECURITY.md +1 -1
- package/dist/index.d.ts +32 -3
- package/dist/index.js +115 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
Todas as mudanças relevantes do projeto serão registradas neste arquivo.
|
|
4
4
|
|
|
5
|
+
## 0.8.0
|
|
6
|
+
|
|
7
|
+
### Adicionado
|
|
8
|
+
|
|
9
|
+
- `app.media.sticker(chatId, { sticker })` para enviar stickers WebP estáticos ou animados a partir de bytes, URL ou arquivo local.
|
|
10
|
+
|
|
11
|
+
## 0.7.0
|
|
12
|
+
|
|
13
|
+
### Adicionado
|
|
14
|
+
|
|
15
|
+
- `app.message.react(message, emoji)` e `app.message.unreact(message)` para adicionar e remover reações sem expor tipos do provider.
|
|
16
|
+
- Evento `groupParticipantsChanged`, com grupo, ação, participantes afetados e autor quando informado pelo WhatsApp.
|
|
17
|
+
|
|
18
|
+
### Alterado
|
|
19
|
+
|
|
20
|
+
- Alterações de participantes continuam invalidando automaticamente o cache de metadados do grupo antes da emissão do evento público.
|
|
21
|
+
|
|
22
|
+
## 0.6.0
|
|
23
|
+
|
|
24
|
+
### Adicionado
|
|
25
|
+
|
|
26
|
+
- `app.media.download(message)` para baixar imagens, vídeos, áudios, documentos e stickers recebidos como um `Buffer` com metadados normalizados.
|
|
27
|
+
- Renovação automática da URL de mídia quando o link original expira.
|
|
28
|
+
- Configurações `cache.memoryMaxEntries` e `messageCacheSize` para limitar a memória usada pelos caches internos.
|
|
29
|
+
|
|
30
|
+
### Alterado
|
|
31
|
+
|
|
32
|
+
- O cache em memória agora usa LRU e agrupa buscas simultâneas dos metadados do mesmo grupo em uma única consulta ao WhatsApp.
|
|
33
|
+
- O cache de mensagens usa a chave completa da conversa, evitando colisões entre mensagens de chats distintos, e mantém até 1.000 entradas por padrão.
|
|
34
|
+
- Envio de presença deixou de fazer uma assinatura remota antes de cada atualização, reduzindo uma ida extra à rede para `typing`, `recording` e `paused`.
|
|
35
|
+
|
|
5
36
|
## 0.5.1
|
|
6
37
|
|
|
7
38
|
### Adicionado
|
package/CONTRIBUTING.md
CHANGED
|
@@ -18,6 +18,9 @@ npm run check
|
|
|
18
18
|
- A API pública usa modelos e erros do WhaNext.
|
|
19
19
|
- Operações dependentes de estado retornam resultados tipados e idempotentes.
|
|
20
20
|
- JID, LID e PN são resolvidos pela biblioteca.
|
|
21
|
+
- Mídias são expostas por modelos normalizados; tipos e objetos do provider não entram na API pública.
|
|
22
|
+
- APIs de presença não devem adicionar chamadas remotas além da atualização de estado solicitada.
|
|
23
|
+
- Caches internos devem ter limite de memória, TTL quando aplicável e cobertura para concorrência.
|
|
21
24
|
- Novos comportamentos possuem testes sem conexão real com o WhatsApp.
|
|
22
25
|
- Imports com vários nomes mantêm um nome por linha.
|
|
23
26
|
- Código-fonte não contém comentários explicando implementação óbvia.
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ await app.login({
|
|
|
35
35
|
- Login por pairing code, sessão persistente e reconexão automática.
|
|
36
36
|
- Prefixo global e comandos declarativos com argumentos tipados.
|
|
37
37
|
- Usuários normalizados entre JID, LID e PN.
|
|
38
|
-
- Mensagens, replies, edição, exclusão, menções e
|
|
38
|
+
- Mensagens, replies, edição, exclusão, menções, mídias e download normalizado.
|
|
39
39
|
- Operações de grupos e membros com resultados idempotentes.
|
|
40
40
|
- Cache de metadados transparente e substituível.
|
|
41
41
|
- Mute permanente ou temporário com SQLite ou banco próprio.
|
|
@@ -261,10 +261,15 @@ await app.message.reply(message, {
|
|
|
261
261
|
|
|
262
262
|
await app.message.edit(sent, 'Texto atualizado.');
|
|
263
263
|
await app.message.delete(sent);
|
|
264
|
+
|
|
265
|
+
await app.message.react(sent, '👏🏻');
|
|
266
|
+
await app.message.unreact(sent);
|
|
264
267
|
```
|
|
265
268
|
|
|
266
269
|
`delete()` aceita `Message`, `SentMessage` ou `MessageKey`.
|
|
267
270
|
|
|
271
|
+
`react()` aceita os mesmos tipos e adiciona uma reação à mensagem. `unreact()` remove a reação da conta conectada.
|
|
272
|
+
|
|
268
273
|
## Mídias
|
|
269
274
|
|
|
270
275
|
```ts
|
|
@@ -284,9 +289,19 @@ await app.media.audio(chatId, {
|
|
|
284
289
|
mimetype: 'audio/ogg; codecs=opus',
|
|
285
290
|
voice: true,
|
|
286
291
|
});
|
|
292
|
+
|
|
293
|
+
await app.media.sticker(chatId, {
|
|
294
|
+
sticker: { path: './sticker.webp' },
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const downloaded = await app.media.download(message);
|
|
298
|
+
|
|
299
|
+
await writeFile(`./downloads/${downloaded.fileName ?? message.id}`, downloaded.data);
|
|
287
300
|
```
|
|
288
301
|
|
|
289
|
-
|
|
302
|
+
`download()` aceita a `Message` recebida ou a sua `MessageKey` e devolve o buffer junto dos metadados normalizados. A mídia deve ser baixada enquanto a mensagem ainda está no cache da instância; o provider tenta renovar a URL de mídia automaticamente quando necessário.
|
|
303
|
+
|
|
304
|
+
Stickers aceitam `Uint8Array`, URL ou caminho local e devem estar em WebP, inclusive para animações. Texto, imagem e vídeo aceitam `User` diretamente em `mentions`.
|
|
290
305
|
|
|
291
306
|
## Grupos e membros
|
|
292
307
|
|
|
@@ -305,6 +320,19 @@ await app.member.demote(groupId, user);
|
|
|
305
320
|
|
|
306
321
|
Estados como `already_open`, `already_admin`, `not_admin`, `not_in_group` e `already_removed` evitam mutações redundantes. A biblioteca escolhe automaticamente a identidade correta para grupos LID ou PN e só retorna sucesso depois da confirmação do WhatsApp.
|
|
307
322
|
|
|
323
|
+
Mudanças feitas por outros participantes podem ser acompanhadas sem lidar com o provider:
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
app.on('groupParticipantsChanged', async (change) => {
|
|
327
|
+
console.log(change.groupId);
|
|
328
|
+
console.log(change.action);
|
|
329
|
+
console.log(change.participantIds);
|
|
330
|
+
console.log(change.authorId);
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`action` pode ser `add`, `remove`, `promote`, `demote` ou `modify`. O cache de metadados do grupo é invalidado antes desse evento ser emitido.
|
|
335
|
+
|
|
308
336
|
## Mute nativo
|
|
309
337
|
|
|
310
338
|
Quando habilitado, o mute é aplicado antes dos eventos públicos e do router. Mensagens de um usuário mutado são apagadas automaticamente.
|
|
@@ -489,11 +517,18 @@ const app = await create({
|
|
|
489
517
|
cache: {
|
|
490
518
|
store: myRedisStore,
|
|
491
519
|
groupTtlMs: 300_000,
|
|
520
|
+
memoryMaxEntries: 1_000,
|
|
492
521
|
},
|
|
493
522
|
});
|
|
494
523
|
```
|
|
495
524
|
|
|
496
|
-
O cache padrão vive na instância do app. Eventos e mutações de grupo invalidam automaticamente entradas relacionadas.
|
|
525
|
+
O cache padrão vive na instância do app, usa LRU limitado e elimina entradas expiradas durante as leituras. Consultas simultâneas dos mesmos metadados são agrupadas em uma única chamada ao WhatsApp. Eventos e mutações de grupo invalidam automaticamente entradas relacionadas.
|
|
526
|
+
|
|
527
|
+
O cache interno de mensagens mantém até 1.000 mensagens por padrão para replies, reenvios do provider e downloads de mídia. Ajuste quando necessário:
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
const app = await create({ messageCacheSize: 2_000 });
|
|
531
|
+
```
|
|
497
532
|
|
|
498
533
|
## Presença
|
|
499
534
|
|
|
@@ -503,6 +538,8 @@ await app.chat.recording(chatId);
|
|
|
503
538
|
await app.chat.stopTyping(chatId);
|
|
504
539
|
```
|
|
505
540
|
|
|
541
|
+
`typing()` envia `composing`, `recording()` envia `recording` com mídia de áudio e `stopTyping()` envia `paused`, conforme o protocolo do WhatsApp. Nenhuma assinatura de presença desnecessária é feita antes do envio.
|
|
542
|
+
|
|
506
543
|
## Chamadas
|
|
507
544
|
|
|
508
545
|
```ts
|
|
@@ -533,8 +570,8 @@ Todos os erros públicos usam `WhaNextError` e códigos estáveis. O logger regi
|
|
|
533
570
|
|
|
534
571
|
| Domínio | Responsabilidade |
|
|
535
572
|
| --- | --- |
|
|
536
|
-
| `app.message` | Envio, reply, edição, exclusão e
|
|
537
|
-
| `app.media` |
|
|
573
|
+
| `app.message` | Envio, reply, edição, exclusão, texto e reações |
|
|
574
|
+
| `app.media` | Envio, download e stickers |
|
|
538
575
|
| `app.group` | Estado, convite, pin e metadados |
|
|
539
576
|
| `app.member` | Remoção, promoção e rebaixamento |
|
|
540
577
|
| `app.user` | Criação e resolução de usuários |
|
|
@@ -544,6 +581,7 @@ Todos os erros públicos usam `WhaNextError` e códigos estáveis. O logger regi
|
|
|
544
581
|
| `app.router()` | Registro e despacho de comandos |
|
|
545
582
|
| `loadCommands()` | Autoload de comandos a partir de uma pasta |
|
|
546
583
|
| `app.health()` | Snapshot de saúde da aplicação |
|
|
584
|
+
| `app.on('groupParticipantsChanged')` | Alterações de participantes em grupos |
|
|
547
585
|
|
|
548
586
|
## Exemplo executável
|
|
549
587
|
|
package/SECURITY.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface CacheStore {
|
|
|
7
7
|
interface CacheOptions {
|
|
8
8
|
store?: CacheStore;
|
|
9
9
|
groupTtlMs?: number;
|
|
10
|
+
memoryMaxEntries?: number;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface UserData {
|
|
@@ -107,6 +108,12 @@ interface SentMessage {
|
|
|
107
108
|
keys: MessageKey;
|
|
108
109
|
timestamp: Date;
|
|
109
110
|
}
|
|
111
|
+
interface DownloadedMedia {
|
|
112
|
+
data: Buffer;
|
|
113
|
+
kind: MediaKind;
|
|
114
|
+
mimetype?: string;
|
|
115
|
+
fileName?: string;
|
|
116
|
+
}
|
|
110
117
|
type MediaSource = Uint8Array | {
|
|
111
118
|
url: string;
|
|
112
119
|
} | {
|
|
@@ -135,7 +142,10 @@ interface AudioContent {
|
|
|
135
142
|
mimetype?: string;
|
|
136
143
|
voice?: boolean;
|
|
137
144
|
}
|
|
138
|
-
|
|
145
|
+
interface StickerContent {
|
|
146
|
+
sticker: MediaSource;
|
|
147
|
+
}
|
|
148
|
+
type MessageContent = TextContent | ImageContent | VideoContent | AudioContent | StickerContent;
|
|
139
149
|
|
|
140
150
|
interface CommandDefinition {
|
|
141
151
|
name: string;
|
|
@@ -150,7 +160,7 @@ interface CommandDefinition {
|
|
|
150
160
|
declare function defineCommand<const Command extends CommandDefinition>(command: Command): Command;
|
|
151
161
|
declare function defineCommands<const Commands extends readonly CommandDefinition[]>(...commands: Commands): Commands;
|
|
152
162
|
|
|
153
|
-
type WhaNextErrorCode = 'AUTH_INVALID_PHONE' | 'AUTH_EXPIRED' | 'CONNECTION_CLOSED' | 'CONNECTION_FAILED' | 'GROUP_NOT_FOUND' | 'MEMBER_NOT_FOUND' | 'BOT_NOT_ADMIN' | 'MESSAGE_NOT_FOUND' | 'MUTE_DISABLED' | 'STORAGE_ERROR' | 'ARGUMENT_MISSING' | 'ARGUMENT_INVALID' | 'COMMAND_NOT_ALLOWED' | 'COMMAND_LOAD_FAILED' | 'PROVIDER_ERROR' | 'UNKNOWN_ERROR';
|
|
163
|
+
type WhaNextErrorCode = 'AUTH_INVALID_PHONE' | 'AUTH_EXPIRED' | 'CONNECTION_CLOSED' | 'CONNECTION_FAILED' | 'GROUP_NOT_FOUND' | 'MEMBER_NOT_FOUND' | 'BOT_NOT_ADMIN' | 'MESSAGE_NOT_FOUND' | 'MEDIA_NOT_AVAILABLE' | 'MUTE_DISABLED' | 'STORAGE_ERROR' | 'ARGUMENT_MISSING' | 'ARGUMENT_INVALID' | 'COMMAND_NOT_ALLOWED' | 'COMMAND_LOAD_FAILED' | 'PROVIDER_ERROR' | 'UNKNOWN_ERROR';
|
|
154
164
|
interface WhaNextErrorOptions {
|
|
155
165
|
cause?: unknown;
|
|
156
166
|
context?: Readonly<Record<string, unknown>>;
|
|
@@ -167,6 +177,13 @@ declare function toWhaNextError(error: unknown, context?: Readonly<Record<string
|
|
|
167
177
|
type GroupAccess = 'open' | 'closed';
|
|
168
178
|
type GroupRole = 'member' | 'admin' | 'owner';
|
|
169
179
|
type GroupAddressingMode = 'lid' | 'pn';
|
|
180
|
+
type GroupParticipantAction = 'add' | 'remove' | 'promote' | 'demote' | 'modify';
|
|
181
|
+
interface GroupParticipantsChanged {
|
|
182
|
+
groupId: string;
|
|
183
|
+
action: GroupParticipantAction;
|
|
184
|
+
participantIds: string[];
|
|
185
|
+
authorId?: string;
|
|
186
|
+
}
|
|
170
187
|
interface GroupParticipant {
|
|
171
188
|
id: string;
|
|
172
189
|
lid?: string;
|
|
@@ -228,6 +245,7 @@ interface ProviderEvents {
|
|
|
228
245
|
groupChanged: {
|
|
229
246
|
groupId: string;
|
|
230
247
|
};
|
|
248
|
+
groupParticipantsChanged: GroupParticipantsChanged;
|
|
231
249
|
call: CallEvent;
|
|
232
250
|
}
|
|
233
251
|
type Unsubscribe = () => void;
|
|
@@ -238,6 +256,8 @@ interface WhatsAppProvider {
|
|
|
238
256
|
requestPairingCode(phone: string): Promise<string>;
|
|
239
257
|
on<Event extends keyof ProviderEvents>(event: Event, listener: (payload: ProviderEvents[Event]) => void | Promise<void>): Unsubscribe;
|
|
240
258
|
sendMessage(chatId: string, content: MessageContent, replyTo?: MessageKey): Promise<SentMessage>;
|
|
259
|
+
reactToMessage(key: MessageKey, emoji?: string): Promise<SentMessage>;
|
|
260
|
+
downloadMedia(key: MessageKey): Promise<DownloadedMedia>;
|
|
241
261
|
editMessage(key: MessageKey, content: string): Promise<SentMessage>;
|
|
242
262
|
deleteMessage(key: MessageKey): Promise<void>;
|
|
243
263
|
getGroup(groupId: string): Promise<GroupSnapshot>;
|
|
@@ -393,6 +413,8 @@ declare class MediaService {
|
|
|
393
413
|
image(chatId: string, content: ImageContent): Promise<SentMessage>;
|
|
394
414
|
video(chatId: string, content: VideoContent): Promise<SentMessage>;
|
|
395
415
|
audio(chatId: string, content: AudioContent): Promise<SentMessage>;
|
|
416
|
+
sticker(chatId: string, content: StickerContent): Promise<SentMessage>;
|
|
417
|
+
download(message: Message | MessageKey): Promise<DownloadedMedia>;
|
|
396
418
|
}
|
|
397
419
|
|
|
398
420
|
declare class MemberService {
|
|
@@ -410,6 +432,8 @@ declare class MessageService {
|
|
|
410
432
|
reply(message: Message, content: MessageContent): Promise<SentMessage>;
|
|
411
433
|
edit(message: Message | SentMessage | MessageKey, text: string): Promise<SentMessage>;
|
|
412
434
|
delete(message: Message | SentMessage | MessageKey): Promise<void>;
|
|
435
|
+
react(message: Message | SentMessage | MessageKey, emoji: string): Promise<SentMessage>;
|
|
436
|
+
unreact(message: Message | SentMessage | MessageKey): Promise<SentMessage>;
|
|
413
437
|
text(chatId: string, text: string, mentions?: MentionTarget[]): Promise<SentMessage>;
|
|
414
438
|
}
|
|
415
439
|
|
|
@@ -426,6 +450,7 @@ interface AppEvents {
|
|
|
426
450
|
error: WhaNextError;
|
|
427
451
|
mute: MuteEnforcement;
|
|
428
452
|
call: CallEvent;
|
|
453
|
+
groupParticipantsChanged: GroupParticipantsChanged;
|
|
429
454
|
}
|
|
430
455
|
interface LoginOptions {
|
|
431
456
|
onCode?: (code: string) => void | Promise<void>;
|
|
@@ -491,12 +516,16 @@ interface CreateOptions {
|
|
|
491
516
|
mute?: MuteOptions;
|
|
492
517
|
router?: Omit<RouterOptions, 'prefix'>;
|
|
493
518
|
reconnect?: ReconnectOptions;
|
|
519
|
+
messageCacheSize?: number;
|
|
494
520
|
provider?: WhatsAppProvider;
|
|
495
521
|
}
|
|
496
522
|
declare function create(options?: CreateOptions): Promise<WhaNextApp>;
|
|
497
523
|
|
|
498
524
|
declare class MemoryCache implements CacheStore {
|
|
499
525
|
#private;
|
|
526
|
+
constructor(options?: {
|
|
527
|
+
maxEntries?: number;
|
|
528
|
+
});
|
|
500
529
|
get<T>(key: string): Promise<T | undefined>;
|
|
501
530
|
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
502
531
|
delete(key: string): Promise<void>;
|
|
@@ -531,4 +560,4 @@ declare class SqliteMuteStore implements MuteStore {
|
|
|
531
560
|
close(): void;
|
|
532
561
|
}
|
|
533
562
|
|
|
534
|
-
export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandDefinition, type CommandRegistrar, CommandRouter, type ConnectionState, type ConnectionUpdate, type CreateOptions, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupRole, type GroupSnapshot, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RemoveMuteResult, type RouterOptions, type SentMessage, SqliteMuteStore, type StoredMute, type TextContent, User, type UserData, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommands, loadCommands, toWhaNextError };
|
|
563
|
+
export { type AddMuteOptions, type AddMuteResult, type AppEvents, type AppHealth, type AppHealthStatus, ArgsParser, type AudioContent, Browser, type CacheOptions, type CacheStore, type CallEvent, type CallStatus, type ChangeResult, type CommandDefinition, type CommandRegistrar, CommandRouter, type ConnectionState, type ConnectionUpdate, type CreateOptions, type DownloadedMedia, type GroupAccess, type GroupAddressingMode, type GroupParticipant, type GroupParticipantAction, type GroupParticipantsChanged, type GroupRole, type GroupSnapshot, type ImageContent, type InviteResult, type LoadCommandsOptions, type LoadCommandsResult, type LoadedCommand, type LogContext, type LogEntry, type LogFormat, type LogLevel, type LogWriter, Logger, type LoggerConfig, type LoggerOptions, type LoginOptions, type MediaKind, type MediaSource, type MemberActionState, MemoryCache, type MentionTarget, type Message, type MessageContent, type MessageKey, type MessageMedia, type MuteChangeResult, type MuteEnforcement, type MuteOptions, type MuteRecord, MuteService, type MuteStore, type ParticipantUpdateResult, type PresenceState, type QuotedMessage, type ReconnectOptions, type RemoveMuteResult, type RouterOptions, type SentMessage, SqliteMuteStore, type StickerContent, type StoredMute, type TextContent, User, type UserData, type VideoContent, WhaNextApp, WhaNextError, type WhaNextErrorCode, type WhatsAppProvider, create, defineCommand, defineCommands, loadCommands, toWhaNextError };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/cache/memory-cache.ts
|
|
2
2
|
var MemoryCache = class {
|
|
3
3
|
#entries = /* @__PURE__ */ new Map();
|
|
4
|
+
#maxEntries;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.#maxEntries = Math.max(1, options.maxEntries ?? 1e3);
|
|
7
|
+
}
|
|
4
8
|
async get(key) {
|
|
5
9
|
const entry = this.#entries.get(key);
|
|
6
10
|
if (!entry) {
|
|
@@ -10,6 +14,8 @@ var MemoryCache = class {
|
|
|
10
14
|
this.#entries.delete(key);
|
|
11
15
|
return void 0;
|
|
12
16
|
}
|
|
17
|
+
this.#entries.delete(key);
|
|
18
|
+
this.#entries.set(key, entry);
|
|
13
19
|
return entry.value;
|
|
14
20
|
}
|
|
15
21
|
async set(key, value, ttlMs) {
|
|
@@ -18,6 +24,11 @@ var MemoryCache = class {
|
|
|
18
24
|
entry.expiresAt = Date.now() + ttlMs;
|
|
19
25
|
}
|
|
20
26
|
this.#entries.set(key, entry);
|
|
27
|
+
while (this.#entries.size > this.#maxEntries) {
|
|
28
|
+
const oldest = this.#entries.keys().next().value;
|
|
29
|
+
if (oldest === void 0) return;
|
|
30
|
+
this.#entries.delete(oldest);
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
async delete(key) {
|
|
23
34
|
this.#entries.delete(key);
|
|
@@ -932,6 +943,7 @@ var GroupService = class {
|
|
|
932
943
|
#provider;
|
|
933
944
|
#cache;
|
|
934
945
|
#ttlMs;
|
|
946
|
+
#requests = /* @__PURE__ */ new Map();
|
|
935
947
|
constructor(provider, cache, ttlMs = 3e5) {
|
|
936
948
|
this.#provider = provider;
|
|
937
949
|
this.#cache = cache;
|
|
@@ -979,9 +991,17 @@ var GroupService = class {
|
|
|
979
991
|
return cached;
|
|
980
992
|
}
|
|
981
993
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
994
|
+
const pending = this.#requests.get(key);
|
|
995
|
+
if (pending) {
|
|
996
|
+
return pending;
|
|
997
|
+
}
|
|
998
|
+
const request = this.#loadMetadata(key, groupId);
|
|
999
|
+
this.#requests.set(key, request);
|
|
1000
|
+
try {
|
|
1001
|
+
return await request;
|
|
1002
|
+
} finally {
|
|
1003
|
+
this.#requests.delete(key);
|
|
1004
|
+
}
|
|
985
1005
|
}
|
|
986
1006
|
async isAdmin(groupId, memberIds) {
|
|
987
1007
|
if (!groupId.endsWith("@g.us")) {
|
|
@@ -1030,6 +1050,11 @@ var GroupService = class {
|
|
|
1030
1050
|
#key(groupId) {
|
|
1031
1051
|
return `group:${groupId}`;
|
|
1032
1052
|
}
|
|
1053
|
+
async #loadMetadata(key, groupId) {
|
|
1054
|
+
const group = await this.#provider.getGroup(groupId);
|
|
1055
|
+
await this.#cache.set(key, group, this.#ttlMs);
|
|
1056
|
+
return group;
|
|
1057
|
+
}
|
|
1033
1058
|
#matchesParticipant(participant, identities) {
|
|
1034
1059
|
const participantIds = [participant.id, participant.lid, participant.phoneNumber].filter((identity) => identity !== void 0);
|
|
1035
1060
|
return identities.some((identity) => participantIds.some((participantId) => identitiesMatch(identity, participantId)));
|
|
@@ -1051,6 +1076,13 @@ var MediaService = class {
|
|
|
1051
1076
|
audio(chatId, content) {
|
|
1052
1077
|
return this.#provider.sendMessage(chatId, content);
|
|
1053
1078
|
}
|
|
1079
|
+
sticker(chatId, content) {
|
|
1080
|
+
return this.#provider.sendMessage(chatId, content);
|
|
1081
|
+
}
|
|
1082
|
+
download(message) {
|
|
1083
|
+
const key = "keys" in message ? message.keys : message;
|
|
1084
|
+
return this.#provider.downloadMedia(key);
|
|
1085
|
+
}
|
|
1054
1086
|
};
|
|
1055
1087
|
|
|
1056
1088
|
// src/services/member-service.ts
|
|
@@ -1141,6 +1173,14 @@ var MessageService = class {
|
|
|
1141
1173
|
const key = "keys" in message ? message.keys : message;
|
|
1142
1174
|
return this.#provider.deleteMessage(key);
|
|
1143
1175
|
}
|
|
1176
|
+
react(message, emoji) {
|
|
1177
|
+
const key = "keys" in message ? message.keys : message;
|
|
1178
|
+
return this.#provider.reactToMessage(key, emoji);
|
|
1179
|
+
}
|
|
1180
|
+
unreact(message) {
|
|
1181
|
+
const key = "keys" in message ? message.keys : message;
|
|
1182
|
+
return this.#provider.reactToMessage(key);
|
|
1183
|
+
}
|
|
1144
1184
|
text(chatId, text, mentions) {
|
|
1145
1185
|
const content = { text };
|
|
1146
1186
|
if (mentions !== void 0) {
|
|
@@ -1199,7 +1239,9 @@ var WhaNextApp = class {
|
|
|
1199
1239
|
this.#provider = provider;
|
|
1200
1240
|
this.#phone = options.phone;
|
|
1201
1241
|
this.logger = logger;
|
|
1202
|
-
const cache = options.cache?.store ?? new MemoryCache(
|
|
1242
|
+
const cache = options.cache?.store ?? new MemoryCache(
|
|
1243
|
+
options.cache?.memoryMaxEntries === void 0 ? void 0 : { maxEntries: options.cache.memoryMaxEntries }
|
|
1244
|
+
);
|
|
1203
1245
|
this.group = new GroupService(provider, cache, options.cache?.groupTtlMs);
|
|
1204
1246
|
this.member = new MemberService(provider, this.group);
|
|
1205
1247
|
this.message = new MessageService(provider);
|
|
@@ -1307,6 +1349,10 @@ var WhaNextApp = class {
|
|
|
1307
1349
|
await this.#events.emit("connection", update);
|
|
1308
1350
|
});
|
|
1309
1351
|
this.#provider.on("groupChanged", ({ groupId }) => this.group.invalidate(groupId));
|
|
1352
|
+
this.#provider.on("groupParticipantsChanged", async (change) => {
|
|
1353
|
+
await this.group.invalidate(change.groupId);
|
|
1354
|
+
await this.#events.emit("groupParticipantsChanged", change);
|
|
1355
|
+
});
|
|
1310
1356
|
this.#provider.on("call", async (call) => {
|
|
1311
1357
|
this.logger.debug("Call received", {
|
|
1312
1358
|
callId: call.id,
|
|
@@ -1401,6 +1447,7 @@ var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
|
1401
1447
|
import {
|
|
1402
1448
|
Browsers,
|
|
1403
1449
|
DisconnectReason,
|
|
1450
|
+
downloadMediaMessage,
|
|
1404
1451
|
makeWASocket,
|
|
1405
1452
|
proto,
|
|
1406
1453
|
useMultiFileAuthState
|
|
@@ -1611,6 +1658,7 @@ var BaileysProvider = class {
|
|
|
1611
1658
|
#events = new TypedEventEmitter();
|
|
1612
1659
|
#logger;
|
|
1613
1660
|
#messageStore = /* @__PURE__ */ new Map();
|
|
1661
|
+
#messageCacheSize;
|
|
1614
1662
|
#socket;
|
|
1615
1663
|
#saveCredentials;
|
|
1616
1664
|
#saveQueue = Promise.resolve();
|
|
@@ -1621,6 +1669,7 @@ var BaileysProvider = class {
|
|
|
1621
1669
|
constructor(options) {
|
|
1622
1670
|
this.#options = options;
|
|
1623
1671
|
this.#logger = options.logger ?? new Logger("silent");
|
|
1672
|
+
this.#messageCacheSize = Math.max(1, options.messageCacheSize ?? 1e3);
|
|
1624
1673
|
}
|
|
1625
1674
|
on(event, listener) {
|
|
1626
1675
|
return this.#events.on(event, listener);
|
|
@@ -1645,7 +1694,7 @@ var BaileysProvider = class {
|
|
|
1645
1694
|
markOnlineOnConnect: false,
|
|
1646
1695
|
enableAutoSessionRecreation: true,
|
|
1647
1696
|
enableRecentMessageCache: true,
|
|
1648
|
-
getMessage: async (key) =>
|
|
1697
|
+
getMessage: async (key) => this.#messageStore.get(this.#messageStoreKey(key))?.message ?? void 0
|
|
1649
1698
|
});
|
|
1650
1699
|
this.#socket = socket;
|
|
1651
1700
|
this.#bind(socket);
|
|
@@ -1724,6 +1773,39 @@ var BaileysProvider = class {
|
|
|
1724
1773
|
const result = await socket.sendMessage(chatId, this.#toContent(content), options);
|
|
1725
1774
|
return this.#sent(result);
|
|
1726
1775
|
}
|
|
1776
|
+
async reactToMessage(key, emoji) {
|
|
1777
|
+
const result = await this.#requireSocket().sendMessage(key.chatId, {
|
|
1778
|
+
react: {
|
|
1779
|
+
text: emoji ?? "",
|
|
1780
|
+
key: this.#toWaKey(key)
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
return this.#sent(result);
|
|
1784
|
+
}
|
|
1785
|
+
async downloadMedia(key) {
|
|
1786
|
+
const message = this.#messageStore.get(this.#messageStoreKey(key));
|
|
1787
|
+
if (!message?.message) {
|
|
1788
|
+
throw new WhaNextError(
|
|
1789
|
+
"MEDIA_NOT_AVAILABLE",
|
|
1790
|
+
"The media is unavailable. Download it from the received message event while it is cached.",
|
|
1791
|
+
{ recoverable: true }
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
const data = await downloadMediaMessage(message, "buffer", {}, {
|
|
1795
|
+
reuploadRequest: (current) => this.#requireSocket().updateMediaMessage(current),
|
|
1796
|
+
logger: createBaileysLogger(this.#logger.child("media"))
|
|
1797
|
+
});
|
|
1798
|
+
const media = normalizeBaileysMessage(message)?.media;
|
|
1799
|
+
if (!media) {
|
|
1800
|
+
throw new WhaNextError("MEDIA_NOT_AVAILABLE", "The selected message does not contain media.");
|
|
1801
|
+
}
|
|
1802
|
+
return {
|
|
1803
|
+
data,
|
|
1804
|
+
kind: media.kind,
|
|
1805
|
+
...media.mimetype ? { mimetype: media.mimetype } : {},
|
|
1806
|
+
...media.fileName ? { fileName: media.fileName } : {}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1727
1809
|
async editMessage(key, content) {
|
|
1728
1810
|
const result = await this.#requireSocket().sendMessage(key.chatId, {
|
|
1729
1811
|
text: content,
|
|
@@ -1792,7 +1874,6 @@ var BaileysProvider = class {
|
|
|
1792
1874
|
}
|
|
1793
1875
|
async setPresence(chatId, state) {
|
|
1794
1876
|
const socket = this.#requireSocket();
|
|
1795
|
-
await socket.presenceSubscribe(chatId);
|
|
1796
1877
|
const presence = state === "typing" ? "composing" : state === "recording" ? "recording" : "paused";
|
|
1797
1878
|
await socket.sendPresenceUpdate(presence, chatId);
|
|
1798
1879
|
}
|
|
@@ -1809,7 +1890,7 @@ var BaileysProvider = class {
|
|
|
1809
1890
|
socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
1810
1891
|
if (type !== "notify") return;
|
|
1811
1892
|
for (const raw of messages) {
|
|
1812
|
-
if (raw.key.id && raw.message) this.#remember(raw
|
|
1893
|
+
if (raw.key.id && raw.message) this.#remember(raw);
|
|
1813
1894
|
const message = normalizeBaileysMessage(raw);
|
|
1814
1895
|
if (message) void this.#events.emit("message", message);
|
|
1815
1896
|
}
|
|
@@ -1819,7 +1900,10 @@ var BaileysProvider = class {
|
|
|
1819
1900
|
if (group.id) void this.#events.emit("groupChanged", { groupId: group.id });
|
|
1820
1901
|
}
|
|
1821
1902
|
});
|
|
1822
|
-
socket.ev.on("group-participants.update", (
|
|
1903
|
+
socket.ev.on("group-participants.update", (update) => {
|
|
1904
|
+
const change = this.#groupParticipantsChanged(update);
|
|
1905
|
+
void this.#events.emit("groupParticipantsChanged", change);
|
|
1906
|
+
const { id } = update;
|
|
1823
1907
|
void this.#events.emit("groupChanged", { groupId: id });
|
|
1824
1908
|
});
|
|
1825
1909
|
socket.ev.on("call", (calls) => {
|
|
@@ -1919,6 +2003,9 @@ var BaileysProvider = class {
|
|
|
1919
2003
|
...content.gif !== void 0 ? { gifPlayback: content.gif } : {}
|
|
1920
2004
|
};
|
|
1921
2005
|
}
|
|
2006
|
+
if ("sticker" in content) {
|
|
2007
|
+
return { sticker: this.#media(content.sticker) };
|
|
2008
|
+
}
|
|
1922
2009
|
return {
|
|
1923
2010
|
audio: this.#media(content.audio),
|
|
1924
2011
|
...content.mimetype ? { mimetype: content.mimetype } : {},
|
|
@@ -1949,7 +2036,7 @@ var BaileysProvider = class {
|
|
|
1949
2036
|
if (!message?.key.id || !message.key.remoteJid) {
|
|
1950
2037
|
throw new WhaNextError("PROVIDER_ERROR", "WhatsApp did not confirm the sent message.");
|
|
1951
2038
|
}
|
|
1952
|
-
if (message.message) this.#remember(message
|
|
2039
|
+
if (message.message) this.#remember(message);
|
|
1953
2040
|
return {
|
|
1954
2041
|
id: message.key.id,
|
|
1955
2042
|
chatId: message.key.remoteJid,
|
|
@@ -1968,6 +2055,15 @@ var BaileysProvider = class {
|
|
|
1968
2055
|
date: call.date ?? /* @__PURE__ */ new Date()
|
|
1969
2056
|
};
|
|
1970
2057
|
}
|
|
2058
|
+
#groupParticipantsChanged(change) {
|
|
2059
|
+
const participantIds = change.participants.map((participant) => participant.id).filter((id) => Boolean(id));
|
|
2060
|
+
return {
|
|
2061
|
+
groupId: change.id,
|
|
2062
|
+
action: change.action,
|
|
2063
|
+
participantIds,
|
|
2064
|
+
...change.author ? { authorId: change.author } : {}
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
1971
2067
|
#callStatus(status) {
|
|
1972
2068
|
const known = [
|
|
1973
2069
|
"offer",
|
|
@@ -1979,13 +2075,19 @@ var BaileysProvider = class {
|
|
|
1979
2075
|
];
|
|
1980
2076
|
return known.find((value) => value === status) ?? "timeout";
|
|
1981
2077
|
}
|
|
1982
|
-
#remember(
|
|
1983
|
-
this.#
|
|
1984
|
-
|
|
2078
|
+
#remember(message) {
|
|
2079
|
+
const key = this.#messageStoreKey(message.key);
|
|
2080
|
+
this.#messageStore.delete(key);
|
|
2081
|
+
this.#messageStore.set(key, message);
|
|
2082
|
+
while (this.#messageStore.size > this.#messageCacheSize) {
|
|
1985
2083
|
const oldest = this.#messageStore.keys().next().value;
|
|
1986
2084
|
if (oldest) this.#messageStore.delete(oldest);
|
|
1987
2085
|
}
|
|
1988
2086
|
}
|
|
2087
|
+
#messageStoreKey(key) {
|
|
2088
|
+
const chatId = "chatId" in key ? key.chatId : key.remoteJid;
|
|
2089
|
+
return `${chatId ?? ""}:${key.id ?? ""}`;
|
|
2090
|
+
}
|
|
1989
2091
|
};
|
|
1990
2092
|
|
|
1991
2093
|
// src/app/create.ts
|
|
@@ -1995,6 +2097,7 @@ async function create(options = {}) {
|
|
|
1995
2097
|
auth: options.auth ?? "./session",
|
|
1996
2098
|
browser: options.browser ?? "windows" /* Windows */,
|
|
1997
2099
|
logger: logger.child("provider"),
|
|
2100
|
+
...options.messageCacheSize !== void 0 ? { messageCacheSize: options.messageCacheSize } : {},
|
|
1998
2101
|
...options.reconnect ? { reconnect: options.reconnect } : {}
|
|
1999
2102
|
});
|
|
2000
2103
|
return new WhaNextApp(provider, {
|