Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WhatsApp protocol support in Xabber #248

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
71102c0
Starting to add WhatsApp protocol stuff.
davidgfnet Aug 15, 2013
5b52c61
Adding whatsapp protocol internals. Translated from libpurple impleme…
davidgfnet Aug 15, 2013
cf5e0fe
Putting aside some files for some time. Seems to build ok and the Wha…
davidgfnet Aug 16, 2013
a759375
More WA protocol classes.
davidgfnet Aug 16, 2013
c58e5d0
Fixing some bugs and adding more functions for WhatsApp protocol func…
davidgfnet Aug 17, 2013
037bd31
Adding WA connection class wrapper.
davidgfnet Aug 17, 2013
288f9f3
Adding message serialization and socket read/write stuff.
davidgfnet Aug 17, 2013
16ef9e1
Fixed many bugs and added integration in the system.
davidgfnet Aug 21, 2013
b4a2ea9
Fixed many bugs. Now we are able to communicate with the servers and …
davidgfnet Aug 21, 2013
ad1aa7c
Fixed many bugs with auth. Now auth works!
davidgfnet Aug 22, 2013
518e6b9
Bit of cleanup.
davidgfnet Aug 22, 2013
db86318
Connect & login working!
davidgfnet Aug 22, 2013
2785d07
Message sending works.
davidgfnet Aug 22, 2013
5749171
Fixed many java related bugs.
davidgfnet Aug 23, 2013
28fdc14
Chat working more or less.
davidgfnet Aug 23, 2013
4cc20d6
Add makefile to remember build commands
davidgfnet Aug 23, 2013
9cd5e65
Added rather simple support for image chats.
davidgfnet Aug 24, 2013
f57034e
Adding permanent contacts using a SQLite table.
davidgfnet Aug 24, 2013
0c8304f
Fixed small issue with contact addition startup :)
davidgfnet Aug 24, 2013
b406df2
Added avatar support.
davidgfnet Aug 25, 2013
e3a3d70
Disabling debug output
davidgfnet Aug 25, 2013
6bdd1e7
Support for timestamp in incoming messages.
davidgfnet Aug 27, 2013
e41b76a
Adding presence sending.
davidgfnet Aug 27, 2013
c57f39f
Adding feature: sending status message along with status.
davidgfnet Aug 27, 2013
015a730
Fix small NullPointerException bug due to presence setting when disco…
davidgfnet Aug 31, 2013
4b7f81c
Adding support for group chats.
davidgfnet Sep 1, 2013
44cf312
Adding "RESOURCE" as nickname for WA accounts.
davidgfnet Sep 1, 2013
fbdb370
Correct small bug which prevented build.
davidgfnet Sep 2, 2013
8c1b0bc
Cleanup of dead code
davidgfnet Sep 15, 2013
2f4f857
More cleanup
davidgfnet Sep 15, 2013
48f113b
Add last seen at the VCard notes field.
davidgfnet Sep 15, 2013
604c9ec
Start adding HTTPS interface for user statuses
davidgfnet Sep 15, 2013
9be0f3c
Update WA protocol to V1.4.
davidgfnet Feb 1, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fixed many java related bugs.
Now it's possible to send and receive messages using whatsapp.
The stream keeps disconnecting though (probably due to not answering to some packets).
Contact status (available / away) shown in the contact list.

Next steps should be: Typing status + Permanently saving contacts.
davidgfnet committed Sep 14, 2013
commit 574917191634a39c1263af30f079f687e8024b7d
4 changes: 4 additions & 0 deletions src/net/davidgf/android/MiscUtil.java
Original file line number Diff line number Diff line change
@@ -124,6 +124,10 @@ public static String bytesToUTF8(byte [] ba) {
return new String();
}
}

public static String getUser(String user) {
return user.split("@")[0];
}

}

