· 5 years ago · Jan 20, 2021, 06:20 PM
1//imports
2var express = require('express');
3const WhatsAppWeb = require('baileys');
4var fs = require("fs");
5
6//pre-init
7var app = express();
8app.use(express.urlencoded({ extended: true }));
9app.use(express.json());
10var client = null;
11
12//Event cache
13var unreadMessages = []
14
15//template for responses
16var responseTemplate = {
17 "succes" : "null",
18 "response" : "null",
19 "error" : "null",
20 "data" : {}
21}
22
23//Login data
24authData = {}
25
26/* format:
27authData = {
28 "clientID": "LbBdDcPDnQSVAnIqqxQ12Q==",
29 "serverToken": "1@fAoA6bwvj4t5FJBu2SfadsddsadCoMxlr79CY4oAkiCUwQAIasdasdas8HlimgessZbXByzQuA==",
30 "clientToken": "0xAhydasv+Kb+Sgor4iThisIsFakeDontWorryFe0N+alwWK3gAe4Pdugzb99Y=",
31 "encKey": "YdEw+To63AdasdaXkUGrSQceAOrJlkf8d8Ufi6P/bRaAd0oM=",
32 "macKey": "lOaEMGpvNA+IYbS+HF9+47tFmXasdasdbOHYyI4Y3ztwg="
33}
34*/
35
36//init
37var server = app.listen(42069, function () {
38 var port = server.address().port
39 console.log("[REST] Baileys REST API listening at http://localhost:%s", port)
40
41 if (!isEmpty(authData)){
42 console.log("[REST] Automatically signing in.")
43 restoreConnection(JSON.parse(JSON.stringify(responseTemplate)), authData)
44 postInit();
45
46 }
47
48})
49
50//post-init
51function postInit(){
52 console.log("[REST] Running post init")
53
54 client.setOnUnreadMessage (m => {
55 onIncomingMessage(m);
56 })
57
58 client.setOnUnexpectedDisconnect (err => console.log("[REST] disconnected with error: ", err) ) //Doesn't call. Bug?
59
60}
61
62//Sign in. Login tokens in the body are optional, otherwise a new connection will be made. The login tokens are returned if they are generated.
63app.post('/client/connect', async (req, res) => {
64 var response = JSON.parse(JSON.stringify(responseTemplate));
65
66 if (isEmpty(req.body)) {
67 //No auth data given, we need a new connection.
68 response = await newConnection(response)
69
70 } else {
71 //Auth data is embedded in the body, restoring the previous connection
72 response = await restoreConnection(response, req.body)
73
74 }
75
76 postInit();
77
78 res.send(response);
79
80})
81
82//Get auth tokens for current session (deprecated)
83app.get('/client/auth', function (req, res) {
84 var response = JSON.parse(JSON.stringify(responseTemplate));
85
86 if (client === null){
87 response.succes = false;
88 }else{
89 response.succes = true;
90 response.response = client.base64EncodedAuthInfo();
91 }
92
93 res.send(response)
94
95})
96
97//Return some info about the connection and health of the server. Currently just the connect whatsapp account's user data.
98app.get('/ping', function (req, res) {
99 var response = JSON.parse(JSON.stringify(responseTemplate));
100
101 if (client === null){
102 response.succes = false;
103 }else{
104 response.succes = true;
105 response.response = client.userMetaData;
106 }
107
108 res.send(response)
109
110})
111
112//Test Endpoint Please Ignore
113app.get('/test', async (req, res) => {
114 var response = JSON.parse(JSON.stringify(responseTemplate));
115
116 response.succes = true;
117
118 res.send(response)
119
120})
121
122//Query unread messages
123app.get('/messages/unread', function (req, res) {
124 var response = JSON.parse(JSON.stringify(responseTemplate));
125 response.succes = true;
126 response.response = []
127
128 while (!isEmpty(unreadMessages)){
129 response.response.push(unreadMessages.shift());
130 }
131
132 res.send(response)
133
134})
135
136//Send read receipt
137/* Body:
138 * {
139 * "remoteJID": "a", //jid of the chat, not the sender. For private chats these are the same, but for groups use the group jid.
140 * "messageID": "b" //stanzaId of the message.
141 * }
142 */
143app.post('/messages/ack', function (req, res) {
144 var response = JSON.parse(JSON.stringify(responseTemplate));
145
146 if (isEmpty(req.body)) {
147
148 response.succes = false
149
150 } else {
151
152 client.sendReadReceipt(req.body["remoteJID"], req.body["messageID"])
153 response.succes = true
154
155 }
156
157 res.send(response)
158
159})
160
161//Send a text message
162/* Body:
163 * {
164 * "contactID": "a",
165 * "quotedMessage": [], //Options are: null, see below
166 * "text": "c"
167 * }
168 *
169 * Body when quoting:
170 * {
171 * "contactID": "a,
172 * "quotedMessage": {
173 * "key": {
174 * "remoteJid": "b", //Quoted person (jid)
175 * "id": "c" //Can be anything. For safety don't leave it empty.
176 * },
177 * "message": { "conversation": "d" } //Quoted message
178 * },
179 * "text": "e" //Reply to the quoted message
180 * }
181 */
182app.post('/messages/send/text', function (req, res) {
183 var response = JSON.parse(JSON.stringify(responseTemplate));
184
185 if (isEmpty(req.body)) {
186
187 response.succes = false
188
189 } else {
190
191 sendTextMessage(req.body["contactID"], req.body["text"], req.body["quotedMessage"])
192 response.succes = true
193
194 }
195
196 res.send(response)
197
198})
199
200//Send a raw message: https://github.com/adiwajshing/Baileys/blob/ea5ca61e496ddabfce0728396bd1d03e68136d26/WhatsAppWeb.Send.js#L167
201/* Body:
202 * {
203 * "contactID": "a",
204 * "message": {} //Options are: see additional data file
205 * }
206 */
207app.post('/messages/send/raw', function (req, res) {
208 var response = JSON.parse(JSON.stringify(responseTemplate));
209
210 if (isEmpty(req.body)) {
211
212 response.succes = false
213
214 } else {
215
216 client.sendMessage(req.body["contactID"], req.body["message"]);
217 response.response = req.body["message"];
218 response.succes = true;
219
220 }
221
222 res.send(response)
223
224})
225
226//Send a media message
227//TODO: Enable quoting other messages, currently limited by Bailey's functionality.
228//TODO: Test video, gif, audio.
229/* Body:
230 * {
231 * "contactID": "a",
232 * "messageType": "b", //Options are: "media/video", "media/image", "media/audio", "media/sticker".
233 * "quotedMessage": [], //Options are: null, []
234 * "text": "d", //Optiones are: "text", for no text use "" not null.
235 * "mediaLocation": "e", //Options are: Local path to the file.
236 * "isGif": f //Options are: true/false
237 * }
238 */
239app.post('/messages/send/media', function (req, res) {
240 var response = JSON.parse(JSON.stringify(responseTemplate));
241 var mediaType = null
242
243 if (isEmpty(req.body)) {
244
245 response.succes = false
246
247 } else {
248
249 if (req.body["messageType"] == "media/video") {
250 mediaType = WhatsAppWeb.MessageType.video;
251 } else if (req.body["messageType"] == "media/image") {
252 mediaType = WhatsAppWeb.MessageType.image;
253 } else if (req.body["messageType"] == "media/audio") {
254 mediaType = WhatsAppWeb.MessageType.audio;
255 } else if (req.body["messageType"] == "media/sticker") {
256 mediaType = WhatsAppWeb.MessageType.sticker;
257 }
258
259 sendMediaMessage(req.body["contactID"], req.body["text"], req.body["mediaLocation"], req.body["isGif"], mediaType)
260 response.succes = true
261
262 }
263
264 res.send(response)
265
266})
267
268//Query info about a person
269app.get('/people/:id/info', async (req, res) => {
270 var response = JSON.parse(JSON.stringify(responseTemplate));
271
272 response.succes = true;
273 response.response = {};
274
275 response.response.jid = req.params.id
276
277 await client.isOnWhatsApp (req.params.id)
278 .then (([exists, id]) => response.response.exists = exists)
279
280 await client.getStatus (req.params.id)
281 .then (json => response.response.status = json[0].status) //Bug? Should be json.status
282
283 await client.getProfilePicture (req.params.id)
284 .then (json => response.response.picture = json[0].eurl)
285
286 res.send(response)
287
288})
289
290//Updating presence
291/* Body:
292 * {
293 * "presence": "a" //Options are: "available", "unavailable", "composing", "recording".
294 * }
295 */
296app.post('/presence/:id', function (req, res) {
297 var response = JSON.parse(JSON.stringify(responseTemplate));
298 response.succes = true;
299 presence = null
300
301 if (req.body.presence == "available") {
302 presence = WhatsAppWeb.Presence.available;
303 } else if (req.body.presence == "unavailable") {
304 presence = WhatsAppWeb.Presence.unavailable;
305 } else if (req.body.presence == "composing") {
306 presence = WhatsAppWeb.Presence.composing;
307 } else if (req.body.presence == "recording") {
308 presence = WhatsAppWeb.Presence.recording;
309 } else {
310 response.succes = false;
311 }
312
313 client.updatePresence(req.params.id, presence)
314 res.send(response)
315
316})
317
318//Query info about a group
319//TODO: Fix after baileys 1.0.2
320app.get('/group/:id/info', async (req, res) => {
321 var response = JSON.parse(JSON.stringify(responseTemplate));
322
323 response.succes = true;
324 response.response = {};
325
326 response.response.jid = req.params.id
327
328 //await client.groupMetadata (req.params.id)
329 //.then (json => response.response.status = json[0].status) //TODO: Doesn't work.
330
331 await client.getProfilePicture (req.params.id)
332 .then (json => response.response.picture = json[0].eurl)
333
334 await client.groupInviteCode (req.params.id)
335 .then (code => response.response.code = code)
336
337 res.send(response)
338
339})
340
341//functional methods
342//Create a new connection
343async function newConnection(response){
344 client = new WhatsAppWeb()
345 client.autoReconnect = true
346
347 await client.connect()
348 .then (([user, chats, contacts, unread]) => {
349 console.log("[REST] Logged in succesfully");
350 response.succes = true;
351 response.response = client.base64EncodedAuthInfo();
352
353 })
354 .catch (([err]) => {
355 console.log("[REST] unexpected error while logging in: " + err)
356 response.succes = false;
357 response.error = err;
358
359 })
360
361 return response
362
363}
364
365//Sign back in to an existing connection
366async function restoreConnection(response, auth){
367 client = new WhatsAppWeb()
368 client.autoReconnect = true
369
370 await client.connect(auth)
371 .then (([user, chats, contacts, unread]) => {
372 console.log("[REST] Logged in succesfully");
373 response.succes = true;
374
375 })
376 .catch (([err]) => {
377 console.log("[REST] unexpected error while logging in: " + err);
378 response.succes = false;
379 response.error = err;
380
381 })
382
383 return response;
384
385}
386
387//Send a text message
388function sendTextMessage(contactID, text, quotedMessage){
389 if (isEmpty(quotedMessage)){
390 client.sendTextMessage(contactID, text)
391 }else{
392 client.sendTextMessage(contactID, text, quotedMessage)
393 }
394}
395
396//Send a media message
397//TODO: Add support for MIME requiring file types.
398function sendMediaMessage(contactID, text, mediaPath, isGif, mediaType){
399 const buffer = fs.readFileSync(mediaPath)
400 //To send text files we need to add support for MIME types in the info array.
401 const info = {}
402
403 if (isGif === true){
404 info["gif"] = isGif;
405 }
406
407 if (text != "" && mediaType != WhatsAppWeb.MessageType.sticker){
408 info["caption"] = text;
409 }
410
411 client.sendMediaMessage(contactID, buffer, mediaType, info)
412}
413
414//events
415//Unread message listener
416function onIncomingMessage(message){
417 console.log("[REST] New message!")
418
419 unreadMessages.push(message);
420
421}
422
423//util methods
424//Check if JSON string is empty (== "{}" || == "" || == [] || == null)
425function isEmpty(obj) {
426 if (obj == null) { return true; }
427
428 return !Object.keys(obj).length > 0;
429}
430
431