Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
adiwajshing committed Mar 16, 2020
0 parents commit 5bd524c
Show file tree
Hide file tree
Showing 319 changed files with 76,526 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
auth_info.json
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Baileys
Reverse Engineered WhatsApp Web API in Node.js

Thank you to [Sigalor](https://github.com/sigalor/whatsapp-web-reveng) for writing the guide reverse engineering WhatsApp Web and to the go reimplementation written by [Rhymen](https://github.com/Rhymen/go-whatsapp/tree/484cfe758705761d76724e01839d6fc473dc10c4)

To use Baileys: ``` js const WhatsAppWeb = require('Baileys') ```
Check out test.js to see exactly how to use Baileys.

# Note
I am in no way affiliated with WhatsApp. This is purely for educational purposes.
205 changes: 205 additions & 0 deletions WhatsAppWeb.Recv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
const Utils = require("./WhatsAppWeb.Utils")
/*
Contains the code for recieving messages and forwarding what to do with them to the correct functions
*/
module.exports = function(WhatsAppWeb) {

const Status = WhatsAppWeb.Status

WhatsAppWeb.prototype.onMessageRecieved = function (message) {

if (message[0] === "!") { // when the first character in the message is an '!', the server is updating on the last seen
const timestamp = message.slice(1,message.length)
this.lastSeen = new Date( parseInt(timestamp) )
} else {
const commaIndex = message.indexOf(",") // all whatsapp messages have a tag and a comma, followed by the actual message

if (commaIndex < 0) { // if there was no comma, then this message must be not be valid
return this.gotError([2, "invalid message", message])
}

var data = message.slice(commaIndex+1, message.length)
if (data.length === 0) {
// got an empty message, usually get one after sending a message or something, just return then
return
}

let json
if (data[0] === "[" || data[0] === "{") { // if the first character is a "[", then the data must just be plain JSON array or object
json = JSON.parse( data ) // simply parse the JSON
} else if (this.status === Status.connected) {
/*
If the data recieved was not a JSON, then it must be an encrypted message.
Such a message can only be decrypted if we're connected successfully to the servers & have encryption keys
*/

data = Buffer.from(data, 'binary') // convert the string to a buffer
const checksum = data.slice(0, 32) // the first 32 bytes of the buffer are the HMAC sign of the message
data = data.slice(32, data.length) // the actual message

const computedChecksum = Utils.hmacSign(data, this.authInfo.macKey) // compute the sign of the message we recieved using our macKey

if (checksum.equals(computedChecksum)) { // the checksum the server sent, must match the one we computed for the message to be valid
const decrypted = Utils.aesDecrypt(data, this.authInfo.encKey) // decrypt using AES
json = this.decoder.read( decrypted ) // decode the binary message into a JSON array
} else {
return this.gotError([7, "checksums don't match"])
}
//console.log("enc_json: " + JSON.stringify(json))
} else {
// if we recieved a message that was encrypted but we weren't conencted, then there must be an error
return this.gotError([3, "recieved encrypted message when not connected: " + this.status, message])
}

//console.log(json)
// the first item in the recieved JSON, if it exists, it tells us what the message is about
switch (json[0]) {
case "Conn":
/*
we get this message when a new connection is established,
whether we're starting a new session or are logging back in.
Sometimes, we also recieve it when one opens their phone
*/
this.validateNewConnection(json[1])
return
case "Cmd":
/*
WhatsApp usually sends this when we're trying to restore a closed session,
WhatsApp will challenge us to see whether we still have the keys
*/
if (json[1].type === "challenge") { // if it really is a challenge
this.respondToChallenge(json[1].challenge)
}
return
case "action":
/*
this is when some action was taken on a chat or that we recieve a message.
json[1] tells us more about the message, it can be null
*/
if (!json[1]) { // if json[1] is null
json = json[2][0] // set json to the first element in json[2]; it contains the relevant part

if (json[0] === "read") { // if one marked a chat as read or unread on the phone
const id = json[1].jid.replace("@c.us", "@s.whatsapp.net") // format the sender's ID
if (this.chats[id] && json[1].type === 'false') { // if it was marked unread
this.chats[id].user.count = 1 // up the read count
this.clearUnreadMessages(id) // send notification to the handler about the unread message
} else { // if it was marked read
this.chats[id].user.count = 0 // set the read count to zero
}
}

} else if (json[1].add === "relay") { // if we just recieved a new message sent to us
this.onNewMessage( json[2][0][2] ) // handle this new message
} else if (json[1].add === "before" || json[1].add === "last") {
/*
if we're recieving a full chat log
if json[1].add equals before: if its non-recent messages
if json[1].add equals last: contains the last message of the conversation between the sender and us
*/

json = json[2] // json[2] is the relevant part
/* reverse for loop, because messages are sent ordered by most recent
I can order them by recency if I add them in reverse order */
for (var k = json.length-1;k >= 0;k--) {
const message = json[k]
const id = message[2].key.remoteJid
if (!this.chats[ id ]) { // if we haven't added this ID before, add them now
this.chats[ id ] = {user: { jid: id, count: 0 }, messages: []}
}

this.chats[id].messages.push(message[2]) // append this message to the array
}

const id = json[0][2].key.remoteJid // get the ID whose chats we just processed
this.clearUnreadMessages(id) // forward to the handler any any unread messages
}
break
case "response":
// if it is the list of all the people the WhatsApp account has chats with
if (json[1].type === "chat") {
json[2].forEach (chat => {
if (chat[0] === "chat" && chat[1].jid) {
const jid = chat[1].jid.replace("@c.us", "@s.whatsapp.net") // format ID
this.chats[ jid ] = {
user: {
jid: jid, // the ID of the person
count: chat[1].count}, // number of unread messages we have from them
messages: [ ] // empty messages, is filled by content in the previous section
}
}
})

}
break
default:
break
}

// if the recieved JSON wasn't an array, then we must have recieved a status for a request we made
// this would include creating new sessions & logging in
switch (json.status) {
case 200: // all good and we can procede to generate a QR code for new connection, or can now login given present auth info

if (this.status === Status.creatingNewConnection){ // if we're trying to start a connection
if (this.authInfo.encKey && this.authInfo.macKey) { // if we have the info to restore a closed session
this.status = Status.loggingIn
// create the login request
const data = ["admin", "login", this.authInfo.clientToken, this.authInfo.serverToken, this.authInfo.clientID, "takeover"]
this.sendJSON( data )
} else {
this.generateKeysForAuth(json.ref)
}
} else {

}
break
case 401: // if the phone was unpaired
this.close()
return this.gotError([json.status, "unpaired from phone", message])
case 429: // request to login was denied, don't know why it happens
this.close()
return this.gotError([ json.status, "request denied, try reconnecting", message ])
case 304: // request to generate a new key for a QR code was denied
console.log("reuse previous ref")
return this.gotError([ json.status, "request for new key denied", message ])
default:
break
}

}
}
// shoot off notifications to the handler that new unread message are available
WhatsAppWeb.prototype.clearUnreadMessages = function (id) {
const chat = this.chats[id] // get the chat
var j = 0
let unreadMessages = chat.user.count
while (unreadMessages > 0) {
if (!chat.messages[j].key.fromMe) { // only forward if the message is from the sender
this.handlers.onUnreadMessage( chat.messages[j] ) // send off the unread message
unreadMessages -= 1 // reduce
}
j += 1
}
}
// when a new message is recieved
WhatsAppWeb.prototype.onNewMessage = function (message) {

if (message && message.message) { // confirm that the message really is valid
if (!this.chats[message.key.remoteJid]) { // if we don't have any chats from this ID before, add them to our DB
this.chats[message.key.remoteJid] = {
user: { jid: message.key.remoteJid, count: 0 },
messages: [ message ]
}
} else {
// if the chat was already there, then insert the message at the front of the array
this.chats[ message.key.remoteJid ].messages.splice(0, 0, message)
}

if (!message.key.fromMe) { // if this message was sent to us, notify the handler
this.handlers.onUnreadMessage ( message )
}
}
}

}
85 changes: 85 additions & 0 deletions WhatsAppWeb.Send.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const Utils = require("./WhatsAppWeb.Utils")

/*
Contains the code for sending stuff to WhatsApp
*/
module.exports = function(WhatsAppWeb) {

// send a read receipt to the given ID on a certain message
WhatsAppWeb.prototype.sendReadReceipt = function (jid, messageID) {
const json = [
"action",
{ epoch: this.msgCount.toString(), type: "set" },
[ ["read", {count: "1", index: messageID, jid: jid, owner: "false"}, null] ]
]
this.sendBinary(json, [10, 128]) // encrypt and send off

if (this.chats[ jid ]) {
this.chats[jid].user.count = 0 // reset read count
}
}
// tell someone about your presence -- online, typing, offline etc.
WhatsAppWeb.prototype.updatePresence = function (jid, type) {
const json = [
"action",
{ epoch: this.msgCount.toString(), type: "set" },
[ ["presence", {type: type, to: jid}, null] ]
]
this.sendBinary(json, [10, 128])
}
// send a text message to someone, optionally you can provide the time at which you want the message to be sent
WhatsAppWeb.prototype.sendTextMessage = function (id, txt, timestamp=null) {
const message = {conversation: txt}
return this.sendMessage(id, message, timestamp)
}
// generic send message construct
WhatsAppWeb.prototype.sendMessage = function (id, message, timestamp=null) {
if (!timestamp) { // if no timestamp was provided,
timestamp = new Date() // set timestamp to now
}
timestamp = timestamp.getTime()/1000

const messageJSON = {
key: {
remoteJid: id,
fromMe: true,
id: Utils.generateMessageID()
},
message: message,
messageTimestamp: timestamp,
status: "ERROR"
}

const json = [
"action",
{epoch: this.msgCount.toString(), type: "relay" },
[ ['message', null, messageJSON] ]
]
return this.sendBinary(json, [16, 128])
}
// send a binary message, the tags parameter tell WhatsApp what the message is all about
WhatsAppWeb.prototype.sendBinary = function (json, tags) {
const binary = this.encoder.write(json) // encode the JSON to the WhatsApp binary format

var buff = Utils.aesEncrypt(binary, this.authInfo.encKey) // encrypt it using AES and our encKey
const sign = Utils.hmacSign(buff, this.authInfo.macKey) // sign the message using HMAC and our macKey

buff = Buffer.concat([
Buffer.from( Utils.generateMessageTag() + "," ), // generate & prefix the message tag
Buffer.from(tags), // prefix some bytes that tell whatsapp what the message is about
sign, // the HMAC sign of the message
buff // the actual encrypted buffer
])
this.send(buff) // send it off
}
// send a JSON message to WhatsApp servers
WhatsAppWeb.prototype.sendJSON = function (json) {
const str = JSON.stringify(json)
this.send( Utils.generateMessageTag() + "," + str )
}
WhatsAppWeb.prototype.send = function (m) {
this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages
this.conn.send( m )
}

}
Loading

0 comments on commit 5bd524c

Please sign in to comment.