6 changes: 3 additions & 3 deletions src/net/davidgf/android/Tree.java
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ public Tree getChild(String tag) {
if (children.get(i).getTag().equals(tag))
return children.get(i);
Tree t = children.get(i).getChild(tag);
if (t == null)
if (t != null)
return t;
}
return null;
@@ -124,13 +124,13 @@ public boolean hasChild(String tag) {
String toString(int sp) {
String ret = "";
String spacing = "";
for (int i = 0; i < sp; i++)
for (int i = 0; i < sp*3; i++)
spacing += " ";
ret += spacing+"Tag: "+tag+"\n";
for (String key: attributes.keySet()) {
ret += spacing+"at["+key+"]="+attributes.get(key)+"\n";
}
ret += spacing+"Data: "+data+"\n";
ret += spacing+"Data: "+MiscUtil.bytesToUTF8(data)+"\n";

for (int i = 0; i < children.size(); i++) {
ret += children.get(i).toString(sp+1);
49 changes: 25 additions & 24 deletions src/net/davidgf/android/WAConnection.java
Original file line number Diff line number Diff line change
@@ -74,7 +74,6 @@ public class WAConnection extends Connection {
Semaphore readwait,writewait;

private ExecutorService listenerExecutor;

int msgid;

private static final String waUA = "WhatsApp/2.10.750 Android/4.2.1 Device/GalaxyS3";
@@ -104,20 +103,8 @@ public WAConnection(ConnectionThread ct, String serviceName, CallbackHandler cal
config.setCompressionEnabled(false);
config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED);
outbuffer_mutex = new byte[1];
readwait = new Semaphore(0);
writewait = new Semaphore(0);
this.cthread = ct;

listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable,
"WA Listener Processor");
thread.setDaemon(true);
return thread;
}
});

commonInit();
}

/**
@@ -132,10 +119,8 @@ public WAConnection(ConnectionThread ct, String serviceName) {
config.setCompressionEnabled(false);
config.setSASLAuthenticationEnabled(true);
config.setDebuggerEnabled(DEBUG_ENABLED);
outbuffer_mutex = new byte[1];
readwait = new Semaphore(0);
writewait = new Semaphore(0);
this.cthread = ct;
commonInit();
}

/**
@@ -146,19 +131,30 @@ public WAConnection(ConnectionThread ct, String serviceName) {
*/
public WAConnection(ConnectionThread ct, ConnectionConfiguration config) {
super(config);
outbuffer_mutex = new byte[1];
readwait = new Semaphore(0);
writewait = new Semaphore(0);
this.cthread = ct;
commonInit();
}

public WAConnection(ConnectionThread ct, ConnectionConfiguration config, CallbackHandler callbackHandler) {
super(config);
config.setCallbackHandler(callbackHandler);
this.cthread = ct;
commonInit();
}

private void commonInit() {
outbuffer_mutex = new byte[1];
readwait = new Semaphore(0);
writewait = new Semaphore(0);
this.cthread = ct;

this.listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable,
"WA Listener Processor");
thread.setDaemon(true);
return thread;
}
});
}

