@kangwifi-pro/waliwa 1.0.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/LICENSE +21 -0
- package/README.md +600 -0
- package/dist/advanced/features.d.ts +211 -0
- package/dist/advanced/features.d.ts.map +1 -0
- package/dist/advanced/features.js +671 -0
- package/dist/advanced/features.js.map +1 -0
- package/dist/auth/auth-state.d.ts +89 -0
- package/dist/auth/auth-state.d.ts.map +1 -0
- package/dist/auth/auth-state.js +330 -0
- package/dist/auth/auth-state.js.map +1 -0
- package/dist/auth/pairing-code.d.ts +78 -0
- package/dist/auth/pairing-code.d.ts.map +1 -0
- package/dist/auth/pairing-code.js +191 -0
- package/dist/auth/pairing-code.js.map +1 -0
- package/dist/auth/qr-code.d.ts +60 -0
- package/dist/auth/qr-code.d.ts.map +1 -0
- package/dist/auth/qr-code.js +151 -0
- package/dist/auth/qr-code.js.map +1 -0
- package/dist/calls/handler.d.ts +61 -0
- package/dist/calls/handler.d.ts.map +1 -0
- package/dist/calls/handler.js +117 -0
- package/dist/calls/handler.js.map +1 -0
- package/dist/core/binary.d.ts +74 -0
- package/dist/core/binary.d.ts.map +1 -0
- package/dist/core/binary.js +448 -0
- package/dist/core/binary.js.map +1 -0
- package/dist/core/crypto.d.ts +60 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +170 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/noise.d.ts +106 -0
- package/dist/core/noise.d.ts.map +1 -0
- package/dist/core/noise.js +307 -0
- package/dist/core/noise.js.map +1 -0
- package/dist/events/emitter.d.ts +44 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +79 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/features/index.d.ts +342 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +755 -0
- package/dist/features/index.js.map +1 -0
- package/dist/fixes/index.d.ts +333 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +762 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/groups/management.d.ts +86 -0
- package/dist/groups/management.d.ts.map +1 -0
- package/dist/groups/management.js +443 -0
- package/dist/groups/management.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +236 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/media.d.ts +93 -0
- package/dist/messages/media.d.ts.map +1 -0
- package/dist/messages/media.js +252 -0
- package/dist/messages/media.js.map +1 -0
- package/dist/messages/message-encoder.d.ts +70 -0
- package/dist/messages/message-encoder.d.ts.map +1 -0
- package/dist/messages/message-encoder.js +453 -0
- package/dist/messages/message-encoder.js.map +1 -0
- package/dist/messages/send.d.ts +101 -0
- package/dist/messages/send.d.ts.map +1 -0
- package/dist/messages/send.js +409 -0
- package/dist/messages/send.js.map +1 -0
- package/dist/recovery/index.d.ts +125 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +584 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/skdm/index.d.ts +220 -0
- package/dist/skdm/index.d.ts.map +1 -0
- package/dist/skdm/index.js +600 -0
- package/dist/skdm/index.js.map +1 -0
- package/dist/socket/hybrid.d.ts +118 -0
- package/dist/socket/hybrid.d.ts.map +1 -0
- package/dist/socket/hybrid.js +352 -0
- package/dist/socket/hybrid.js.map +1 -0
- package/dist/socket/wa-socket.d.ts +300 -0
- package/dist/socket/wa-socket.d.ts.map +1 -0
- package/dist/socket/wa-socket.js +1094 -0
- package/dist/socket/wa-socket.js.map +1 -0
- package/dist/socket/ws-socket.d.ts +96 -0
- package/dist/socket/ws-socket.d.ts.map +1 -0
- package/dist/socket/ws-socket.js +302 -0
- package/dist/socket/ws-socket.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/jid.d.ts +46 -0
- package/dist/utils/jid.d.ts.map +1 -0
- package/dist/utils/jid.js +152 -0
- package/dist/utils/jid.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +175 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API.md +745 -0
- package/docs/ARCHITECTURE.md +307 -0
- package/docs/BAILEYS_FIXES.md +360 -0
- package/docs/FEATURES.md +532 -0
- package/docs/PROTOCOL.md +489 -0
- package/docs/RECOVERY.md +409 -0
- package/docs/SKDM.md +233 -0
- package/examples/ai-bot/index.ts +479 -0
- package/examples/ai-bot/package.json +17 -0
- package/examples/echo-bot/index.ts +171 -0
- package/examples/echo-bot/package.json +18 -0
- package/examples/group-bot/index.ts +557 -0
- package/examples/group-bot/package.json +17 -0
- package/examples/rest-gateway/index.ts +499 -0
- package/examples/rest-gateway/package.json +19 -0
- package/package.json +75 -0
- package/src/advanced/features.ts +817 -0
- package/src/auth/auth-state.ts +342 -0
- package/src/auth/pairing-code.ts +246 -0
- package/src/auth/qr-code.ts +191 -0
- package/src/calls/handler.ts +153 -0
- package/src/core/binary.ts +464 -0
- package/src/core/crypto.ts +189 -0
- package/src/core/noise.ts +406 -0
- package/src/events/emitter.ts +88 -0
- package/src/features/index.ts +921 -0
- package/src/fixes/index.ts +882 -0
- package/src/groups/management.ts +497 -0
- package/src/index.ts +274 -0
- package/src/messages/media.ts +372 -0
- package/src/messages/message-encoder.ts +520 -0
- package/src/messages/send.ts +521 -0
- package/src/recovery/index.ts +704 -0
- package/src/skdm/index.ts +693 -0
- package/src/socket/hybrid.ts +414 -0
- package/src/socket/wa-socket.ts +1347 -0
- package/src/socket/ws-socket.ts +355 -0
- package/src/types/index.ts +457 -0
- package/src/utils/jid.ts +156 -0
- package/src/utils/logger.ts +84 -0
- package/src/utils/retry.ts +205 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waliwa - Advanced features
|
|
3
|
+
* Polls, Newsletter, Status/Stories, Business Profile, Labels, Catalog
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
JIDLike,
|
|
8
|
+
WALogger,
|
|
9
|
+
WAMessage,
|
|
10
|
+
WAMessageKey,
|
|
11
|
+
PollUpdate
|
|
12
|
+
} from '../types/index.js';
|
|
13
|
+
import { encodeBinaryNode, BinaryNode } from '../core/binary.js';
|
|
14
|
+
import { jidToString } from '../utils/jid.js';
|
|
15
|
+
import { generateStanzaId, generateMessageId } from '../messages/message-encoder.js';
|
|
16
|
+
import { SilentLogger } from '../utils/logger.js';
|
|
17
|
+
|
|
18
|
+
// ============== Poll Manager ==============
|
|
19
|
+
|
|
20
|
+
export interface PollOptions {
|
|
21
|
+
name: string;
|
|
22
|
+
values: string[];
|
|
23
|
+
/** Max selectable options per user (default: 1 = single choice) */
|
|
24
|
+
selectableCounts?: number[];
|
|
25
|
+
/** Allow multiple answers */
|
|
26
|
+
multipleAnswers?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PollVote {
|
|
30
|
+
/** JID of voter */
|
|
31
|
+
voter: JIDLike;
|
|
32
|
+
/** Selected option indices */
|
|
33
|
+
selectedOptions: number[];
|
|
34
|
+
/** Timestamp */
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class PollManager {
|
|
39
|
+
private send: (data: Uint8Array) => void;
|
|
40
|
+
private registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
41
|
+
private logger: WALogger;
|
|
42
|
+
private voteListeners: Map<string, (vote: PollVote) => void> = new Map();
|
|
43
|
+
|
|
44
|
+
constructor(opts: {
|
|
45
|
+
send: (data: Uint8Array) => void;
|
|
46
|
+
registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
47
|
+
logger?: WALogger;
|
|
48
|
+
}) {
|
|
49
|
+
this.send = opts.send;
|
|
50
|
+
this.registerIqHandler = opts.registerIqHandler;
|
|
51
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create poll message
|
|
56
|
+
*/
|
|
57
|
+
async createPoll(jid: JIDLike, opts: PollOptions): Promise<WAMessageKey> {
|
|
58
|
+
const messageId = generateMessageId();
|
|
59
|
+
const selectableCounts = opts.selectableCounts ?? [opts.multipleAnswers ? opts.values.length : 1];
|
|
60
|
+
|
|
61
|
+
const pollNode: BinaryNode = {
|
|
62
|
+
tag: 'message',
|
|
63
|
+
attrs: {
|
|
64
|
+
to: jidToString(jid),
|
|
65
|
+
type: 'poll',
|
|
66
|
+
id: messageId
|
|
67
|
+
},
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
tag: 'pollCreationMessage',
|
|
71
|
+
attrs: {
|
|
72
|
+
name: opts.name,
|
|
73
|
+
selectableOptionsCount: JSON.stringify(selectableCounts)
|
|
74
|
+
},
|
|
75
|
+
content: opts.values.map((value, idx) => ({
|
|
76
|
+
tag: 'option',
|
|
77
|
+
attrs: {
|
|
78
|
+
index: idx.toString(),
|
|
79
|
+
name: value
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const data = encodeBinaryNode(pollNode);
|
|
87
|
+
this.send(data);
|
|
88
|
+
return {
|
|
89
|
+
remoteJid: jid,
|
|
90
|
+
fromMe: true,
|
|
91
|
+
id: messageId
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Vote on existing poll
|
|
97
|
+
*/
|
|
98
|
+
async voteOnPoll(
|
|
99
|
+
pollKey: WAMessageKey,
|
|
100
|
+
selectedOptions: number[]
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
const messageId = generateMessageId();
|
|
103
|
+
const voteNode: BinaryNode = {
|
|
104
|
+
tag: 'message',
|
|
105
|
+
attrs: {
|
|
106
|
+
to: jidToString(pollKey.remoteJid),
|
|
107
|
+
type: 'poll_vote',
|
|
108
|
+
id: messageId
|
|
109
|
+
},
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
tag: 'pollVoteMessage',
|
|
113
|
+
attrs: {
|
|
114
|
+
pollCreationMessageKey: JSON.stringify({
|
|
115
|
+
remoteJid: jidToString(pollKey.remoteJid),
|
|
116
|
+
fromMe: pollKey.fromMe,
|
|
117
|
+
id: pollKey.id
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
content: selectedOptions.map(idx => ({
|
|
121
|
+
tag: 'selectedOption',
|
|
122
|
+
attrs: { index: idx.toString() }
|
|
123
|
+
}))
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
};
|
|
127
|
+
this.send(encodeBinaryNode(voteNode));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Listen for poll votes
|
|
132
|
+
*/
|
|
133
|
+
onVote(pollMessageId: string, handler: (vote: PollVote) => void): void {
|
|
134
|
+
this.voteListeners.set(pollMessageId, handler);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handle incoming poll vote
|
|
139
|
+
*/
|
|
140
|
+
handlePollVote(node: BinaryNode): void {
|
|
141
|
+
if (node.tag !== 'message') return;
|
|
142
|
+
const attrs = node.attrs || {};
|
|
143
|
+
const from = attrs.from;
|
|
144
|
+
|
|
145
|
+
if (Array.isArray(node.content)) {
|
|
146
|
+
for (const child of node.content) {
|
|
147
|
+
if (child?.tag === 'pollVoteMessage') {
|
|
148
|
+
const pollKeyJson = child.attrs?.pollCreationMessageKey;
|
|
149
|
+
if (!pollKeyJson) continue;
|
|
150
|
+
try {
|
|
151
|
+
const pollKey = JSON.parse(pollKeyJson);
|
|
152
|
+
const listener = this.voteListeners.get(pollKey.id);
|
|
153
|
+
if (!listener) continue;
|
|
154
|
+
|
|
155
|
+
const selectedOptions = Array.isArray(child.content)
|
|
156
|
+
? child.content.filter(c => c?.tag === 'selectedOption').map(c => parseInt(c.attrs?.index ?? '0', 10))
|
|
157
|
+
: [];
|
|
158
|
+
listener({
|
|
159
|
+
voter: from,
|
|
160
|
+
selectedOptions,
|
|
161
|
+
timestamp: Date.now()
|
|
162
|
+
});
|
|
163
|
+
} catch (err: any) {
|
|
164
|
+
this.logger.warn(`Failed to parse poll vote: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============== Newsletter Manager ==============
|
|
173
|
+
|
|
174
|
+
export interface NewsletterMetadata {
|
|
175
|
+
id: JIDLike;
|
|
176
|
+
name: string;
|
|
177
|
+
description?: string;
|
|
178
|
+
subscribersCount: number;
|
|
179
|
+
state: 'active' | 'inactive';
|
|
180
|
+
createdAt?: number;
|
|
181
|
+
verification: 'verified' | 'unverified';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export class NewsletterManager {
|
|
185
|
+
private send: (data: Uint8Array) => void;
|
|
186
|
+
private registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
187
|
+
private logger: WALogger;
|
|
188
|
+
|
|
189
|
+
constructor(opts: {
|
|
190
|
+
send: (data: Uint8Array) => void;
|
|
191
|
+
registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
192
|
+
logger?: WALogger;
|
|
193
|
+
}) {
|
|
194
|
+
this.send = opts.send;
|
|
195
|
+
this.registerIqHandler = opts.registerIqHandler;
|
|
196
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create new newsletter
|
|
201
|
+
*/
|
|
202
|
+
async create(name: string, description?: string): Promise<NewsletterMetadata> {
|
|
203
|
+
const id = generateStanzaId();
|
|
204
|
+
const node: BinaryNode = {
|
|
205
|
+
tag: 'iq',
|
|
206
|
+
attrs: {
|
|
207
|
+
type: 'set',
|
|
208
|
+
to: 's.whatsapp.net',
|
|
209
|
+
id
|
|
210
|
+
},
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
tag: 'newsletter',
|
|
214
|
+
attrs: {
|
|
215
|
+
name,
|
|
216
|
+
description: description ?? ''
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
};
|
|
221
|
+
const response = await this.sendIqAndWait(node, id);
|
|
222
|
+
const nlNode = (response.content as any[])?.find(c => c.tag === 'newsletter');
|
|
223
|
+
return {
|
|
224
|
+
id: nlNode?.attrs?.id,
|
|
225
|
+
name,
|
|
226
|
+
description,
|
|
227
|
+
subscribersCount: 0,
|
|
228
|
+
state: 'active',
|
|
229
|
+
verification: 'unverified'
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Subscribe ke newsletter
|
|
235
|
+
*/
|
|
236
|
+
async subscribe(newsletterId: JIDLike): Promise<void> {
|
|
237
|
+
const id = generateStanzaId();
|
|
238
|
+
const node: BinaryNode = {
|
|
239
|
+
tag: 'iq',
|
|
240
|
+
attrs: {
|
|
241
|
+
type: 'set',
|
|
242
|
+
to: jidToString(newsletterId),
|
|
243
|
+
id
|
|
244
|
+
},
|
|
245
|
+
content: [{
|
|
246
|
+
tag: 'subscribe',
|
|
247
|
+
attrs: { action: 'subscribe' }
|
|
248
|
+
}]
|
|
249
|
+
};
|
|
250
|
+
await this.sendIqAndWait(node, id);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Unsubscribe dari newsletter
|
|
255
|
+
*/
|
|
256
|
+
async unsubscribe(newsletterId: JIDLike): Promise<void> {
|
|
257
|
+
const id = generateStanzaId();
|
|
258
|
+
const node: BinaryNode = {
|
|
259
|
+
tag: 'iq',
|
|
260
|
+
attrs: {
|
|
261
|
+
type: 'set',
|
|
262
|
+
to: jidToString(newsletterId),
|
|
263
|
+
id
|
|
264
|
+
},
|
|
265
|
+
content: [{
|
|
266
|
+
tag: 'subscribe',
|
|
267
|
+
attrs: { action: 'unsubscribe' }
|
|
268
|
+
}]
|
|
269
|
+
};
|
|
270
|
+
await this.sendIqAndWait(node, id);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Mute newsletter
|
|
275
|
+
*/
|
|
276
|
+
async mute(newsletterId: JIDLike): Promise<void> {
|
|
277
|
+
const id = generateStanzaId();
|
|
278
|
+
const node: BinaryNode = {
|
|
279
|
+
tag: 'iq',
|
|
280
|
+
attrs: {
|
|
281
|
+
type: 'set',
|
|
282
|
+
to: jidToString(newsletterId),
|
|
283
|
+
id
|
|
284
|
+
},
|
|
285
|
+
content: [{
|
|
286
|
+
tag: 'mute',
|
|
287
|
+
attrs: { action: 'mute' }
|
|
288
|
+
}]
|
|
289
|
+
};
|
|
290
|
+
await this.sendIqAndWait(node, id);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private sendIqAndWait(node: BinaryNode, id: string, timeoutMs: number = 15000): Promise<BinaryNode> {
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
const timeout = setTimeout(() => reject(new Error(`IQ ${id} timeout`)), timeoutMs);
|
|
296
|
+
this.registerIqHandler(id, (response) => {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
resolve(response);
|
|
299
|
+
});
|
|
300
|
+
this.send(encodeBinaryNode(node));
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ============== Status / Stories Manager ==============
|
|
306
|
+
|
|
307
|
+
export interface StatusOptions {
|
|
308
|
+
/** Background color for text-only status */
|
|
309
|
+
backgroundColor?: string;
|
|
310
|
+
/** Font for text status */
|
|
311
|
+
font?: number;
|
|
312
|
+
/** Caption */
|
|
313
|
+
caption?: string;
|
|
314
|
+
/** Status expiration in seconds (default 86400 = 24h) */
|
|
315
|
+
expiration?: number;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export class StatusManager {
|
|
319
|
+
private send: (data: Uint8Array) => void;
|
|
320
|
+
private logger: WALogger;
|
|
321
|
+
|
|
322
|
+
constructor(opts: { send: (data: Uint8Array) => void; logger?: WALogger }) {
|
|
323
|
+
this.send = opts.send;
|
|
324
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Upload text status
|
|
329
|
+
*/
|
|
330
|
+
async sendTextStatus(text: string, opts: StatusOptions = {}): Promise<WAMessageKey> {
|
|
331
|
+
const messageId = generateMessageId();
|
|
332
|
+
const node: BinaryNode = {
|
|
333
|
+
tag: 'message',
|
|
334
|
+
attrs: {
|
|
335
|
+
to: 'status@broadcast',
|
|
336
|
+
type: 'text',
|
|
337
|
+
id: messageId
|
|
338
|
+
},
|
|
339
|
+
content: [
|
|
340
|
+
{
|
|
341
|
+
tag: 'textStatusMessage',
|
|
342
|
+
attrs: {
|
|
343
|
+
text,
|
|
344
|
+
backgroundColor: opts.backgroundColor ?? '#000000',
|
|
345
|
+
font: (opts.font ?? 0).toString(),
|
|
346
|
+
expiration: ((opts.expiration ?? 86400) * 1000).toString()
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
};
|
|
351
|
+
this.send(encodeBinaryNode(node));
|
|
352
|
+
return {
|
|
353
|
+
remoteJid: 'status@broadcast',
|
|
354
|
+
fromMe: true,
|
|
355
|
+
id: messageId
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Upload media status
|
|
361
|
+
*/
|
|
362
|
+
async sendMediaStatus(
|
|
363
|
+
mediaType: 'image' | 'video',
|
|
364
|
+
mediaUrl: string,
|
|
365
|
+
opts: StatusOptions = {}
|
|
366
|
+
): Promise<WAMessageKey> {
|
|
367
|
+
const messageId = generateMessageId();
|
|
368
|
+
const tag = mediaType === 'image' ? 'imageStatusMessage' : 'videoStatusMessage';
|
|
369
|
+
const node: BinaryNode = {
|
|
370
|
+
tag: 'message',
|
|
371
|
+
attrs: {
|
|
372
|
+
to: 'status@broadcast',
|
|
373
|
+
type: mediaType,
|
|
374
|
+
id: messageId
|
|
375
|
+
},
|
|
376
|
+
content: [
|
|
377
|
+
{
|
|
378
|
+
tag,
|
|
379
|
+
attrs: {
|
|
380
|
+
url: mediaUrl,
|
|
381
|
+
caption: opts.caption ?? '',
|
|
382
|
+
expiration: ((opts.expiration ?? 86400) * 1000).toString()
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
};
|
|
387
|
+
this.send(encodeBinaryNode(node));
|
|
388
|
+
return {
|
|
389
|
+
remoteJid: 'status@broadcast',
|
|
390
|
+
fromMe: true,
|
|
391
|
+
id: messageId
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Delete own status
|
|
397
|
+
*/
|
|
398
|
+
async deleteStatus(statusId: string): Promise<void> {
|
|
399
|
+
const node: BinaryNode = {
|
|
400
|
+
tag: 'iq',
|
|
401
|
+
attrs: {
|
|
402
|
+
type: 'set',
|
|
403
|
+
to: 'status@broadcast',
|
|
404
|
+
id: `delstatus-${Date.now()}`
|
|
405
|
+
},
|
|
406
|
+
content: [{
|
|
407
|
+
tag: 'delete',
|
|
408
|
+
attrs: { id: statusId }
|
|
409
|
+
}]
|
|
410
|
+
};
|
|
411
|
+
this.send(encodeBinaryNode(node));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get status views
|
|
416
|
+
*/
|
|
417
|
+
async getStatusViews(statusId: string): Promise<JIDLike[]> {
|
|
418
|
+
const id = generateStanzaId();
|
|
419
|
+
const node: BinaryNode = {
|
|
420
|
+
tag: 'iq',
|
|
421
|
+
attrs: {
|
|
422
|
+
type: 'get',
|
|
423
|
+
to: 'status@broadcast',
|
|
424
|
+
id
|
|
425
|
+
},
|
|
426
|
+
content: [{
|
|
427
|
+
tag: 'views',
|
|
428
|
+
attrs: { id: statusId }
|
|
429
|
+
}]
|
|
430
|
+
};
|
|
431
|
+
// Send and wait for response (simplified - real impl uses IQ handler registry)
|
|
432
|
+
this.send(encodeBinaryNode(node));
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ============== Business Profile Manager ==============
|
|
438
|
+
|
|
439
|
+
export interface BusinessProfileOptions {
|
|
440
|
+
description?: string;
|
|
441
|
+
email?: string;
|
|
442
|
+
websites?: string[];
|
|
443
|
+
category?: string;
|
|
444
|
+
address?: string;
|
|
445
|
+
businessHours?: BusinessHours[];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export interface BusinessHours {
|
|
449
|
+
day: 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
|
|
450
|
+
openTime: string; // "09:00"
|
|
451
|
+
closeTime: string; // "17:00"
|
|
452
|
+
mode: 'open' | 'closed' | 'by_appointment';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export class BusinessProfileManager {
|
|
456
|
+
private send: (data: Uint8Array) => void;
|
|
457
|
+
private registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
458
|
+
private logger: WALogger;
|
|
459
|
+
|
|
460
|
+
constructor(opts: {
|
|
461
|
+
send: (data: Uint8Array) => void;
|
|
462
|
+
registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
463
|
+
logger?: WALogger;
|
|
464
|
+
}) {
|
|
465
|
+
this.send = opts.send;
|
|
466
|
+
this.registerIqHandler = opts.registerIqHandler;
|
|
467
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Update business profile
|
|
472
|
+
*/
|
|
473
|
+
async update(opts: BusinessProfileOptions): Promise<void> {
|
|
474
|
+
const id = generateStanzaId();
|
|
475
|
+
const content: any[] = [];
|
|
476
|
+
if (opts.description) {
|
|
477
|
+
content.push({ tag: 'description', attrs: {}, content: opts.description });
|
|
478
|
+
}
|
|
479
|
+
if (opts.email) {
|
|
480
|
+
content.push({ tag: 'email', attrs: {}, content: opts.email });
|
|
481
|
+
}
|
|
482
|
+
if (opts.websites?.length) {
|
|
483
|
+
content.push({
|
|
484
|
+
tag: 'websites',
|
|
485
|
+
attrs: {},
|
|
486
|
+
content: opts.websites.map(w => ({ tag: 'website', attrs: { url: w } }))
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (opts.category) {
|
|
490
|
+
content.push({ tag: 'category', attrs: { name: opts.category } });
|
|
491
|
+
}
|
|
492
|
+
if (opts.address) {
|
|
493
|
+
content.push({ tag: 'address', attrs: {}, content: opts.address });
|
|
494
|
+
}
|
|
495
|
+
if (opts.businessHours?.length) {
|
|
496
|
+
content.push({
|
|
497
|
+
tag: 'business_hours',
|
|
498
|
+
attrs: {},
|
|
499
|
+
content: opts.businessHours.map(h => ({
|
|
500
|
+
tag: 'business_hours_config',
|
|
501
|
+
attrs: {
|
|
502
|
+
day_of_week: h.day,
|
|
503
|
+
mode: h.mode,
|
|
504
|
+
open_time: h.openTime,
|
|
505
|
+
close_time: h.closeTime
|
|
506
|
+
}
|
|
507
|
+
}))
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const node: BinaryNode = {
|
|
512
|
+
tag: 'iq',
|
|
513
|
+
attrs: {
|
|
514
|
+
type: 'set',
|
|
515
|
+
to: 's.whatsapp.net',
|
|
516
|
+
id
|
|
517
|
+
},
|
|
518
|
+
content: [
|
|
519
|
+
{
|
|
520
|
+
tag: 'business_profile',
|
|
521
|
+
attrs: {},
|
|
522
|
+
content
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
};
|
|
526
|
+
await this.sendIqAndWait(node, id);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get business profile
|
|
531
|
+
*/
|
|
532
|
+
async get(jid: JIDLike): Promise<BusinessProfileOptions | null> {
|
|
533
|
+
const id = generateStanzaId();
|
|
534
|
+
const node: BinaryNode = {
|
|
535
|
+
tag: 'iq',
|
|
536
|
+
attrs: {
|
|
537
|
+
type: 'get',
|
|
538
|
+
to: jidToString(jid),
|
|
539
|
+
id
|
|
540
|
+
},
|
|
541
|
+
content: [{
|
|
542
|
+
tag: 'business_profile',
|
|
543
|
+
attrs: {}
|
|
544
|
+
}]
|
|
545
|
+
};
|
|
546
|
+
const response = await this.sendIqAndWait(node, id);
|
|
547
|
+
const profileNode = (response.content as any[])?.find(c => c.tag === 'business_profile');
|
|
548
|
+
if (!profileNode) return null;
|
|
549
|
+
|
|
550
|
+
const result: BusinessProfileOptions = {};
|
|
551
|
+
if (Array.isArray(profileNode.content)) {
|
|
552
|
+
for (const child of profileNode.content) {
|
|
553
|
+
switch (child.tag) {
|
|
554
|
+
case 'description':
|
|
555
|
+
result.description = typeof child.content === 'string' ? child.content : '';
|
|
556
|
+
break;
|
|
557
|
+
case 'email':
|
|
558
|
+
result.email = typeof child.content === 'string' ? child.content : '';
|
|
559
|
+
break;
|
|
560
|
+
case 'category':
|
|
561
|
+
result.category = child.attrs?.name;
|
|
562
|
+
break;
|
|
563
|
+
case 'address':
|
|
564
|
+
result.address = typeof child.content === 'string' ? child.content : '';
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private sendIqAndWait(node: BinaryNode, id: string, timeoutMs: number = 15000): Promise<BinaryNode> {
|
|
573
|
+
return new Promise((resolve, reject) => {
|
|
574
|
+
const timeout = setTimeout(() => reject(new Error(`IQ ${id} timeout`)), timeoutMs);
|
|
575
|
+
this.registerIqHandler(id, (response) => {
|
|
576
|
+
clearTimeout(timeout);
|
|
577
|
+
resolve(response);
|
|
578
|
+
});
|
|
579
|
+
this.send(encodeBinaryNode(node));
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ============== Catalog Manager ==============
|
|
585
|
+
|
|
586
|
+
export interface CatalogItem {
|
|
587
|
+
id: string;
|
|
588
|
+
name: string;
|
|
589
|
+
description?: string;
|
|
590
|
+
price: number;
|
|
591
|
+
currency: string;
|
|
592
|
+
url?: string;
|
|
593
|
+
imageUrl?: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export class CatalogManager {
|
|
597
|
+
private send: (data: Uint8Array) => void;
|
|
598
|
+
private registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
599
|
+
private logger: WALogger;
|
|
600
|
+
|
|
601
|
+
constructor(opts: {
|
|
602
|
+
send: (data: Uint8Array) => void;
|
|
603
|
+
registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
604
|
+
logger?: WALogger;
|
|
605
|
+
}) {
|
|
606
|
+
this.send = opts.send;
|
|
607
|
+
this.registerIqHandler = opts.registerIqHandler;
|
|
608
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Add product to catalog
|
|
613
|
+
*/
|
|
614
|
+
async addProduct(item: Omit<CatalogItem, 'id'>): Promise<string> {
|
|
615
|
+
const id = generateStanzaId();
|
|
616
|
+
const productId = `prod-${Date.now()}`;
|
|
617
|
+
const node: BinaryNode = {
|
|
618
|
+
tag: 'iq',
|
|
619
|
+
attrs: {
|
|
620
|
+
type: 'set',
|
|
621
|
+
to: 's.whatsapp.net',
|
|
622
|
+
id
|
|
623
|
+
},
|
|
624
|
+
content: [{
|
|
625
|
+
tag: 'product',
|
|
626
|
+
attrs: {
|
|
627
|
+
id: productId,
|
|
628
|
+
name: item.name,
|
|
629
|
+
price: item.price.toString(),
|
|
630
|
+
currency: item.currency
|
|
631
|
+
},
|
|
632
|
+
content: [
|
|
633
|
+
item.description ? { tag: 'description', attrs: {}, content: item.description } : null,
|
|
634
|
+
item.url ? { tag: 'url', attrs: {}, content: item.url } : null
|
|
635
|
+
].filter(Boolean) as any[]
|
|
636
|
+
}]
|
|
637
|
+
};
|
|
638
|
+
await this.sendIqAndWait(node, id);
|
|
639
|
+
return productId;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Get catalog for business account
|
|
644
|
+
*/
|
|
645
|
+
async getCatalog(jid: JIDLike): Promise<CatalogItem[]> {
|
|
646
|
+
const id = generateStanzaId();
|
|
647
|
+
const node: BinaryNode = {
|
|
648
|
+
tag: 'iq',
|
|
649
|
+
attrs: {
|
|
650
|
+
type: 'get',
|
|
651
|
+
to: jidToString(jid),
|
|
652
|
+
id
|
|
653
|
+
},
|
|
654
|
+
content: [{
|
|
655
|
+
tag: 'catalog',
|
|
656
|
+
attrs: {}
|
|
657
|
+
}]
|
|
658
|
+
};
|
|
659
|
+
const response = await this.sendIqAndWait(node, id);
|
|
660
|
+
const catalogNode = (response.content as any[])?.find(c => c.tag === 'catalog');
|
|
661
|
+
if (!catalogNode) return [];
|
|
662
|
+
|
|
663
|
+
const items: CatalogItem[] = [];
|
|
664
|
+
if (Array.isArray(catalogNode.content)) {
|
|
665
|
+
for (const child of catalogNode.content) {
|
|
666
|
+
if (child.tag === 'product') {
|
|
667
|
+
items.push({
|
|
668
|
+
id: child.attrs?.id,
|
|
669
|
+
name: child.attrs?.name,
|
|
670
|
+
description: typeof child.content === 'string' ? child.content : '',
|
|
671
|
+
price: parseFloat(child.attrs?.price ?? '0'),
|
|
672
|
+
currency: child.attrs?.currency ?? 'USD',
|
|
673
|
+
url: child.attrs?.url,
|
|
674
|
+
imageUrl: child.attrs?.imageUrl
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return items;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private sendIqAndWait(node: BinaryNode, id: string, timeoutMs: number = 15000): Promise<BinaryNode> {
|
|
683
|
+
return new Promise((resolve, reject) => {
|
|
684
|
+
const timeout = setTimeout(() => reject(new Error(`IQ ${id} timeout`)), timeoutMs);
|
|
685
|
+
this.registerIqHandler(id, (response) => {
|
|
686
|
+
clearTimeout(timeout);
|
|
687
|
+
resolve(response);
|
|
688
|
+
});
|
|
689
|
+
this.send(encodeBinaryNode(node));
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ============== Labels Manager ==============
|
|
695
|
+
|
|
696
|
+
export interface Label {
|
|
697
|
+
id: string;
|
|
698
|
+
name: string;
|
|
699
|
+
color: number;
|
|
700
|
+
priority?: number;
|
|
701
|
+
deleted?: boolean;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export class LabelsManager {
|
|
705
|
+
private send: (data: Uint8Array) => void;
|
|
706
|
+
private registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
707
|
+
private logger: WALogger;
|
|
708
|
+
|
|
709
|
+
constructor(opts: {
|
|
710
|
+
send: (data: Uint8Array) => void;
|
|
711
|
+
registerIqHandler: (id: string, handler: (node: BinaryNode) => void) => void;
|
|
712
|
+
logger?: WALogger;
|
|
713
|
+
}) {
|
|
714
|
+
this.send = opts.send;
|
|
715
|
+
this.registerIqHandler = opts.registerIqHandler;
|
|
716
|
+
this.logger = opts.logger ?? new SilentLogger();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Create label
|
|
721
|
+
*/
|
|
722
|
+
async create(name: string, color: number): Promise<Label> {
|
|
723
|
+
const id = generateStanzaId();
|
|
724
|
+
const labelId = `label-${Date.now()}`;
|
|
725
|
+
const node: BinaryNode = {
|
|
726
|
+
tag: 'iq',
|
|
727
|
+
attrs: {
|
|
728
|
+
type: 'set',
|
|
729
|
+
to: 's.whatsapp.net',
|
|
730
|
+
id
|
|
731
|
+
},
|
|
732
|
+
content: [{
|
|
733
|
+
tag: 'label',
|
|
734
|
+
attrs: {
|
|
735
|
+
id: labelId,
|
|
736
|
+
name,
|
|
737
|
+
color: color.toString()
|
|
738
|
+
}
|
|
739
|
+
}]
|
|
740
|
+
};
|
|
741
|
+
await this.sendIqAndWait(node, id);
|
|
742
|
+
return { id: labelId, name, color };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Assign label ke chat
|
|
747
|
+
*/
|
|
748
|
+
async assignToChat(labelId: string, chatJid: JIDLike): Promise<void> {
|
|
749
|
+
const id = generateStanzaId();
|
|
750
|
+
const node: BinaryNode = {
|
|
751
|
+
tag: 'iq',
|
|
752
|
+
attrs: {
|
|
753
|
+
type: 'set',
|
|
754
|
+
to: 's.whatsapp.net',
|
|
755
|
+
id
|
|
756
|
+
},
|
|
757
|
+
content: [{
|
|
758
|
+
tag: 'label',
|
|
759
|
+
attrs: {
|
|
760
|
+
id: labelId,
|
|
761
|
+
action: 'assign',
|
|
762
|
+
chat: jidToString(chatJid)
|
|
763
|
+
}
|
|
764
|
+
}]
|
|
765
|
+
};
|
|
766
|
+
await this.sendIqAndWait(node, id);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Get all labels
|
|
771
|
+
*/
|
|
772
|
+
async list(): Promise<Label[]> {
|
|
773
|
+
const id = generateStanzaId();
|
|
774
|
+
const node: BinaryNode = {
|
|
775
|
+
tag: 'iq',
|
|
776
|
+
attrs: {
|
|
777
|
+
type: 'get',
|
|
778
|
+
to: 's.whatsapp.net',
|
|
779
|
+
id
|
|
780
|
+
},
|
|
781
|
+
content: [{
|
|
782
|
+
tag: 'labels',
|
|
783
|
+
attrs: {}
|
|
784
|
+
}]
|
|
785
|
+
};
|
|
786
|
+
const response = await this.sendIqAndWait(node, id);
|
|
787
|
+
const labelsNode = (response.content as any[])?.find(c => c.tag === 'labels');
|
|
788
|
+
if (!labelsNode) return [];
|
|
789
|
+
|
|
790
|
+
const labels: Label[] = [];
|
|
791
|
+
if (Array.isArray(labelsNode.content)) {
|
|
792
|
+
for (const child of labelsNode.content) {
|
|
793
|
+
if (child.tag === 'label') {
|
|
794
|
+
labels.push({
|
|
795
|
+
id: child.attrs?.id,
|
|
796
|
+
name: child.attrs?.name,
|
|
797
|
+
color: parseInt(child.attrs?.color ?? '0', 10),
|
|
798
|
+
priority: child.attrs?.priority ? parseInt(child.attrs.priority, 10) : undefined,
|
|
799
|
+
deleted: child.attrs?.deleted === 'true'
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return labels;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private sendIqAndWait(node: BinaryNode, id: string, timeoutMs: number = 15000): Promise<BinaryNode> {
|
|
808
|
+
return new Promise((resolve, reject) => {
|
|
809
|
+
const timeout = setTimeout(() => reject(new Error(`IQ ${id} timeout`)), timeoutMs);
|
|
810
|
+
this.registerIqHandler(id, (response) => {
|
|
811
|
+
clearTimeout(timeout);
|
|
812
|
+
resolve(response);
|
|
813
|
+
});
|
|
814
|
+
this.send(encodeBinaryNode(node));
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|