diff --git a/backend/status_decoder.py b/backend/status_decoder.py new file mode 100644 index 0000000..bc514d8 --- /dev/null +++ b/backend/status_decoder.py @@ -0,0 +1,21 @@ +import json +import sys +from whatsapp import HKDF,AESUnpad; +import base64; +import urllib2; +import time; +from Crypto.Cipher import AES; +query = json.loads(sys.argv[1]) +mediaK= query['mediakey'] +if query['mimetype'] == "video/mp4": + mediaKeyExpanded=HKDF(base64.b64decode(mediaK),112,"WhatsApp Video Keys") +else: + mediaKeyExpanded=HKDF(base64.b64decode(mediaK),112,"WhatsApp Image Keys") + +mediaData= urllib2.urlopen(query['url']).read() +file= mediaData[:-10] +iv=mediaKeyExpanded[:16] +cipherKey= mediaKeyExpanded[16:48] +decryptor = AES.new(cipherKey, AES.MODE_CBC, iv) +fileData=AESUnpad(decryptor.decrypt(file)) +print(base64.b64encode(fileData)) \ No newline at end of file diff --git a/backend/whatsapp.py b/backend/whatsapp.py index a7a7dfa..5edb56f 100755 --- a/backend/whatsapp.py +++ b/backend/whatsapp.py @@ -31,7 +31,7 @@ from utilities import *; from whatsapp_binary_reader import whatsappReadBinary; -WHATSAPP_WEB_VERSION="2,2121,6" +WHATSAPP_WEB_VERSION="2,2136,10" reload(sys); sys.setdefaultencoding("utf-8"); @@ -181,6 +181,22 @@ def onMessage(self, ws, message): try: processedData = whatsappReadBinary(decryptedMessage, True); messageType = "binary"; + + # sort contacts obj{jid : name} + try: + if processedData[1]['type'] is "contacts": + messageType = "jsonContacts"; + processedData.append(self.sortedContacts(processedData)) + except: + pass + # sort statuses + try: + if processedData[2][0][0] is "status": + processedData[2] = self.sortedStatuses(processedData) + messageType = "jsonStatuses"; + except: + pass + except: processedData = { "traceback": traceback.format_exc().splitlines() }; messageType = "error"; @@ -209,7 +225,7 @@ def onMessage(self, ws, message): self.loginInfo["key"]["encKey"] = keysDecrypted[:32]; self.loginInfo["key"]["macKey"] = keysDecrypted[32:64]; - self.save_session(); + self.saveSession(); # eprint("private key : ", base64.b64encode(self.loginInfo["privateKey"].serialize())); # eprint("secret : ", base64.b64encode(self.connInfo["secret"])); # eprint("shared secret : ", base64.b64encode(self.connInfo["sharedSecret"])); @@ -229,6 +245,7 @@ def onMessage(self, ws, message): eprint(json.dumps( [messageTag,["admin","challenge",challenge,self.connInfo["serverToken"],self.loginInfo["clientId"]]])) self.activeWs.send(json.dumps( [messageTag,["admin","challenge",challenge,self.connInfo["serverToken"],self.loginInfo["clientId"]]])); elif jsonObj[0] == "Stream": + self.getStatuses(); # request for contacts statuses pass; elif jsonObj[0] == "Props": pass; @@ -276,7 +293,7 @@ def restoreSession(self, callback=None): "serverToken"] + '", "' + self.loginInfo["clientId"] + '", "takeover"]' self.activeWs.send(message) - def save_session(self): + def saveSession(self): session = {"clientToken":self.connInfo["clientToken"],"serverToken":self.connInfo["serverToken"], "clientId":self.loginInfo["clientId"],"macKey": self.loginInfo["key"]["macKey"].decode("latin_1") ,"encKey": self.loginInfo["key"]["encKey"].decode("latin_1")}; @@ -284,6 +301,42 @@ def save_session(self): f.write(json.dumps(session)) f.close() + def sortedContacts(self,processedData): + contacts = {} + for contact in range(len(processedData[2])): + if 'name' in processedData[2][contact][1].keys() : + contacts[processedData[2][contact][1]['jid']] = processedData[2][contact][1]['name'] + return contacts + + def getStatuses(self): + messageId = "3EB0"+binascii.hexlify(Random.get_random_bytes(8)).upper() + encryptedMessage = WhatsAppEncrypt( + self.loginInfo["key"]["encKey"], + self.loginInfo["key"]["macKey"], + whatsappWriteBinary(["query", {"type": "status","jid":""}, None]) + ) + payload = bytearray(messageId) + bytearray(",") + bytearray( + to_bytes(WAMetrics.QUERY_MEDIA, 1) + ) + bytearray([0x80]) + encryptedMessage + self.activeWs.send(payload, websocket.ABNF.OPCODE_BINARY) + + def sortedStatuses(self,processedData): + entries = {} + bad = [] + for user in range(len(processedData[2])): + jid = processedData[2][user][1]['jid'] + for story in range(len(processedData[2][user][2])): + if processedData[2][user][2][story][0] == "picture" : + bad.append(story) + continue + decoded_msgs = WAWebMessageInfo.decode(processedData[2][user][2][story][2]) + processedData[2][user][2][story] = decoded_msgs['message'] + entries[jid] = processedData[2][user][2] + for b in bad: + del entries[jid][b] + return entries + + def getLoginInfo(self, callback): callback["func"]({ "type": "login_info", "data": self.loginInfo }, callback); diff --git a/client/css/main.css b/client/css/main.css index c9ad3da..bcbbe1d 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -110,16 +110,18 @@ body { } #console-arrow > button { font-size: 2.5vh; + background: #000; + color: #fff; } #console-arrow.extended { - right: 20vw; + right: 30vw; } #console { position: absolute; top: 0vh; left: 100vw; - width: 20vw; + width: 30vw; height: 100vh; padding: 3vh; transition: left 0.2s; @@ -128,5 +130,186 @@ body { background-color: rgba(200, 200, 200, 0.5); } #console.extended { - left: 80vw; + left: 70vw; } + +.statuses { + width: 98%; + background-color: #1e262b; + color: aliceblue; + padding: 1%; + height: 98%; + display: flow-root; + overflow: scroll; + position: relative; +} + +.user-item { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: nowrap; + align-content: space-between; + justify-content: flex-start; +} + +.user-item:hover { + background-color: #2c353a; +} + +.user-details { + display: flex; + justify-content: space-between; + flex-direction: column; + flex-wrap: nowrap; + align-items: stretch; + align-content: space-between; +} + +.user-details span:nth-child(2) { + font-size: small; + color: gray; + padding: 2px; +} + +.users-list { + position: absolute; +} + +.user-image { + background-repeat: no-repeat; + background-position: 50%; + background-size: cover; + border-radius: 50%; + width: 40px; + height: 40px; + overflow: hidden; + padding: 2px; + margin: 5px; + background-color: azure; +} + +.full-view { + position: sticky; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #193636; + background-position-x: center; + background-position-y: center; + background-size: contain; + background-repeat: no-repeat; + max-width: 400px; + max-height: 800px; + margin: 0 auto; +} + +.statuses .close { + padding: 8px 13px; + background: #000000b0; + border-radius: 15px; + width: fit-content; + margin: 5px; + position: absolute; + top: 3%; + color: #fff; + opacity: 1; +} + +.next { + background: #00000000; + border-radius: 5px; + position: absolute; + top: 15%; + right: 0; + height: 85%; + width: 20%; +} + +.pervious { + background: #00000000; + border-radius: 5px; + position: absolute; + top: 15%; + left: 0; + height: 85%; + width: 20%; +} + +video { + width: 100%; + height: 100%; +} + +.text-media { + display: flex; + height: 100%; + align-content: space-between; + justify-content: center; + flex-wrap: nowrap; + flex-direction: column; + text-align: center; + padding: 15px; +} + +.status-dots { + height: 10px; + background: #00000000; + width: 94%; + margin: 1% 3%; + position: absolute; + top: 0; + display: flex; + flex-wrap: nowrap; + flex-direction: row; + align-content: space-between; + justify-content: space-between; + align-items: center; +} + +.dots { + height: 50%; + background: aliceblue; + width: 10%; + margin: 0% 1%; + border-radius: 10px; +} + +.dots.active { + background: black; +} + +.loader { + border: 10px solid #f3f3f3; + border-radius: 50%; + border-top: 10px solid #298313d9; + border-right: 10px solid #b9c502d1; + border-bottom: 10px solid #4bc92b; + border-left: 10px solid #00ff4396; + width: 40px; + height: 40px; + -webkit-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; + position: absolute; + right: 45%; + top: 45%; +} + +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/client/index.html b/client/index.html index 24cd268..beecf46 100644 --- a/client/index.html +++ b/client/index.html @@ -29,6 +29,10 @@ + + + +