public String getConnectionID() {
@@ -381,7 +377,7 @@ public void sendPacket(Packet packet) {
System.out.println("Remove contact!\n");
}else{
// Adding contact, notify underlying connection for status query
//waconnection.addContact(it.getUser());
waconnection.addContact(it.getUser(),true);
System.out.println("Add contact!\n");
}
}
@@ -512,7 +508,7 @@ public void run() {
writerThread.setName("Socket data writer");
writerThread.setDaemon(true);
writerThread.start();

readerThread = new Thread() {
public void run() {
readPackets(this,istream);
@@ -594,7 +590,7 @@ private void readPackets(Thread thisThread, InputStream istream) {
synchronized ( waconnection ) {
synchronized (inbuffer) {
int used = waconnection.pushIncomingData(inbuffer);
inbuffer = Arrays.copyOf(inbuffer, inbuffer.length - used);
inbuffer = Arrays.copyOfRange(inbuffer, used, inbuffer.length);
}
}

@@ -606,8 +602,13 @@ private void readPackets(Thread thisThread, InputStream istream) {
waconnected = true;
}

if (listenerExecutor == null) {
System.out.println("Null!!! Shit\n");
}

Packet p = waconnection.getNextPacket();
while (p != null) {
System.out.println("Received message!\n");
for (PacketCollector collector: getPacketCollectors()) {
collector.processPacket(p);
}
68 changes: 64 additions & 4 deletions src/net/davidgf/android/WhatsappConnection.java
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
package net.davidgf.android;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;

import java.util.*;

@@ -32,6 +33,7 @@ private enum SessionStatus { SessionNone, SessionConnecting, SessionWaitingChall
private String mypresence;

private Vector <Packet> received_packets;
private Vector <Contact> contacts;

public WhatsappConnection(String phone, String pass, String nick) {
session_key = new byte[20];
@@ -43,6 +45,7 @@ public WhatsappConnection(String phone, String pass, String nick) {
this.mypresence = "available";
outbuffer = new DataBuffer();
received_packets = new Vector <Packet>();
contacts = new Vector <Contact> ();
}

public Tree read_tree(DataBuffer data) {
@@ -55,6 +58,7 @@ public Tree read_tree(DataBuffer data) {
return t;
}else if (type == 2) {
data.popData(1);
System.out.println("NO data in this tree\n");
return new Tree("treeerr"); // No data in this tree...
}

@@ -78,6 +82,7 @@ Tree parse_tree(DataBuffer data) {
int bflag = (data.getInt(1,0) & 0xF0)>>4;
int bsize = data.getInt(2,1);
if (bsize > data.size()-3) {
System.out.println("NO data enough\n");
return new Tree("treeerr"); // Next message incomplete, return consumed data
}
data.popData(3);
@@ -110,12 +115,12 @@ public int pushIncomingData(byte [] data) {
Tree t;
do {
t = this.parse_tree(db);
if (t.getTag() != "treeerr")
if (!t.getTag().equals("treeerr"))
this.processPacket(t);

System.out.println("Received tree!\n");
System.out.println(t.toString(0));
} while (t.getTag() != "treeerr" && db.size() >= 3);
} while (!t.getTag().equals("treeerr") && db.size() >= 3);

return data.length - db.size();
}
@@ -168,6 +173,20 @@ else if (t.getTag().equals("success")) {
//std::cout << "Account " << phone << " status: " << account_status << " kind: " << account_type <<
// " expires: " << account_expiration << " creation: " << account_creation << std::endl;
}
else if (t.getTag().equals("presence")) {
// Receives the presence of the user
if ( t.hasAttribute("from") && t.hasAttribute("type") ) {
Presence.Mode mode = Presence.Mode.away;
if (t.getAttribute("type").equals("available"))
mode = Presence.Mode.available;

Presence presp = new Presence(Presence.Type.available);
presp.setMode(mode);
presp.setFrom(MiscUtil.getUser(t.getAttribute("from")));
presp.setTo(MiscUtil.getUser(phone));
received_packets.add(presp);
}
}
else if (t.getTag().equals("iq")) {
if (t.hasAttribute("from") && t.hasAttribute("id") && t.hasChild("ping")) {
System.out.println("Received PING!\n");
@@ -336,6 +355,27 @@ void sendAuthResponse() {
outbuffer = outbuffer.addBuf(new DataBuffer(serialize_tree(t,false)));
}

public void addContact(String user, boolean user_request) {
user = MiscUtil.getUser(user);

for (int i = 0; i < contacts.size(); i++)
if (contacts.get(i).phone.equals(user))
return;

Contact c = new Contact(user, user_request);
contacts.add(c);

subscribePresence(user);
}

public void subscribePresence(String user) {
final String username = MiscUtil.getUser(user);
Tree request = new Tree("presence",
new HashMap < String,String >() {{ put("type","subscribe"); put("to",username); }} );

outbuffer = outbuffer.addBuf(new DataBuffer(serialize_tree(request,false)));
}


public byte [] getWriteData() {
byte [] r = outbuffer.getPtr();
@@ -395,14 +435,34 @@ public ChatMessage(String from, long time, String id, String message, String aut

public Packet serializePacket() {
Message message = new Message();
message.setTo(this.from);
message.setFrom("");
message.setTo(MiscUtil.getUser(phone));
message.setFrom(MiscUtil.getUser(this.from));
message.setType(Message.Type.chat);
message.setBody(this.message);

return message;
}
}


public class Contact {
String phone, name;
String presence, typing;
String status;
long last_seen, last_status;
boolean mycontact;
String ppprev, pppicture;
boolean subscribed;

Contact(String phone, boolean myc) {
this.phone = phone;
this.mycontact = myc;
this.last_seen = 0;
this.subscribed = false;
this.typing = "paused";
this.status = "";
}
};
}