def _ping(self): msgid = self._msgid() message = Node("iq", id=msgid, type="get", to=self.SERVER) message.add(Node("ping", xmlns="w:p")) self._write(message)
def _received(self, node): request = node.child("request") if request is None or request["xmlns"] != "urn:xmpp:receipts": return message = Node("message", to=node["from"], id=node["id"], type="chat") message.add(Node("received", xmlns="urn:xmpp:receipts")) self._write(message)
def send_sync(self, numbers, mode="full", context="registration", index=0, last=True): msgid = self._msgid("sync") sid = (int(time()) + 11644477200) * 10000000 sync = Node("sync", mode=mode, context=context, sid=str(sid), index=str(index), last="true" if last else "false") node = Node("iq", to=self.number + "@" + self.SERVER, type="get", id=msgid, xmlns="urn:xmpp:whatsapp:sync") node.add(sync) # Add numbers to node for number in numbers: if number[0] != "+": number = "+" + number sync.add(Node("user", data=number)) self._write(node)
def _clear_dirty(self, *categories): nodes = [] for category in categories: nodes.append(Node("clean", type=category)) self._write( Node("iq", id=self._msgid("cleardirty"), type="set", to=self.SERVER, xmlns="urn:xmpp:whatsapp:dirty", children=nodes))
def vcard(self, number, name, data): """ Send a vCard to a contact. WhatsApp will display the PHOTO if it is embedded in the vCard data (as Base64 encoded JPEG). """ vcard = Node("vcard", data=data) vcard["name"] = name media = Node("media", children=[vcard], xmlns="urn:xmpp:whatsapp:mms", type="vcard", encoding="text") msgid, message = self._message(number, media) return msgid
def _message(self, to, node, group=False): msgid = self._msgid("message") to = self._jid(to) x = Node("x", xmlns="jabber:x:event", children=Node("server")) notify = Node("notify", xmlns="urn:xmpp:whatsapp", name=self.nickname) request = Node("request", xmlns="urn:xmpp:receipts") message = Node("message", to=to, type="text", id=msgid, t=utils.timestamp(), children=[x, notify, request, node]) return msgid, message
def _receipt(self, node): self._write( Node("receipt", type="read", to=node["from"], id=node["id"], t=utils.timestamp()))
def on_success(node): logger.info("Login attempt successfull") self.account_info = node.attributes presence = Node("presence") presence["name"] = self.nickname self._write(presence)
def chatstate(self, number, state): if state not in CHATSTATES: raise ValueError("Invalid chatstate: %r" % state) node = Node(state, xmlns=CHATSTATE_NS) msgid, message = self._message(number, node) self._write(message) return msgid
def vcard(self, number, name, data): """ Send a vCard to a contact. WhatsApp will display the photo if it is embedded in the vCard data as base64 encoded JPEG. """ vcard = Node("vcard", name=name, data=data) media = Node("media", children=[vcard], xmlns="urn:xmpp:whatsapp:mms", type="vcard", encoding="text") msgid, message = self._message(number, media) self._write(message) return msgid
def on_success(node): self.auth_blob = node.data self.account_info = node.attributes if node["status"] == "expired": self._disconnect() raise LoginError("Account marked as expired.") self._write(Node("presence", name=self.nickname))
def last_seen(self, number): msgid = self._msgid("lastseen") iq = Node("iq", type="get", id=msgid) iq["from"] = self.number + "@" + self.SERVER iq["to"] = number + "@" + self.SERVER iq.add(Node("query", xmlns="jabber:iq:last")) self._write(iq) def on_iq(node): if node["id"] != msgid: return if node["type"] == "error": return StreamError(node.child("error").children[0].name) return int(node.child("query")["seconds"]) callback = Callback("iq", on_iq) self.register_callback_and_wait(callback)
def _message(self, to, node, group=False): msgid = self._msgid() message = Node("message", type="chat", id=msgid) message["to"] = to + "@" + (self.GROUPHOST if group else self.SERVER) x = Node("x", xmlns="jabber:x:event") x.add(Node("server")) message.add(x) message.add(node) return msgid, message
def _challenge(self, node): encryption = Encryption(self.number, self.secret, node.data) self.writer.encrypt = encryption.encrypt self.reader.decrypt = encryption.decrypt logger.debug("Session Key: %s", encryption.export_key()) response = Node("response", xmlns="urn:ietf:params:xml:ns:xmpp-sasl", data=encryption.get_response()) self._write(response, encrypt=False) self._incoming()
def _notification(self, node): out = Node("ack", to=node["from"], id=node["id"], type=node["type"]) # Class is reserved keyword. out["class"] = "notification" if node.has_attribute("to"): out["from"] = node["to"] if node.has_attribute("participant"): out["participant"] = node["participant"] self._write(out)
def _iq(self, node): # Node without children could be a ping reply if len(node.children) == 0: return iq = node.children[0] if node["type"] == "get" and iq.name == "ping": self._write( Node("iq", to=self.SERVER, id=node["id"], type="result")) elif node["type"] == "result": pass else: logger.debug("Unknown iq message received: %s", node["type"])
def _challenge(self, node): encryption = Encryption(self.secret, node.data) logger.debug("Session Keys: %s", [key.encode("hex") for key in encryption.keys]) self.writer.encrypt = encryption.encrypt self.reader.decrypt = encryption.decrypt data = "%s%s%s" % (self.number, node.data, utils.timestamp()) response = Node("response", data=encryption.encrypt(data, False)) self._write(response, encrypt=False) self._incoming()
def location(self, number, latitude, longitude): """ Send a location update to a contact. """ media = Node("media", xmlns="urn:xmpp:whatsapp:mms", type="location", latitude=latitude, longitude=longitude) msgid, message = self._message(number, media) self._write(message) return msgid
def _read(self): length = self.list_start() token = self.peek_int8() if token == 0x01: self._consume(1) attributes = self.attributes(length) return Node("start", **attributes) elif token == 0x02: self._consume(1) raise EndOfStream() node = Node(self.string()) node.attributes = self.attributes(length) if (length % 2) == 0: token = self.peek_int8() if token == 0xf8 or token == 0xf9: node.children = self.list() else: node.data = self.string() return node
def _read(self): length = self.list_start() token = self._peek(1) if token == "\x01": self._consume(1) attributes = self.attributes(length) return Node("start", **attributes) if token == "\x02": self._consume(1) raise EndOfStream() node = Node(self.string()) node.attributes = self.attributes(length) if (length % 2) == 0: token = self._peek(1) if token == "\xF8" or token == "\xF9": node.children = self.list() else: node.data = self.string() return node
def _iq(self, node): # Node without children could be a ping reply if len(node.children) == 0: return iq = node.children[0] if node["type"] == "get" and iq.name == "ping": self._write( Node("iq", to=self.SERVER, id=node["id"], type="result")) elif node["type"] == "result" and iq.name == "query": self.messages.append(node) else: logger.debug("Unknown iq message received") if self.debug: print node.toxml(indent=" ") + "\n"
def audio(self, number, url, basename, size, attributes): valid_attributes = ("abitrate", "acodec", "asampfmt", "asampfreq", "duration", "encoding", "filehash", "mimetype") for name, value in attributes.iteritems: if name not in valid_attributes: raise Exception("Unknown audio attribute: %r" % name) media = Node("media", xmlns="urn:xmpp:whatsapp:mms", type="audio", url=url, file=basename, size=size, **attributes) msgid, message = self._message(number, media) self._write(message) return msgid
def image(self, number, url, basename, size, thumbnail=None): """ Send an image to a contact. Url should be publicly accessible Basename does not have to match Url Size is the size of the image, in bytes Thumbnail should be a Base64 encoded JPEG image, if provided. """ # TODO: Where does WhatsApp upload images? # PNG thumbnails are apparently not supported media = Node("media", xmlns="urn:xmpp:whatsapp:mms", type="image", url=url, file=basename, size=size, data=thumbnail) msgid, message = self._message(number, media) self._write(message) return msgid
def send_sync(self, numbers, mode="full", context="registration", index=0, last=True): msgid = self._msgid("sync") sid = (int(time()) + 11644477200) * 10000000 sync = Node( "sync", mode=mode, context=context, sid=str(sid), index=str(index), last="true" if last else "false") node = Node( "iq", to=self.number + "@" + self.SERVER, type="get", id=msgid, xmlns="urn:xmpp:whatsapp:sync") node.add(sync) # Add numbers to node for number in numbers: if number[0] != "+": number = "+" + number sync.add(Node("user", data=number)) self._write(node)
def login(self): assert self.socket is None self._connect() buf = self.writer.start_stream(self.SERVER, self.VERSION) self._write(buf) features = Node("stream:features") features.add(Node("receipt_acks")) features.add(Node("w:profile:picture", type="all")) features.add(Node("status")) # m1.java:48 features.add(Node("notification", type="participant")) features.add(Node("groups")) self._write(features) auth = Node("auth", xmlns="urn:ietf:params:xml:ns:xmpp-sasl", mechanism="WAUTH-1", user=self.number) self._write(auth) def on_success(node): logger.info("Login attempt successfull") self.account_info = node.attributes presence = Node("presence") presence["name"] = self.nickname self._write(presence) # Create new callback callback = Callback("success", on_success) self.register_callback_and_wait(callback) # Done return self.account_info != None
def connect(self): self.reader = Reader() self.writer = Writer() self._connect() buf = self.writer.start_stream(self.SERVER, "%s-%s-%d" % ( PROTOCOL_DEVICE, PROTOCOL_VERSION, PORT)) self._write(buf) # Send features node features = Node("stream:features") features.add(Node("readreceipts")) features.add(Node("groups_v2")) features.add(Node("privacy")) features.add(Node("presence")) self._write(features) # Send auth node auth = Node("auth", mechanism="WAUTH-2", user=self.number) if self.auth_blob: encryption = AuthBlobEncryption(self.secret, self.auth_blob) logger.debug( "Session Keys (re-using auth challenge): %s", [key.encode("hex") for key in encryption.keys]) self.reader.decrypt = encryption.decrypt # From WhatsAPI. It does not encrypt the data, but generates a MAC # based on the keys. data = "%s%s%s" % (self.number, self.auth_blob, utils.timestamp()) auth.data = encryption.encrypt("", False) + data self._write(auth) def on_success(node): self.auth_blob = node.data self.account_info = node.attributes if node["status"] == "expired": self._disconnect() raise LoginError("Account marked as expired.") self._write(Node("presence", name=self.nickname)) def on_failure(node): self._disconnect() raise LoginError("Incorrect number and/or secret.") # Wait for either success, or failure self.register_callback_and_wait( LoginSuccessCallback(on_success), LoginFailedCallback(on_failure))
def presence(self, state): self._write(Node("presence", type=state))
def group_message(self, group, text): msgid, message = self._message(group, Node("body", data=text), True) self._write(message) return msgid
def message(self, number, text): msgid, message = self._message(number, Node("body", data=text)) self._write(message) return msgid
def send_server_properties(self): msgid = self._msgid("getproperties") node = Node("iq", id=msgid, type="get", xmlns="w", to=self.SERVER) node.add(Node("props")) self._write(node)
def connect(self): self.reader = Reader() self.writer = Writer() self._connect() buf = self.writer.start_stream( self.SERVER, "%s-%s-%d" % (PROTOCOL_DEVICE, PROTOCOL_VERSION, PORT)) self._write(buf) # Send features node features = Node("stream:features") features.add(Node("readreceipts")) features.add(Node("groups_v2")) features.add(Node("privacy")) features.add(Node("presence")) self._write(features) # Send auth node auth = Node("auth", mechanism="WAUTH-2", user=self.number) if self.auth_blob: encryption = AuthBlobEncryption(self.secret, self.auth_blob) logger.debug("Session Keys (re-using auth challenge): %s", [key.encode("hex") for key in encryption.keys]) self.reader.decrypt = encryption.decrypt # From WhatsAPI. It does not encrypt the data, but generates a MAC # based on the keys. data = "%s%s%s" % (self.number, self.auth_blob, utils.timestamp()) auth.data = encryption.encrypt("", False) + data self._write(auth) def on_success(node): self.auth_blob = node.data self.account_info = node.attributes if node["status"] == "expired": self._disconnect() raise LoginError("Account marked as expired.") self._write(Node("presence", name=self.nickname)) def on_failure(node): self._disconnect() raise LoginError("Incorrect number and/or secret.") # Wait for either success, or failure self.register_callback_and_wait(LoginSuccessCallback(on_success), LoginFailedCallback(on_failure))