@theotherwillembotha/node-red-whatsapp 0.0.55
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 +15 -0
- package/README.md +147 -0
- package/build/GenerateNodes.js +21 -0
- package/build/Nodes.html +1814 -0
- package/build/Nodes.js +24 -0
- package/build/Plugins.js +70 -0
- package/build/icons/whatsapp.png +0 -0
- package/build/index.js +21 -0
- package/build/whatsapp/node/WhatsappAccountConfigNode.js +36 -0
- package/build/whatsapp/node/WhatsappGroupConfigNode.js +45 -0
- package/build/whatsapp/node/WhatsappReceiveMessageNode.js +82 -0
- package/build/whatsapp/node/WhatsappSendMessageNode.js +95 -0
- package/build/whatsapp/service/WhatsappClient.js +869 -0
- package/build/whatsapp/service/WhatsappService.js +212 -0
- package/build/whatsapp/service/WhatsappStore.js +408 -0
- package/icons/whatsapp.png +0 -0
- package/package.json +71 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ChatType = exports.GroupClient = exports.WhatsappClient = exports.ALBUM_TIMEOUT_MS = void 0;
|
|
40
|
+
const baileys_1 = __importStar(require("baileys"));
|
|
41
|
+
const uuid_1 = require("uuid");
|
|
42
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
43
|
+
const pino_1 = __importDefault(require("pino"));
|
|
44
|
+
const WhatsappStore_1 = require("./WhatsappStore");
|
|
45
|
+
const usePairingCode = false;
|
|
46
|
+
const msgRetryCounterCache = new node_cache_1.default();
|
|
47
|
+
exports.ALBUM_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour default
|
|
48
|
+
class WhatsappClient {
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.onQRCodeListeners = [];
|
|
51
|
+
this.onConnectionSuccessListeners = [];
|
|
52
|
+
this.messageSubscribers = {};
|
|
53
|
+
this.groupClients = {};
|
|
54
|
+
this.pendingAlbums = new Map();
|
|
55
|
+
this.config = config;
|
|
56
|
+
}
|
|
57
|
+
clientConfig() {
|
|
58
|
+
return this.config;
|
|
59
|
+
}
|
|
60
|
+
details() {
|
|
61
|
+
//@ts-ignore
|
|
62
|
+
let userID = this.state.creds.me.id.replace(/:\d+(?=@)/, "");
|
|
63
|
+
return {
|
|
64
|
+
userID: userID,
|
|
65
|
+
msisdn: userID.substring(0, userID.indexOf("@"))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
onQRCode(listener) {
|
|
69
|
+
this.onQRCodeListeners.push(listener);
|
|
70
|
+
// when someone subscribes to the qr code and there is a current active QR code, send it out.
|
|
71
|
+
if (this.currentQRCode) {
|
|
72
|
+
listener(this.currentQRCode);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
groups() {
|
|
76
|
+
return this._groupService;
|
|
77
|
+
}
|
|
78
|
+
getGroupClient(groupId) {
|
|
79
|
+
let groupClient = this.groupClients[groupId];
|
|
80
|
+
if (!groupClient) {
|
|
81
|
+
this.groupClients[groupId] = groupClient = new GroupClient(this, groupId);
|
|
82
|
+
}
|
|
83
|
+
return groupClient;
|
|
84
|
+
}
|
|
85
|
+
subscribe(listener, filter) {
|
|
86
|
+
let subscriberID = (0, uuid_1.v4)();
|
|
87
|
+
filter = filter ? filter : {};
|
|
88
|
+
console.log("subscribe to whatsapp messages", filter);
|
|
89
|
+
this.messageSubscribers[subscriberID] = (message) => {
|
|
90
|
+
console.log("Received message: ", message);
|
|
91
|
+
if ((!filter.groupId || filter.groupId === message.chat.id) &&
|
|
92
|
+
(!filter.types || filter.types.length === 0 || filter.types.indexOf(message.type) > -1)) {
|
|
93
|
+
listener(message);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
return subscriberID;
|
|
97
|
+
}
|
|
98
|
+
unsubscribe(subscriber) {
|
|
99
|
+
Object.entries(this.messageSubscribers)
|
|
100
|
+
.filter(([key, value]) => value === subscriber)
|
|
101
|
+
.forEach(([key, value]) => delete this.messageSubscribers[key]);
|
|
102
|
+
}
|
|
103
|
+
subscribers() {
|
|
104
|
+
return Object.values(this.messageSubscribers);
|
|
105
|
+
}
|
|
106
|
+
async sendMessage(chatId, message) {
|
|
107
|
+
let result;
|
|
108
|
+
if (message.image) {
|
|
109
|
+
// send image, with optional caption if text is provided
|
|
110
|
+
result = await this.socket.sendMessage(chatId, {
|
|
111
|
+
image: message.image,
|
|
112
|
+
caption: message.text
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (message.text) {
|
|
116
|
+
// send text-only message
|
|
117
|
+
result = await this.socket.sendMessage(chatId, { text: message.text });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw new Error("Message must contain either text or image");
|
|
121
|
+
}
|
|
122
|
+
return { messageId: result.key.id };
|
|
123
|
+
}
|
|
124
|
+
onConnectionSuccess(listener) {
|
|
125
|
+
this.onConnectionSuccessListeners.push(listener);
|
|
126
|
+
}
|
|
127
|
+
async start() {
|
|
128
|
+
if (this.reconnectTimer) {
|
|
129
|
+
clearTimeout(this.reconnectTimer);
|
|
130
|
+
this.reconnectTimer = undefined;
|
|
131
|
+
}
|
|
132
|
+
console.log("starting client", this.clientConfig());
|
|
133
|
+
this.logger = (0, pino_1.default)({ timestamp: () => `,"time":"${new Date().toJSON()}"` }, pino_1.default.destination(this.config.fileStorageRoot + '/wa-logs.txt'));
|
|
134
|
+
this.logger.level = 'trace';
|
|
135
|
+
// attempt to load the local config.
|
|
136
|
+
const { state, saveCreds } = await (0, baileys_1.useMultiFileAuthState)(this.config.fileStorageRoot + "/" + this.config.localConnectionId);
|
|
137
|
+
this.state = state;
|
|
138
|
+
this.saveCreds = saveCreds;
|
|
139
|
+
// fetch latest version of WA Web
|
|
140
|
+
const { version, isLatest } = await (0, baileys_1.fetchLatestBaileysVersion)();
|
|
141
|
+
console.log(`using WA v${version.join('.')}, isLatest: ${isLatest}`);
|
|
142
|
+
// before creating the connection, create / open the database.
|
|
143
|
+
this.store = await WhatsappStore_1.WhatsappStore.createStore(this.config.localConnectionId, this.config.fileStorageRoot + "/" + this.config.localConnectionId);
|
|
144
|
+
// create the websocket.
|
|
145
|
+
this.socket = (0, baileys_1.default)({
|
|
146
|
+
version: version,
|
|
147
|
+
logger: this.logger,
|
|
148
|
+
//printQRInTerminal: !usePairingCode,
|
|
149
|
+
auth: {
|
|
150
|
+
creds: state.creds,
|
|
151
|
+
keys: (0, baileys_1.makeCacheableSignalKeyStore)(state.keys, this.logger),
|
|
152
|
+
},
|
|
153
|
+
msgRetryCounterCache,
|
|
154
|
+
generateHighQualityLinkPreview: true,
|
|
155
|
+
// uncomment to avoid receiving historic messages.
|
|
156
|
+
//shouldSyncHistoryMessage:() => false,
|
|
157
|
+
//getMessage
|
|
158
|
+
});
|
|
159
|
+
// create the services.
|
|
160
|
+
this._groupService = new GroupService(this.socket, this.store, this.state);
|
|
161
|
+
console.log("bound socket to client", this.clientConfig());
|
|
162
|
+
// connection.
|
|
163
|
+
this.socket.ev.on('connection.update', this.connection_update.bind(this));
|
|
164
|
+
this.socket.ev.on('creds.update', this.saveCreds.bind(this));
|
|
165
|
+
// contacts
|
|
166
|
+
this.socket.ev.on('contacts.upsert', this.contacts_received.bind(this));
|
|
167
|
+
this.socket.ev.on('contacts.update', this.contacts_updated.bind(this));
|
|
168
|
+
// messages
|
|
169
|
+
this.socket.ev.on('messaging-history.set', this.messaging_history.bind(this));
|
|
170
|
+
this.socket.ev.on('messages.upsert', this.messages_upsert.bind(this));
|
|
171
|
+
this.socket.ev.on('messages.update', this.messages_update.bind(this));
|
|
172
|
+
// groups
|
|
173
|
+
this.socket.ev.on('groups.upsert', this.groups_upsert.bind(this));
|
|
174
|
+
this.socket.ev.on('group-participants.update', this.group_participants_update.bind(this));
|
|
175
|
+
this.socket.ev.on('group.member-tag.update', this.group_member_tag_update.bind(this));
|
|
176
|
+
// other.
|
|
177
|
+
this.socket.ev.on('labels.edit', this.doNothing.bind(this));
|
|
178
|
+
this.socket.ev.on('presence.update', this.doNothing.bind(this));
|
|
179
|
+
this.socket.ev.on('message-receipt.update', this.doNothing.bind(this));
|
|
180
|
+
// handle socket events.
|
|
181
|
+
this.socket.ev.process(
|
|
182
|
+
// events is a map for event name => event data
|
|
183
|
+
async (events) => {
|
|
184
|
+
if (events["connection.update"] ||
|
|
185
|
+
events["creds.update"] ||
|
|
186
|
+
events["messaging-history.set"] ||
|
|
187
|
+
events["messages.upsert"] ||
|
|
188
|
+
events["messages.update"] ||
|
|
189
|
+
events["labels.edit"] ||
|
|
190
|
+
events["presence.update"] ||
|
|
191
|
+
events["message-receipt.update"] ||
|
|
192
|
+
events["contacts.update"] ||
|
|
193
|
+
events["groups.upsert"] ||
|
|
194
|
+
events["group-participants.update"] ||
|
|
195
|
+
events["group.member-tag.update"]) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// just spill everything for now.
|
|
199
|
+
console.log("EVENT! ", JSON.stringify(events));
|
|
200
|
+
});
|
|
201
|
+
// start album cleanup timer (check every 5 minutes)
|
|
202
|
+
this.albumCleanupTimer = setInterval(() => this.cleanupTimedOutAlbums(), 5 * 60 * 1000);
|
|
203
|
+
//*/
|
|
204
|
+
}
|
|
205
|
+
async messages_upsert(upsert) {
|
|
206
|
+
if (upsert.type === 'notify') {
|
|
207
|
+
upsert.messages.forEach(message => this.message_received(message, true));
|
|
208
|
+
}
|
|
209
|
+
if (upsert.type === 'append') {
|
|
210
|
+
upsert.messages.forEach(message => this.message_received(message, true));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async messages_update(updates) {
|
|
214
|
+
for (const update of updates) {
|
|
215
|
+
const loc = update.update.message?.liveLocationMessage;
|
|
216
|
+
if (!loc)
|
|
217
|
+
continue;
|
|
218
|
+
const remoteJid = update.key.remoteJid;
|
|
219
|
+
const chat = await this.store.chats().get(remoteJid);
|
|
220
|
+
const whatsappmessage = {
|
|
221
|
+
timestamp: Date.now() / 1000,
|
|
222
|
+
messageId: update.key.id,
|
|
223
|
+
chat: {
|
|
224
|
+
id: chat?.id ?? remoteJid,
|
|
225
|
+
name: chat?.name ?? remoteJid,
|
|
226
|
+
},
|
|
227
|
+
sender: {
|
|
228
|
+
id: update.key.participant ?? remoteJid,
|
|
229
|
+
name: update.key.participant ?? remoteJid,
|
|
230
|
+
me: !!update.key.fromMe,
|
|
231
|
+
},
|
|
232
|
+
type: WhatsappStore_1.MessageType.LiveLocation,
|
|
233
|
+
payload: {
|
|
234
|
+
latitude: loc.degreesLatitude,
|
|
235
|
+
longitude: loc.degreesLongitude,
|
|
236
|
+
sequenceNumber: loc.sequenceNumber != null ? String(loc.sequenceNumber) : undefined,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
Object.values(this.messageSubscribers).forEach(sub => sub(whatsappmessage));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async groups_upsert(groups) {
|
|
243
|
+
for (const group of groups) {
|
|
244
|
+
const chat = await this.store.chats().create(group.id, "", ChatType.Group, group.owner ?? "", group.subject, group.desc ?? "");
|
|
245
|
+
for (const participant of group.participants) {
|
|
246
|
+
const contactId = participant.phoneNumber ?? participant.id;
|
|
247
|
+
const contact = await this.store.contacts().get(contactId);
|
|
248
|
+
if (contact) {
|
|
249
|
+
chat.addParticipant(contact, WAClientHelper.parseRole(participant.admin));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
await this.store.chats().save(chat);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async group_participants_update(event) {
|
|
256
|
+
const groupId = event.id;
|
|
257
|
+
const myJid = this.state.creds.me.id.replace(/:\d+(?=@)/, "");
|
|
258
|
+
if (event.action === "remove") {
|
|
259
|
+
// check if the linked account itself was removed from the group.
|
|
260
|
+
const selfRemoved = event.participants.some(p => p.phoneNumber === myJid || p.id === myJid);
|
|
261
|
+
if (selfRemoved) {
|
|
262
|
+
console.log(`Linked account removed from group ${groupId} — deleting from store`);
|
|
263
|
+
await this.store.chats().delete(groupId);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// remove other participants from the store.
|
|
267
|
+
const contactIds = event.participants.map(p => p.phoneNumber ?? p.id);
|
|
268
|
+
await this.store.chats().removeParticipants(groupId, contactIds);
|
|
269
|
+
}
|
|
270
|
+
else if (event.action === "add") {
|
|
271
|
+
// load with participants relation — without it, addParticipant() initialises a fresh
|
|
272
|
+
// empty array and orphanedRowAction:"delete" would wipe all existing members on save.
|
|
273
|
+
const chat = await this.store.chats().getWithParticipants(groupId);
|
|
274
|
+
if (chat) {
|
|
275
|
+
for (const participant of event.participants) {
|
|
276
|
+
const contactId = participant.phoneNumber ?? participant.id;
|
|
277
|
+
const contact = await this.store.contacts().get(contactId);
|
|
278
|
+
if (contact) {
|
|
279
|
+
chat.addParticipant(contact, WAClientHelper.parseRole(participant.admin));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
await this.store.chats().save(chat);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else if (event.action === "promote" || event.action === "demote") {
|
|
286
|
+
const role = event.action === "promote" ? WhatsappStore_1.Role.Admin : WhatsappStore_1.Role.Member;
|
|
287
|
+
for (const participant of event.participants) {
|
|
288
|
+
const contactId = participant.phoneNumber ?? participant.id;
|
|
289
|
+
await this.store.chats().updateParticipantRole(groupId, contactId, role);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async group_member_tag_update(event) {
|
|
294
|
+
const contactId = event.participantAlt ?? event.participant;
|
|
295
|
+
await this.store.chats().updateParticipantLabel(event.groupId, contactId, event.label);
|
|
296
|
+
}
|
|
297
|
+
async connection_update(update) {
|
|
298
|
+
if (update.connection === 'close') {
|
|
299
|
+
const statusCode = update.lastDisconnect?.error?.output?.statusCode;
|
|
300
|
+
if (statusCode === baileys_1.DisconnectReason.loggedOut) {
|
|
301
|
+
console.log(`WhatsApp [${this.config.localConnectionId}]: logged out.`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// restartRequired (515) is the normal post-QR-scan restart; use a shorter backoff.
|
|
305
|
+
// All other close reasons (network drop, WebSocket error, etc.) use a longer backoff.
|
|
306
|
+
const delayMs = statusCode === baileys_1.DisconnectReason.restartRequired ? 3000 : 5000;
|
|
307
|
+
this.scheduleReconnect(delayMs, statusCode);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (update.connection === 'open') {
|
|
311
|
+
console.log(`WhatsApp [${this.config.localConnectionId}]: connected.`);
|
|
312
|
+
this.onConnectionSuccessListeners.forEach(listener => listener());
|
|
313
|
+
// fetch recent message history on connect.
|
|
314
|
+
let count = 10;
|
|
315
|
+
let oldestMessageKey = {};
|
|
316
|
+
let oldestMessageTimestamp = new Date().getTime() - (10 * 24 * 60 * 1000);
|
|
317
|
+
this.socket.fetchMessageHistory(count, oldestMessageKey, oldestMessageTimestamp);
|
|
318
|
+
}
|
|
319
|
+
if (update.qr) {
|
|
320
|
+
// uncomment to render QRCode to console
|
|
321
|
+
// QRCode.toString(update.qr, {type:"terminal"}, function (err:any, imageString:any) {
|
|
322
|
+
// if (err) throw err
|
|
323
|
+
// console.log(imageString);
|
|
324
|
+
// })
|
|
325
|
+
this.currentQRCode = update.qr;
|
|
326
|
+
this.onQRCodeListeners.forEach(listener => listener(update.qr));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
scheduleReconnect(delayMs, statusCode) {
|
|
330
|
+
if (this.reconnectTimer) {
|
|
331
|
+
clearTimeout(this.reconnectTimer);
|
|
332
|
+
}
|
|
333
|
+
console.log(`WhatsApp [${this.config.localConnectionId}]: connection closed (code: ${statusCode}). Reconnecting in ${delayMs / 1000}s...`);
|
|
334
|
+
this.reconnectTimer = setTimeout(() => {
|
|
335
|
+
this.reconnectTimer = undefined;
|
|
336
|
+
this.start();
|
|
337
|
+
}, delayMs);
|
|
338
|
+
}
|
|
339
|
+
async stop() {
|
|
340
|
+
if (this.reconnectTimer) {
|
|
341
|
+
clearTimeout(this.reconnectTimer);
|
|
342
|
+
this.reconnectTimer = undefined;
|
|
343
|
+
}
|
|
344
|
+
if (this.albumCleanupTimer) {
|
|
345
|
+
clearInterval(this.albumCleanupTimer);
|
|
346
|
+
this.albumCleanupTimer = undefined;
|
|
347
|
+
}
|
|
348
|
+
return this.socket.logout();
|
|
349
|
+
}
|
|
350
|
+
cleanupTimedOutAlbums() {
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
for (const [chatId, album] of this.pendingAlbums) {
|
|
353
|
+
if (now - album.createdAt > exports.ALBUM_TIMEOUT_MS) {
|
|
354
|
+
console.log(`Album timeout: finalizing album ${album.albumMessageId} for chat ${chatId} (received ${album.receivedImages}/${album.expectedImages} images, ${album.receivedVideos}/${album.expectedVideos} videos)`);
|
|
355
|
+
this.pendingAlbums.delete(chatId);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async contacts_received(contacts) {
|
|
360
|
+
// sample { "id": "<phone>@s.whatsapp.net", "notify": "<name>" },
|
|
361
|
+
// sample { "id": "<groupid>@g.us", "name": "<group_name>" },
|
|
362
|
+
// sample { "id": "0@s.whatsapp.net", "name": "WhatsApp Business" },
|
|
363
|
+
// sample { "id": "<phone>@s.whatsapp.net", "phoneNumber": "<phone>@s.whatsapp.net", "verifiedName": "<business_name>" },
|
|
364
|
+
await Promise.all(contacts.map(async (newContact) => {
|
|
365
|
+
// find the contact by id.
|
|
366
|
+
if (newContact.id.endsWith("@lid")) {
|
|
367
|
+
console.log("contacts_received: FOUND Contact referenced by lid", newContact);
|
|
368
|
+
}
|
|
369
|
+
let contact = await this.store.contacts().get(newContact.id);
|
|
370
|
+
// update the phone number
|
|
371
|
+
if (newContact.phoneNumber) {
|
|
372
|
+
contact.phoneNumber = newContact.phoneNumber;
|
|
373
|
+
}
|
|
374
|
+
;
|
|
375
|
+
// verified name > name > notify;
|
|
376
|
+
if (newContact.verifiedName) {
|
|
377
|
+
contact.name = newContact.verifiedName;
|
|
378
|
+
}
|
|
379
|
+
;
|
|
380
|
+
if (newContact.name && !newContact.verifiedName) {
|
|
381
|
+
contact.name = newContact.name;
|
|
382
|
+
}
|
|
383
|
+
;
|
|
384
|
+
if (newContact.notify && !newContact.name && !newContact.verifiedName) {
|
|
385
|
+
contact.name = newContact.name;
|
|
386
|
+
}
|
|
387
|
+
;
|
|
388
|
+
// set the lid.
|
|
389
|
+
if (newContact.lid) {
|
|
390
|
+
contact.lid = newContact.lid;
|
|
391
|
+
}
|
|
392
|
+
;
|
|
393
|
+
return this.store.contacts().save(contact);
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
async contacts_updated(contacts) {
|
|
397
|
+
// sample 'contacts.update': [ { id: '94953791303764@lid', imgUrl: 'changed' } ]
|
|
398
|
+
console.log("Handle Contacts.Updated", JSON.stringify(contacts));
|
|
399
|
+
}
|
|
400
|
+
async messaging_history(history) {
|
|
401
|
+
if (history.contacts) {
|
|
402
|
+
await this.contacts_received(history.contacts);
|
|
403
|
+
}
|
|
404
|
+
if (history.chats) {
|
|
405
|
+
await this.chats_receieved(history.chats);
|
|
406
|
+
}
|
|
407
|
+
if (history.messages) {
|
|
408
|
+
await Promise.all(history.messages.map(async (message) => {
|
|
409
|
+
return this.message_received(message, false);
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async chats_receieved(chats) {
|
|
414
|
+
await Promise.all(chats.map(async (chat) => {
|
|
415
|
+
//console.log("new chat: ", JSON.stringify(chat));
|
|
416
|
+
let id = chat.id;
|
|
417
|
+
let type = (id.endsWith("@g.us")) ? ChatType.Group : ChatType.Conversation;
|
|
418
|
+
let lid = id.endsWith("@lid") ? id : "";
|
|
419
|
+
let persistedChat;
|
|
420
|
+
if (type === ChatType.Group) {
|
|
421
|
+
// fetch the chat / group metadata.
|
|
422
|
+
let metadata = await this.socket.groupMetadata(chat.id);
|
|
423
|
+
//console.log("CHAT GROUP METADATA", JSON.stringify(metadata, null, 2));
|
|
424
|
+
// persist the chat.
|
|
425
|
+
persistedChat = await this.store.chats().create(id, lid, type, metadata.owner, metadata.subject, metadata.desc);
|
|
426
|
+
for (let participant of metadata.participants) {
|
|
427
|
+
if (participant.phoneNumber && participant.phoneNumber.endsWith("@lid")) {
|
|
428
|
+
console.log("chats_receieved: FOUND Contact referenced by lid", participant.phoneNumber);
|
|
429
|
+
}
|
|
430
|
+
// the phone number will usually be included.
|
|
431
|
+
let contact = await this.store.contacts().get(participant.phoneNumber);
|
|
432
|
+
if (!contact) {
|
|
433
|
+
console.log("contact " + participant.id + " not found for chat " + chat.id, metadata);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
//console.log("adding participant ", persistedChat.id, participant);
|
|
437
|
+
persistedChat.addParticipant(contact, WAClientHelper.parseRole(participant.admin));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
this.store.chats().save(persistedChat);
|
|
441
|
+
}
|
|
442
|
+
if (type === ChatType.Conversation) {
|
|
443
|
+
persistedChat = await this.store.chats().create(id, lid, type, "", "", "");
|
|
444
|
+
this.store.chats().save(persistedChat);
|
|
445
|
+
}
|
|
446
|
+
// after persisting the chat, check if it has some messages attached to it.
|
|
447
|
+
let messages = chat.messages ? chat.messages.map(message => message.message) : [];
|
|
448
|
+
return await Promise.all(messages.map(message => {
|
|
449
|
+
return this.message_received(message, false);
|
|
450
|
+
})).then(() => persistedChat);
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
async message_received(message, live = true) {
|
|
454
|
+
// skip protocol messages (internal WhatsApp sync, not user messages)
|
|
455
|
+
if (message.message?.protocolMessage) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
// skip MASK_LINKED_DEVICES placeholder — internal linked-device notification, not a user message
|
|
459
|
+
if (message.message?.placeholderMessage?.type === baileys_1.proto.Message.PlaceholderMessage.PlaceholderType.MASK_LINKED_DEVICES) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// find the chat for this message.
|
|
463
|
+
let chat = await this.store.chats().get(message.key?.remoteJid);
|
|
464
|
+
let id = message.key?.id;
|
|
465
|
+
let timestamp = message.messageTimestamp ? Number(message.messageTimestamp) : 0;
|
|
466
|
+
// verify the chat object.
|
|
467
|
+
if (!chat) {
|
|
468
|
+
console.log("could not find chat for", JSON.stringify(message.key));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// lookup sender contact
|
|
472
|
+
let sender = await this.lookupOrCreateSender(message);
|
|
473
|
+
// figure out the type of the message.
|
|
474
|
+
let messageType = message.message?.conversation ? WhatsappStore_1.MessageType.Text :
|
|
475
|
+
message.message?.imageMessage ? WhatsappStore_1.MessageType.Image :
|
|
476
|
+
message.message?.videoMessage ? WhatsappStore_1.MessageType.Video :
|
|
477
|
+
message.message?.documentMessage ? WhatsappStore_1.MessageType.Document :
|
|
478
|
+
message.message?.templateMessage ? WhatsappStore_1.MessageType.Template :
|
|
479
|
+
message.message?.interactiveMessage ? WhatsappStore_1.MessageType.Interactive :
|
|
480
|
+
message.message?.extendedTextMessage ? WhatsappStore_1.MessageType.ExtendedText :
|
|
481
|
+
message.message?.albumMessage ? WhatsappStore_1.MessageType.Album :
|
|
482
|
+
message.message?.contactMessage ? WhatsappStore_1.MessageType.Contact :
|
|
483
|
+
message.message?.locationMessage ? WhatsappStore_1.MessageType.Location :
|
|
484
|
+
message.message?.liveLocationMessage ? WhatsappStore_1.MessageType.LiveLocation :
|
|
485
|
+
message.messageStubType ? WhatsappStore_1.MessageType.Stub :
|
|
486
|
+
WhatsappStore_1.MessageType.Unknown;
|
|
487
|
+
if (messageType === WhatsappStore_1.MessageType.Unknown) {
|
|
488
|
+
if (baileys_1.proto.WebMessageInfo.StubType.BIZ_PRIVACY_MODE_TO_FB === message.messageStubType) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
let messageContents;
|
|
493
|
+
let subscriberPayload; // for image/video, includes binary data not stored in db
|
|
494
|
+
// handle a text message.
|
|
495
|
+
if (messageType === WhatsappStore_1.MessageType.Text) {
|
|
496
|
+
messageContents = { text: message.message?.conversation };
|
|
497
|
+
}
|
|
498
|
+
// handle an image message.
|
|
499
|
+
if (messageType === WhatsappStore_1.MessageType.Image) {
|
|
500
|
+
let chatId = message.key?.remoteJid;
|
|
501
|
+
let albumId = undefined;
|
|
502
|
+
// check for pending album
|
|
503
|
+
let pendingAlbum = this.pendingAlbums.get(chatId);
|
|
504
|
+
if (pendingAlbum) {
|
|
505
|
+
albumId = pendingAlbum.albumMessageId;
|
|
506
|
+
pendingAlbum.receivedImages++;
|
|
507
|
+
pendingAlbum.mediaIds.push(id);
|
|
508
|
+
// check if album is complete
|
|
509
|
+
if (pendingAlbum.receivedImages >= pendingAlbum.expectedImages &&
|
|
510
|
+
pendingAlbum.receivedVideos >= pendingAlbum.expectedVideos) {
|
|
511
|
+
console.log(`Album complete: ${albumId} with ${pendingAlbum.mediaIds.length} media items`);
|
|
512
|
+
this.pendingAlbums.delete(chatId);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// extract metadata from image message
|
|
516
|
+
const imageMsg = message.message?.imageMessage;
|
|
517
|
+
messageContents = {
|
|
518
|
+
mimetype: imageMsg.mimetype,
|
|
519
|
+
width: imageMsg.width,
|
|
520
|
+
height: imageMsg.height,
|
|
521
|
+
fileLength: imageMsg.fileLength ? Number(imageMsg.fileLength) : undefined,
|
|
522
|
+
caption: imageMsg.caption,
|
|
523
|
+
albumId: albumId,
|
|
524
|
+
};
|
|
525
|
+
// only download media for live messages — historical URLs expire quickly and will 403
|
|
526
|
+
if (live) {
|
|
527
|
+
try {
|
|
528
|
+
const imageData = await (0, baileys_1.downloadMediaMessage)(message, 'buffer', {}, { logger: this.logger, reuploadRequest: this.socket.updateMediaMessage });
|
|
529
|
+
subscriberPayload = { ...messageContents, data: imageData };
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
console.log("Failed to download image:", error);
|
|
533
|
+
subscriberPayload = { ...messageContents, data: null };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// handle a video message.
|
|
538
|
+
if (messageType === WhatsappStore_1.MessageType.Video) {
|
|
539
|
+
let chatId = message.key?.remoteJid;
|
|
540
|
+
let albumId = undefined;
|
|
541
|
+
// check for pending album
|
|
542
|
+
let pendingAlbum = this.pendingAlbums.get(chatId);
|
|
543
|
+
if (pendingAlbum) {
|
|
544
|
+
albumId = pendingAlbum.albumMessageId;
|
|
545
|
+
pendingAlbum.receivedVideos++;
|
|
546
|
+
pendingAlbum.mediaIds.push(id);
|
|
547
|
+
// check if album is complete
|
|
548
|
+
if (pendingAlbum.receivedImages >= pendingAlbum.expectedImages &&
|
|
549
|
+
pendingAlbum.receivedVideos >= pendingAlbum.expectedVideos) {
|
|
550
|
+
console.log(`Album complete: ${albumId} with ${pendingAlbum.mediaIds.length} media items`);
|
|
551
|
+
this.pendingAlbums.delete(chatId);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// extract metadata from video message
|
|
555
|
+
const videoMsg = message.message?.videoMessage;
|
|
556
|
+
messageContents = {
|
|
557
|
+
mimetype: videoMsg.mimetype,
|
|
558
|
+
width: videoMsg.width,
|
|
559
|
+
height: videoMsg.height,
|
|
560
|
+
fileLength: videoMsg.fileLength ? Number(videoMsg.fileLength) : undefined,
|
|
561
|
+
seconds: videoMsg.seconds,
|
|
562
|
+
caption: videoMsg.caption,
|
|
563
|
+
albumId: albumId,
|
|
564
|
+
};
|
|
565
|
+
// only download media for live messages — historical URLs expire quickly and will 403
|
|
566
|
+
if (live) {
|
|
567
|
+
try {
|
|
568
|
+
const videoData = await (0, baileys_1.downloadMediaMessage)(message, 'buffer', {}, { logger: this.logger, reuploadRequest: this.socket.updateMediaMessage });
|
|
569
|
+
subscriberPayload = { ...messageContents, data: videoData };
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
console.log("Failed to download video:", error);
|
|
573
|
+
subscriberPayload = { ...messageContents, data: null };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (messageType === WhatsappStore_1.MessageType.Document) {
|
|
578
|
+
const docMsg = message.message?.documentMessage;
|
|
579
|
+
messageContents = {
|
|
580
|
+
filename: docMsg.fileName,
|
|
581
|
+
mimetype: docMsg.mimetype,
|
|
582
|
+
fileLength: docMsg.fileLength ? Number(docMsg.fileLength) : undefined,
|
|
583
|
+
pageCount: docMsg.pageCount,
|
|
584
|
+
caption: docMsg.caption,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (messageType === WhatsappStore_1.MessageType.Template) {
|
|
588
|
+
// the sensible way to handle this for now is to change it to a Text Mesasge.
|
|
589
|
+
messageContents = {
|
|
590
|
+
contentText: message.message.templateMessage.hydratedTemplate.hydratedContentText,
|
|
591
|
+
footerText: message.message.templateMessage.hydratedTemplate.hydratedFooterText,
|
|
592
|
+
commands: []
|
|
593
|
+
};
|
|
594
|
+
if (message.message.templateMessage.hydratedTemplate.hydratedButtons) {
|
|
595
|
+
for (let button of message.message.templateMessage.hydratedTemplate.hydratedButtons) {
|
|
596
|
+
if (button.urlButton) {
|
|
597
|
+
messageContents.commands.push({ text: button.urlButton.displayText, url: button.urlButton.url });
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (button.quickReplyButton) {
|
|
601
|
+
messageContents.commands.push({ text: button.quickReplyButton.displayText, id: button.quickReplyButton.id });
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
console.log("UNHANDLED TEMPLATE BUTTON", message.message.templateMessage.hydratedTemplate.hydratedButtons);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (messageType === WhatsappStore_1.MessageType.Interactive) {
|
|
609
|
+
let msg = message.message.interactiveMessage;
|
|
610
|
+
messageContents = {
|
|
611
|
+
headerText: msg.header,
|
|
612
|
+
contentText: msg.body,
|
|
613
|
+
footerText: msg.footer,
|
|
614
|
+
commands: [],
|
|
615
|
+
};
|
|
616
|
+
if (msg.nativeFlowMessage?.buttons) {
|
|
617
|
+
for (let button of msg.nativeFlowMessage.buttons) {
|
|
618
|
+
if (button.buttonParamsJson) {
|
|
619
|
+
let buttonMarkup = JSON.parse(button.buttonParamsJson);
|
|
620
|
+
messageContents.commands.push({ text: buttonMarkup.display_text, url: buttonMarkup.url });
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
console.log("UNHANDLED nativeFlowMessage BUTTON", msg);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (msg.carouselMessage || msg.collectionMessage || msg.shopStorefrontMessage || msg.contextInfo || msg.urlTrackingMap) {
|
|
627
|
+
console.log("UNHANDLED interactiveMessage Component", msg);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (messageType === WhatsappStore_1.MessageType.ExtendedText) {
|
|
631
|
+
messageContents = { text: message.message?.extendedTextMessage?.text };
|
|
632
|
+
}
|
|
633
|
+
// handle an album message.
|
|
634
|
+
if (messageType === WhatsappStore_1.MessageType.Album) {
|
|
635
|
+
let chatId = message.key?.remoteJid;
|
|
636
|
+
// if there's already a pending album for this chat, finalize it
|
|
637
|
+
if (this.pendingAlbums.has(chatId)) {
|
|
638
|
+
console.log(`Album interrupted: finalizing previous album for chat ${chatId}`);
|
|
639
|
+
this.pendingAlbums.delete(chatId);
|
|
640
|
+
}
|
|
641
|
+
// create new pending album
|
|
642
|
+
let albumMessage = message.message?.albumMessage;
|
|
643
|
+
this.pendingAlbums.set(chatId, {
|
|
644
|
+
chatId: chatId,
|
|
645
|
+
albumMessageId: id,
|
|
646
|
+
expectedImages: albumMessage.expectedImageCount ?? 0,
|
|
647
|
+
expectedVideos: albumMessage.expectedVideoCount ?? 0,
|
|
648
|
+
receivedImages: 0,
|
|
649
|
+
receivedVideos: 0,
|
|
650
|
+
createdAt: Date.now(),
|
|
651
|
+
mediaIds: []
|
|
652
|
+
});
|
|
653
|
+
messageContents = {
|
|
654
|
+
expectedImages: albumMessage.expectedImageCount,
|
|
655
|
+
expectedVideos: albumMessage.expectedVideoCount
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
if (messageType === WhatsappStore_1.MessageType.Contact) {
|
|
659
|
+
messageContents = {
|
|
660
|
+
name: message.message?.contactMessage?.displayName,
|
|
661
|
+
vcard: message.message?.contactMessage?.vcard,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (messageType === WhatsappStore_1.MessageType.Location) {
|
|
665
|
+
const loc = message.message?.locationMessage;
|
|
666
|
+
messageContents = {
|
|
667
|
+
latitude: loc.degreesLatitude,
|
|
668
|
+
longitude: loc.degreesLongitude,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (messageType === WhatsappStore_1.MessageType.LiveLocation) {
|
|
672
|
+
const loc = message.message?.liveLocationMessage;
|
|
673
|
+
messageContents = {
|
|
674
|
+
latitude: loc.degreesLatitude,
|
|
675
|
+
longitude: loc.degreesLongitude,
|
|
676
|
+
sequenceNumber: loc.sequenceNumber != null ? String(loc.sequenceNumber) : undefined,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (messageType === WhatsappStore_1.MessageType.Stub) {
|
|
680
|
+
// stub message. doesnt do much by the looks of it.
|
|
681
|
+
}
|
|
682
|
+
if (messageType === WhatsappStore_1.MessageType.Unknown) {
|
|
683
|
+
console.log("unkown new message: ", JSON.stringify(message, null, 2));
|
|
684
|
+
}
|
|
685
|
+
// persist the message.
|
|
686
|
+
await this.store.messages().create(chat, id, timestamp, messageType, JSON.stringify(messageContents, null, 2));
|
|
687
|
+
// send the message to the subscribers.
|
|
688
|
+
let whatsappmessage = {
|
|
689
|
+
timestamp: timestamp,
|
|
690
|
+
messageId: id,
|
|
691
|
+
chat: {
|
|
692
|
+
id: chat.id,
|
|
693
|
+
name: chat.name,
|
|
694
|
+
},
|
|
695
|
+
sender: {
|
|
696
|
+
id: sender?.id ?? "unknown",
|
|
697
|
+
name: sender?.getName() ?? "unknown",
|
|
698
|
+
me: !!message.key?.fromMe
|
|
699
|
+
},
|
|
700
|
+
type: messageType,
|
|
701
|
+
payload: subscriberPayload ?? messageContents,
|
|
702
|
+
};
|
|
703
|
+
Object.values(this.messageSubscribers).forEach(subscriber => subscriber(whatsappmessage));
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Lookup or create the sender contact from a message.
|
|
707
|
+
* For received messages, uses lid-based lookup first (if addressingMode is "lid"),
|
|
708
|
+
* then falls back to id-based lookup.
|
|
709
|
+
* Creates a new contact if not found, and back-populates the name from pushName if missing.
|
|
710
|
+
*/
|
|
711
|
+
async lookupOrCreateSender(message) {
|
|
712
|
+
const key = message.key;
|
|
713
|
+
if (!key)
|
|
714
|
+
return null;
|
|
715
|
+
const participant = key.participant; // lid format: "<lid>@lid"
|
|
716
|
+
const participantAlt = key.participantAlt; // id format: "<phone>@s.whatsapp.net"
|
|
717
|
+
const addressingMode = key.addressingMode;
|
|
718
|
+
const pushName = message.pushName;
|
|
719
|
+
let contact = null;
|
|
720
|
+
// Try lid-based lookup first if addressingMode is "lid"
|
|
721
|
+
if (addressingMode === "lid" && participant) {
|
|
722
|
+
contact = await this.store.contacts().getByLid(participant);
|
|
723
|
+
}
|
|
724
|
+
// Fall back to id-based lookup using participantAlt
|
|
725
|
+
if (!contact && participantAlt) {
|
|
726
|
+
contact = await this.store.contacts().get(participantAlt);
|
|
727
|
+
}
|
|
728
|
+
// If still not found and we have participantAlt, create a new contact
|
|
729
|
+
if (!contact && participantAlt) {
|
|
730
|
+
contact = await this.store.contacts().create(participantAlt);
|
|
731
|
+
// Set the lid if we have it
|
|
732
|
+
if (participant && addressingMode === "lid") {
|
|
733
|
+
contact.lid = participant;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Back-populate name from pushName if the contact exists but has no name
|
|
737
|
+
if (contact && pushName && !contact.name) {
|
|
738
|
+
contact.name = pushName;
|
|
739
|
+
}
|
|
740
|
+
// Save any updates
|
|
741
|
+
if (contact) {
|
|
742
|
+
await this.store.contacts().save(contact);
|
|
743
|
+
}
|
|
744
|
+
return contact;
|
|
745
|
+
}
|
|
746
|
+
async doNothing(message) {
|
|
747
|
+
// do nothing.
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
exports.WhatsappClient = WhatsappClient;
|
|
751
|
+
class GroupService {
|
|
752
|
+
constructor(socket, store, state) {
|
|
753
|
+
this.socket = socket;
|
|
754
|
+
this.store = store;
|
|
755
|
+
this.state = state;
|
|
756
|
+
}
|
|
757
|
+
async getAll() {
|
|
758
|
+
// creds.me is only populated after authentication — guard against undefined for new accounts.
|
|
759
|
+
const myJid = this.state.creds.me?.id.replace(/:\d+(?=@)/, "") ?? "";
|
|
760
|
+
return this.store.chats().getGroups()
|
|
761
|
+
.then(groups => groups
|
|
762
|
+
.map(groups => ({
|
|
763
|
+
id: groups.id,
|
|
764
|
+
subject: groups.name,
|
|
765
|
+
participants: (!groups.participants) ? [] : groups.participants.map(p => ({
|
|
766
|
+
id: p.contact.id,
|
|
767
|
+
name: p.contact.name ?? "",
|
|
768
|
+
role: p.role,
|
|
769
|
+
label: p.label,
|
|
770
|
+
isMe: myJid !== "" && p.contact.id === myJid,
|
|
771
|
+
}))
|
|
772
|
+
})));
|
|
773
|
+
}
|
|
774
|
+
async createGroup(subject) {
|
|
775
|
+
let group = await this.socket.groupCreate(subject, []);
|
|
776
|
+
return {
|
|
777
|
+
id: group.id,
|
|
778
|
+
subject: group.subject,
|
|
779
|
+
participants: group.participants.map(p => ({
|
|
780
|
+
id: p.id,
|
|
781
|
+
role: WAClientHelper.parseRole(p.admin)
|
|
782
|
+
}))
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Add a user as a participant to a group.
|
|
787
|
+
* @param userId the Id of the user. should be in the phonenumber format: <phone>@s.whatsapp.net
|
|
788
|
+
* @param groupId the groupId. it is expectedd that the group is known and can be added to at this point.
|
|
789
|
+
* @param role the role that the user should be assigned.
|
|
790
|
+
* @returns nothing.
|
|
791
|
+
*/
|
|
792
|
+
async addUserToGroup(userId, groupId, role) {
|
|
793
|
+
let group = this.store.chats().get(groupId);
|
|
794
|
+
if (!group) {
|
|
795
|
+
console.log(`addUserToGroup: group '${groupId}' not found`);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
await this.socket.groupParticipantsUpdate(groupId, [userId], 'add');
|
|
799
|
+
// promote the user if need be.
|
|
800
|
+
if (role !== WhatsappStore_1.Role.Member) {
|
|
801
|
+
await this.socket.groupParticipantsUpdate(groupId, [userId], "promote");
|
|
802
|
+
}
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Update a user's role in a group.
|
|
807
|
+
* @param userId the Id of the user. should be in the phonenumber format: <phone>@s.whatsapp.net
|
|
808
|
+
* @param groupId the groupId.
|
|
809
|
+
* @param role the new role for the user.
|
|
810
|
+
*/
|
|
811
|
+
async updateUserRole(userId, groupId, role) {
|
|
812
|
+
if (role === WhatsappStore_1.Role.Member) {
|
|
813
|
+
await this.socket.groupParticipantsUpdate(groupId, [userId], 'demote');
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
await this.socket.groupParticipantsUpdate(groupId, [userId], 'promote');
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Remove a user from a group.
|
|
821
|
+
* @param userId the Id of the user. should be in the phonenumber format: <phone>@s.whatsapp.net
|
|
822
|
+
* @param groupId the groupId. it is expectedd that the group is known and can be added to at this point.
|
|
823
|
+
* @returns nothing
|
|
824
|
+
*/
|
|
825
|
+
async removeUserFromGroup(userId, groupId) {
|
|
826
|
+
let group = this.store.chats().get(groupId);
|
|
827
|
+
if (!group) {
|
|
828
|
+
console.log(`addUserToGroup: group '${groupId}' not found`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
await this.socket.groupParticipantsUpdate(groupId, [userId], 'remove');
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
class WAClientHelper {
|
|
836
|
+
static parseRole(value) {
|
|
837
|
+
if (!value) {
|
|
838
|
+
return WhatsappStore_1.Role.Member;
|
|
839
|
+
}
|
|
840
|
+
if (value.toLocaleLowerCase() === "admin") {
|
|
841
|
+
return WhatsappStore_1.Role.Admin;
|
|
842
|
+
}
|
|
843
|
+
return WhatsappStore_1.Role.SuperAdmin;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
class GroupClient {
|
|
847
|
+
constructor(client, id) {
|
|
848
|
+
this._client = client;
|
|
849
|
+
this._id = id;
|
|
850
|
+
}
|
|
851
|
+
id() {
|
|
852
|
+
return this._id;
|
|
853
|
+
}
|
|
854
|
+
subscribe(subscription) {
|
|
855
|
+
this._client.subscribe(subscription, { groupId: this.id() });
|
|
856
|
+
}
|
|
857
|
+
unsubscribe(subscription) {
|
|
858
|
+
this._client.unsubscribe(subscription);
|
|
859
|
+
}
|
|
860
|
+
sendMessage(message) {
|
|
861
|
+
this._client.sendMessage(this.id(), message);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
exports.GroupClient = GroupClient;
|
|
865
|
+
var ChatType;
|
|
866
|
+
(function (ChatType) {
|
|
867
|
+
ChatType["Group"] = "Group";
|
|
868
|
+
ChatType["Conversation"] = "Conversation";
|
|
869
|
+
})(ChatType || (exports.ChatType = ChatType = {}));
|