Permalink
Browse files

preparing 0.2 release

  • Loading branch information...
1 parent 5be75a0 commit 0f7dc92831653f7a9abcb5991f8abe29035f97b3 @sch3m4 committed Sep 21, 2012
View
19 CHANGELOG
@@ -1,5 +1,22 @@
+WForensic (0.2)
+
+ * Updated attachments thumbnails display
+ * Shows if the involved files are writable or not
+ * Messages sorted by received date
+ * Removed 'decrypt.py' tool and a new one created ('encryption.py') to decrypt and encrypt
+ * Index layout has been modified
+ * Added script to run the DJango server (run.sh)
+ * View contacts profile
+ * The files wa.db and msgstore.db must be now placed into "databases/" directory
+ * Added support to download the entire contact list in XML format
+ * Fixed Base64 decode issue
+ * Added "tools/merge.py" script to merge many msgstore.db files into a single one
+ * wa.db contacts database is no longer required
+
+ -- Chema Garcia <chema@safetybits.net> (XX/YY/2012)
+
WForensic (0.1b)
* First release
- -- Chema Garcia <sch3m4@safetybits.net> (08/05/2012)
+ -- Chema Garcia <chema@safetybits.net> (08/05/2012)
View
130 README
@@ -1,130 +0,0 @@
-WForensic
-=========
-
-User-friendly DJango project to data interpretation and chart of messages activity from WhatsApp records, and also provides tools for decrypting encrypted 'msgstored' files and to merge many SQLite3 database files into a single one
-
-
-
-[+] Getting the source
-======================
-
-~$ git clone git://github.com/sch3m4/wforensic.git
-Cloning into 'wforensic'...
-remote: Counting objects: 136, done.
-remote: Compressing objects: 100% (117/117), done.
-remote: Total 136 (delta 11), reused 135 (delta 10)
-Receiving objects: 100% (136/136), 364.66 KiB | 163 KiB/s, done.
-Resolving deltas: 100% (11/11), done.
-~$
-
-
-
-[+] Initializing the environment
-================================
-~$ cd wforensic/
-~/wforensic$ cp -v /tmp/*.db .
-`/tmp/msgstore.db' -> `./msgstore.db'
-`/tmp/wa.db' -> `./wa.db'
-~/wforensic$ ls -1
-CHANGELOG
-LICENSE
-README
-msgstore.db
-tools
-wa.db
-wforensic
-~/wforensic$
-
-
-
-[+] Using an gzipped unencrypted msgstore
-=========================================
-~$ gzip -d whatsapp-2012-05-03.1.log.gz
-~$ mv whatsapp-2012-05-03.1.log wforensic/msgstore.db
-~$ python wforensic/wforensic/manage.py runserver
-Validating models...
-
-0 errors found
-Django version 1.4, using settings 'wforensic.settings'
-Development server is running at http://127.0.0.1:8000/
-Quit the server with CONTROL-C.
-
-
-
-[+] Decrypting all msgstore encrypted files
-=========================================
-$ python decrypt.py -d WhatsApp/Databases/ ./plain/
-
- #######################################
- # WhatsApp Forensic Tool 0.3 #
- #-------------------------------------#
- # Decrypts encrypted msgstore files #
- # This tool is part of WForensic #
- # https://github.com/sch3m4/wforensic #
- #######################################
-
-[i] Setting AES key....... OK
-
-[+] Decrypting msgstore-2012-06-29.1.db.crypt (msgstore-2012-06-29.1.db) -> 1282064 Bytes
- + 4765 Messages from 23 contacts
-
-[+] Decrypting msgstore.db.crypt (msgstore.db) -> 1311760 Bytes
- + 4922 Messages from 25 contacts
-
-[+] Decrypting msgstore-2012-06-28.1.db.crypt (msgstore-2012-06-28.1.db) -> 1258512 Bytes
- + 4673 Messages from 23 contacts
-
-[+] Decrypting msgstore-2012-06-24.1.db.crypt (msgstore-2012-06-24.1.db) -> 1243152 Bytes
- + 4618 Messages from 23 contacts
-
-[+] Decrypting msgstore-2012-06-25.1.db.crypt (msgstore-2012-06-25.1.db) -> 1246224 Bytes
- + 4631 Messages from 23 contacts
-
-[+] Decrypting msgstore-2012-06-26.1.db.crypt (msgstore-2012-06-26.1.db) -> 1249296 Bytes
- + 4633 Messages from 23 contacts
-
-[+] Decrypting msgstore-2012-06-30.1.db.crypt (msgstore-2012-06-30.1.db) -> 1289232 Bytes
- + 4807 Messages from 24 contacts
-
-[+] Decrypting msgstore-2012-07-01.1.db.crypt (msgstore-2012-07-01.1.db) -> 1311760 Bytes
- + 4919 Messages from 25 contacts
-
-[+] Done!
-$
-
-
-
-[+] Decrypting a msgstore encrypted file
-===========================================
-$ python decrypt.py -f WhatsApp/Databases/msgstore.db.crypt plain/msgstore1.db
-
- #######################################
- # WhatsApp Forensic Tool 0.3 #
- #-------------------------------------#
- # Decrypts encrypted msgstore files #
- # This tool is part of WForensic #
- # https://github.com/sch3m4/wforensic #
- #######################################
-
-[i] Setting AES key....... OK
-
-[+] Decrypting msgstore.db.crypt (msgstore.db) -> 1311760 Bytes
- + 4922 Messages from 25 contacts
-
-[+] Done!
-
-$
-
-
-
-[+] Running DJango
-==================
-~/wforensic$ python wforensic/manage.py runserver
-Validating models...
-
-0 errors found
-Django version 1.4, using settings 'wforensic.settings'
-Development server is running at http://127.0.0.1:8000/
-Quit the server with CONTROL-C.
-
-
View
213 README.md
@@ -0,0 +1,213 @@
+This project also provides tools for decrypting msgstored files and to merge many msgstore database files into a single one
+
+## Spanish post @ Security By Default
+http://www.securitybydefault.com/2012/05/whatsapp-forensics.html
+
+**IMPORTANT:** The path of msgstore.db and wa.db has been changed to the directory "databases/" inside the project root.
+
+
+## Getting the source
+
+ ~$ git clone git://github.com/sch3m4/wforensic.git
+ Cloning into 'wforensic'...
+ remote: Counting objects: 288, done.
+ remote: Compressing objects: 100% (222/222), done.
+ remote: Total 288 (delta 82), reused 221 (delta 50)
+ Receiving objects: 100% (288/288), 987.14 KiB | 229 KiB/s, done.
+ Resolving deltas: 100% (82/82), done.
+ ~$
+
+
+## Dependences
+To run this tool properly you should have solved the following dependences:
+ * Python interpreter
+ * DJango
+ * ReportLab
+
+In Debian-based systems, you can install them as follows:
+
+ $ sudo apt-get install python python-django python-reportlab
+
+
+## Retrieving msgstore.db and wa.db files
+
+Although there are many ways to get the needed files msgstore.db and wa.db (the last one is not really required), you can use the example application from PyADB project available here: https://github.com/sch3m4/pyadb
+So once it is installed, you can continue as follows:
+
+ ~/pyadb/example$ python whatsapp.py
+ [+] Using PyADB version 0.1.0
+ [+] Verifying ADB path... OK
+ [+] ADB Version: 1.0.29
+
+ [+] Restarting ADB server...
+ [+] Detecting devices... OK
+ 0: XXXXXXXXXXXX
+
+ [+] Using "XXXXXXXXXXXX" as target device
+ [+] Looking for 'su' binary: /system/xbin/su
+ [+] Checking if 'su' binary can give root access:
+ - Yes
+
+ [+] Copying Whatsapp data folder
+ - Local destination [/home/sch3m4/pyadb/example]:
+
+ [+] Creating remote tar file: /sdcard/whatsapp_JKsBgtryAa.tar
+ - Command: /system/xbin/su -c 'tar -c /data/data/com.whatsapp -f /sdcard/whatsapp_JKsBgtryAa.tar'
+
+ [+] Retrieving remote file: /sdcard/whatsapp_JKsBgtryAa.tar
+ [+] Removing remote file: /sdcard/whatsapp_JKsBgtryAa.tar
+
+ [+] Remote Whatsapp files from device memory are now locally accessible at "/home/sch3m4/pyadb/example/databases/whatsapp_JKsBgtryAa.tar"
+
+ [+] Looking for 'tar' binary... /system/xbin/tar
+
+ [+] Creating remote tar file: /sdcard/whatsapp_VFeCdqTSCg.tar
+ + Command: /system/xbin/tar -c /sdcard/WhatsApp -f /sdcard/whatsapp_VFeCdqTSCg.tar
+
+ [+] Remote tar file created: /sdcard/whatsapp_VFeCdqTSCg.tar
+ - Local destination [/home/sch3m4/pyadb/example]:
+
+ [+] Retrieving remote file: /sdcard/whatsapp_VFeCdqTSCg.tar...
+
+ [+] WhatsApp SDcard folder is now available in tar file: /home/sch3m4/pyadb/example/whatsapp_VFeCdqTSCg.tar
+
+ ~/pyadb/example$
+
+**IMPORTANT:** This application example does not belongs to WForensic project. (May it be in the future?)
+
+Once you have exported the needed files, you'll need to change the files & folder owner and untar the files:
+
+ ~/pyadb/example$ tar xf whatsapp_VFeCdqTSCg.tar
+ ~/pyadb/example$ sudo chown -R sch3m4:sch3m4 sdcard/
+ ~/pyadb/example$ sudo chown -R sch3m4:sch3m4 databases/
+ ~/pyadb/example$ tar xf databases/whatsapp_JKsBgtryAa.tar
+ ~/pyadb/example$ sudo chown -R sch3m4:sch3m4 data/
+ ~/pyadb/example$ sudo chmod -R 755 sdcard/ data/
+
+And you'll get the msgstore & wa.db files at:
+
+ ~/pyadb/example$ ls -l sdcard/WhatsApp/Databases/ data/data/com.whatsapp/databases/
+ data/data/com.whatsapp/databases/:
+ total 1732
+ -rwxr-xr-x 1 sch3m4 sch3m4 1740800 jul 30 13:32 msgstore.db
+ -rwxr-xr-x 1 sch3m4 sch3m4 26624 jul 30 13:32 wa.db
+
+ sdcard/WhatsApp/Databases/:
+ total 13060
+ -rwxr-xr-x 1 sch3m4 sch3m4 1575952 jul 22 04:00 msgstore-2012-07-23.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1613840 jul 24 04:00 msgstore-2012-07-24.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1640464 jul 24 17:22 msgstore-2012-07-25.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1653776 jul 25 04:00 msgstore-2012-07-27.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1680400 jul 27 04:00 msgstore-2012-07-28.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1697808 jul 28 04:00 msgstore-2012-07-29.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1723408 jul 29 04:00 msgstore-2012-07-30.1.db.crypt
+ -rwxr-xr-x 1 sch3m4 sch3m4 1736720 jul 30 04:00 msgstore.db.crypt
+
+## Decrypting many msgstore files
+
+Following the above instructions, the next step is to decrypt all the *.crypt files. To do that, follow the next steps:
+
+ $ cd ~/wforensic/tools/
+ ~/wforensic/tools$ python encryption.py --decrypt --dir encrypted/ --output-dir decrypted
+
+ #######################################
+ # WhatsApp Encryption Tool 0.3 #
+ #-------------------------------------#
+ # Decrypts encrypted msgstore files #
+ # This tool is part of WForensic #
+ # https://sch3m4.github.com/wforensic #
+ #######################################
+
+ [i] Setting AES key....... OK
+
+ [+] Decrypting msgstore-2012-09-19.1.db.crypt (msgstore-2012-09-19.1.db) -> 2789392 Bytes
+ + 11544 Messages from 44 contacts
+
+ [+] Decrypting msgstore-2012-09-18.1.db.crypt (msgstore-2012-09-18.1.db) -> 2700304 Bytes
+ + 11202 Messages from 43 contacts
+
+ [+] Decrypting msgstore-2012-09-15.1.db.crypt (msgstore-2012-09-15.1.db) -> 2515984 Bytes
+ + 10375 Messages from 43 contacts
+
+ [+] Decrypting msgstore-2012-09-20.1.db.crypt (msgstore-2012-09-20.1.db) -> 2863120 Bytes
+ + 11865 Messages from 44 contacts
+
+ [+] Decrypting msgstore-2012-09-16.1.db.crypt (msgstore-2012-09-16.1.db) -> 2649104 Bytes
+ + 10963 Messages from 43 contacts
+
+ [+] Done!
+
+ ~/wforensic/tools$
+
+## Merging all msgstore files into a single one
+
+At this step, you'll get many msgstore files, so to merge them you can use another tool located at wforensic/tools called 'merge.py':
+
+ ~/wforensic/tools$ cp ~/pyadb/example/data/data/com.whatsapp/databases/msgstore.db plain/msgstore1.db
+ ~/wforensic/tools$ python merge.py plain/ msgstore* ./msgstore.db
+
+ #######################################
+ # WhatsApp Msgstore Merge Tool 0.2b #
+ #-------------------------------------#
+ # Merges WhatsApp message files into #
+ # a single one. #
+ # This tool is part of WForensic #
+ # https://github.com/sch3m4/wforensic #
+ #######################################
+
+ [i] Origin: decrypted/msgstore*
+ [i] Output file: ./msgstore.db
+
+ + Merging: msgstore-2012-09-15.1.db
+ + Merging: msgstore-2012-09-20.1.db (Merged 1 contacts and 1490 messages)
+ + Merging: msgstore-2012-09-16.1.db (Merged 0 contacts and 0 messages)
+ + Merging: msgstore1.db (Merged 0 contacts and 233 messages)
+ + Merging: msgstore-2012-09-18.1.db (Merged 0 contacts and 0 messages)
+ + Merging: msgstore-2012-09-19.1.db (Merged 0 contacts and 0 messages)
+
+ ~/wforensic/tools$
+
+At this point, the final msgstore.db file is located at "~/wforensic/tools/msgstore.db"
+
+## Environment schema
+
+The last point, is to move/copy/link the needed files (remember, wa.db is not really needed but useful) to the correct path:
+
+ ~/wforensic/tools$ cp msgstore.db ../databases
+ ~/wforensic/tools$ cp ~/pyadb/example/data/data/com.whatsapp/databases/wa.db ../databases/
+ ~/wforensic$ cd .. && ls -1
+ CHANGELOG
+ databases
+ LICENSE
+ README
+ tools
+ wforensic
+ ~/wforensic$ ls -1 databases/
+ msgstore.db
+ wa.db
+ ~/wforensic$
+
+## Running the application
+
+ ~/wforensic$ ./run.sh
+ Python 2.7.3
+
+ ==========================================================================
+ = ==== ==== == =================================================
+ = ==== ==== == =======================================================
+ = ==== ==== == =======================================================
+ = ==== ==== == ========= === = ==== === = ==== === === ==
+ = == == === ==== == = == = == == = ====== = =
+ == == == === ======== = == ======= == = === ==== == ====
+ == == == === ======== = == ======= ===== = ==== === == ====
+ === == ==== ======== = == ======= = == = == = == == = =
+ ==== ==== ===== ========= === ======== === = === === === ==
+ ==========================================================================
+
+ Validating models...
+
+ 0 errors found
+ Django version 1.4.1, using settings 'wforensic.settings'
+ Development server is running at http://127.0.0.1:8000/
+ Quit the server with CONTROL-C.
+
View
2 databases/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
View
29 run.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+PYBIN=`which python`
+
+if [ -z "$PYBIN" ]
+then
+ echo "Cannot find python binary"
+ exit -1
+fi
+
+echo -n $($PYBIN --version)
+
+echo "
+ ==========================================================================
+ = ==== ==== == =================================================
+ = ==== ==== == =======================================================
+ = ==== ==== == =======================================================
+ = ==== ==== == ========= === = ==== === = ==== === === ==
+ = == == === ==== == = == = == == = ====== = =
+ == == == === ======== = == ======= == = === ==== == ====
+ == == == === ======== = == ======= ===== = ==== === == ====
+ === == ==== ======== = == ======= = == = == = == == = =
+ ==== ==== ===== ========= === ======== === = === === === ==
+ ==========================================================================
+"
+
+$PYBIN wforensic/manage.py runserver
+
+exit $?
View
203 tools/decrypt.py → tools/encryption.py
@@ -10,10 +10,10 @@
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# Neither the name of the SafetyBits nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
#
@@ -27,163 +27,192 @@
# www.securitybydefault.com
# http://twitter.com/aramosf
#
-# This tool is part of WhatsApp Forensic (https://github.com/sch3m4/wforensic)
+# This tool is part of WhatsApp Forensic / https://sch3m4.github.com/wforensic
#
-# Version: 0.3
+# Version: 0.4
#
+#
+
try:
import os
import sys
import errno
import sqlite3
+ import argparse
from Crypto.Cipher import AES
-except ImportError,e:
+except ImportError, e:
print "[f] Required module missing. %s" % e.args[0]
sys.exit(-1)
-
+
key = "\x34\x6a\x23\x65\x2a\x46\x39\x2b\x4d\x73\x25\x7c\x67\x31\x7e\x35\x2e\x33\x72\x48\x21\x77\x65\x2c"
aes = None
+MODE_ENCRYPT = 1
+MODE_DECRYPT = 2
+
+mode = None
+
+
def getinfo(path):
db = sqlite3.connect(path)
cur = db.cursor()
- res = cur.execute("SELECT COUNT(*) FROM messages UNION ALL SELECT COUNT(DISTINCT key_remote_jid) FROM chat_list;").fetchall()
+ res = cur.execute("SELECT COUNT(*) FROM messages UNION ALL SELECT COUNT(DISTINCT key_remote_jid) FROM chat_list").fetchall()
cur.close()
db.close()
-
- return (res[0][0],res[1][0])
-def decrypt_file(path,dest):
+ return (res[0][0], res[1][0])
+
+
+def set_aes():
global aes
-
if aes is None:
# shoulds never fail
- print "[i] Setting AES key......." ,
+ print "[i] Setting AES key.......",
try:
- aes = AES.new(key,AES.MODE_ECB)
+ aes = AES.new(key, AES.MODE_ECB)
print "OK"
- except Exception,e:
+ except Exception, e:
print "ERROR: %s" % e.msg
sys.exit(-4)
- # open input file
- print "\n[+] Decrypting %s (%s) ->" % (os.path.basename(path),os.path.basename(dest)) ,
- sys.stdout.flush()
+def work_file(path, dest):
+ global aes
+ global mode
+ global MODE_ENCRYPT
+ global MODE_DECRYPT
+
+ set_aes()
+
+ # open input file
try:
- ctext = open(path,'rb')
- except Exception , e:
+ ctext = open(path, 'rb')
+ except Exception, e:
print "ERROR: %s" % e.msg
sys.exit(-5)
-
+
# open output file
try:
- ptext = open(dest,"wb")
- except Exception,e:
+ dfile = os.path.basename(path)
+ if mode == MODE_DECRYPT:
+ dest += dfile.replace('.crypt', '')
+ else:
+ dest += dfile + '.crypt'
+ ptext = open(dest, "wb")
+ except Exception, e:
print "ERROR: %s" % e.msg
ctext.close()
sys.exit(-6)
+ if mode == MODE_ENCRYPT:
+ print "\n[+] Encrypting",
+ else:
+ print "\n[+] Decrypting",
+
+ print "%s (%s) ->" % (os.path.basename(path), os.path.basename(dest)),
+ sys.stdout.flush()
+
# read input file and outputs decrypted block to output file
cbytes = 0
backwards = 0
for block in iter(lambda: ctext.read(AES.block_size), ''):
- ptext.write(aes.decrypt(block))
+ if mode == MODE_ENCRYPT:
+ ptext.write(aes.encrypt(block))
+ else:
+ ptext.write(aes.decrypt(block))
+
cbytes += AES.block_size
for i in range(backwards):
sys.stdout.write("\b")
backwards = len(str(cbytes)) + len(" Bytes")
-
- print "%d Bytes" % cbytes ,
+
+ print "%d Bytes" % cbytes,
sys.stdout.flush()
ctext.close()
ptext.close()
-
- totmsg,peermsg = getinfo(dest)
- print "\n\t+ %d Messages from %d contacts" % (totmsg,peermsg)
- sys.stdout.flush()
-def decrypt_dir(path,dest):
- global aes
+ if mode == MODE_DECRYPT:
+ totmsg, peermsg = getinfo(dest)
+ print "\n\t+ %d Messages from %d contacts" % (totmsg, peermsg)
+ sys.stdout.flush()
+
+
+def work_dir(path, dest):
+
+ set_aes()
- # shoulds never fail
- print "[i] Setting AES key......." ,
- try:
- aes = AES.new(key,AES.MODE_ECB)
- print "OK"
- except Exception,e:
- print "ERROR: %s" % e.msg
- sys.exit(-4)
-
for filename in os.listdir(path):
if not os.path.isfile(path + filename):
continue
-
- decrypt_file(path+filename,dest+filename.replace(".crypt",""))
+
+ work_file(path + filename, dest )
return
-def usage(base):
- print "[i] Usage: %s [options] <output>" % base
- print "\n+ Options:"
- print "\t-f | --file <path> --------> Path to file to be decrypted"
- print "\t-d | --dir <path> ---------> Directory containing encrypted 'msgstore' files"
- print "\n+ Example: %s -f msgstore-2012-05-07.1.db.crypt msgstore-2012-05-07.1.db" % base
- print "+ Example: %s -d msgFiles/ plain/\n" % base
- sys.exit(0)
-
+
if __name__ == "__main__":
print """
#######################################
- # WhatsApp Forensic Tool 0.3 #
+ # WhatsApp Encryption Tool 0.3 #
#-------------------------------------#
# Decrypts encrypted msgstore files #
# This tool is part of WForensic #
- # https://github.com/sch3m4/wforensic #
+ # https://sch3m4.github.com/wforensic #
#######################################
"""
- if not len(sys.argv) is 4:
- usage(sys.argv[0])
-
- if not sys.argv[1] in ["-f","--file","-d","--dir"]:
- usage(sys.argv[0])
-
- sys.argv[1] = sys.argv[1].replace("--file","-f")
- sys.argv[1] = sys.argv[1].replace("--dir","-d")
-
- # decrypt the whole directory
- if sys.argv[1] == "-d":
+ # specify what arguments do we accept
+ parser = argparse.ArgumentParser()
+ group1 = parser.add_mutually_exclusive_group(required=True)
+ group1.add_argument('--encrypt', help='Encryption mode', dest='operation' , action='store_const', const='encrypt')
+ group1.add_argument('--decrypt', help='Decryption mode', dest='operation' , action='store_const', const='decrypt')
+ group2 = parser.add_mutually_exclusive_group(required=True)
+ group2.add_argument('--file', type=str, help='Encryption mode', dest='file' , action='store')
+ group2.add_argument('--dir', type=str, help='Decryption mode', dest='dir' , action='store')
+ parser.add_argument('--output-dir', type=str , help='Output directory' , dest='output', action='store' , required = True)
+ args = parser.parse_args()
+
+ if args.operation == 'encrypt':
+ mode = MODE_ENCRYPT
+ else:
+ mode = MODE_DECRYPT
- if sys.argv[2][-1:] != '/':
- sys.argv[2] += '/'
+ if args.output[:1] == '.':
+ args.output += '/'
+
+ # directory
+ if args.dir is not None:
+
+ if args.dir[-1:] != '/':
+ args.dir += '/'
- if sys.argv[3][-1:] != '/':
- sys.argv[3] += '/'
-
- if not os.path.isdir(sys.argv[2]):
+ if args.output[-1:] != '/':
+ args.output += '/'
+
+ if not os.path.isdir(args.dir):
print "[e] Input directory not found!"
sys.exit(-1)
-
- try:
- os.makedirs(os.path.dirname(sys.argv[3]))
- except OSError as err:
- if err.errno == errno.EEXIST:
- pass
- else:
- print "[e] Error creating output directory: %s\n"
- sys.exit(-2)
- decrypt_dir ( sys.argv[2] , sys.argv[3] )
- elif not os.path.isfile(sys.argv[2]):
+ elif not os.path.isfile(args.file):
print "[e] Input file not found!"
sys.exit(-3)
- else: # decrypt a single file
- decrypt_file ( sys.argv[2] , sys.argv[3] )
- print "\n[+] Done!\n"
+ try:
+ os.makedirs(os.path.dirname(args.output))
+ except OSError as err:
+ if err.errno == errno.EEXIST:
+ pass
+ else:
+ print "[e] Error creating output directory: %s\n"
+ sys.exit(-2)
+
+ if args.dir is not None:
+ work_dir(args.dir, args.output)
+ else: # single file
+ work_file(args.file, args.output )
+
+ print "\n[+] Done!\n"
sys.exit(0)
-
View
211 tools/merge.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
#
-# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-# !!!! THIS SCRIPT IS NOT WORKING DUE TO PRIMARY KEY AND UNIQUE VALUES MISTAKES !!!!
-# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-#
-#
#-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# Copyright (c) 2012, Chema Garcia
# All rights reserved.
@@ -15,10 +10,10 @@
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# Neither the name of the SafetyBits nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
#
@@ -28,168 +23,132 @@
# http://safetybits.net
# http://twitter.com/sch3m4
#
-# This tool is part of WhatsApp Forensic (https://github.com/sch3m4/wforensic)
+# This tool is part of WhatsApp Forensic / https://sch3m4.github.com/wforensic
#
-# Version: 0.1
+# Version: 0.2b
#
try:
import os
import re
import sys
+ import shutil
import sqlite3
except ImportError,e:
print "[f] Required module missing. %s" % e.args[0]
sys.exit(-1)
-
-# method "iterdump" from sqlite3 currently fails (http://bugs.python.org/issue15109)
-# renamed "iterdump" from python-pysqlite2
-def dump_iterator(connection):
- """
- Returns an iterator to the dump of the database in an SQL text format.
- Used to produce an SQL dump of the database. Useful to save an in-memory
- database for later restoration. This function should not be called
- directly but instead called from the Connection method, iterdump().
+def merge(path,pattern,dest):
+ """
+ Reads from files in 'path' and dumps its contents to 'dest'
"""
- cu = connection.cursor()
- yield('BEGIN TRANSACTION;')
-
- # sqlite_master table contains the SQL CREATE statements for the database.
- q = """
- SELECT name, type, sql
- FROM sqlite_master
- WHERE sql NOT NULL AND
- type == 'table'
- """
- schema_res = cu.execute(q)
- for table_name, type, sql in schema_res.fetchall():
- if table_name == 'sqlite_sequence':
- yield('DELETE FROM sqlite_sequence;')
- elif table_name == 'sqlite_stat1':
- yield('ANALYZE sqlite_master;')
- elif table_name.startswith('sqlite_'):
- continue
- # NOTE: Virtual table support not implemented
- #elif sql.startswith('CREATE VIRTUAL TABLE'):
- # qtable = table_name.replace("'", "''")
- # yield("INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"\
- # "VALUES('table','%s','%s',0,'%s');" %
- # qtable,
- # qtable,
- # sql.replace("''"))
- else:
- yield('%s;' % sql)
-
- # Build the insert statement for each row of the current table
- res = cu.execute("PRAGMA table_info('%s')" % table_name)
- column_names = [str(table_info[1]) for table_info in res.fetchall()]
- q = "SELECT 'INSERT INTO \"%(tbl_name)s\" VALUES("
- q += ",".join(["'||quote(" + col + ")||'" for col in column_names])
- q += ")' FROM '%(tbl_name)s'"
- query_res = cu.execute(q % {'tbl_name': table_name})
- for row in query_res:
- #yield("%s;" % row[0])
- yield(row[0] + ';')
-
- # Now when the type is 'index', 'trigger', or 'view'
- q = """
- SELECT name, type, sql
- FROM sqlite_master
- WHERE sql NOT NULL AND
- type IN ('index', 'trigger', 'view')
- """
- schema_res = cu.execute(q)
- for name, type, sql in schema_res.fetchall():
- yield('%s;' % sql)
-
-def merge(path,pattern,dest):
-
- output = sqlite3.connect(dest)
-
- len1 = 0 # current file
- len2 = 0 # output file
- backwards = 0
+ first = 0
+ output = None
+ mtableid = 0
for filename in os.listdir(path):
- if not os.path.isfile(path + filename) or not re.match(pattern,filename):
+ if not os.path.isfile(path + filename) or not re.match(pattern, filename):
continue
-
- len1 = 0
- backwards = 0
-
- print "+ Merging: %s -> " % filename ,
+
+ print "\n+ Merging: %s" % filename ,
sys.stdout.flush()
-
+
+ if first == 0:
+ shutil.copy2 (path + filename, dest)
+ first += 1
+ continue
+ elif output is None:
+ output = sqlite3.connect(dest)
+ wcursor = output.cursor()
+
+ ccontacts = 0
+ cmessages = 0
+
+ # get all remote_key_jid values from messages table
orig = sqlite3.connect(path + filename)
-
- for line in dump_iterator(orig):
+ rcursor = orig.cursor()
+
+ if mtableid == 0:
+ # get biggest message_table_id value (what is this column for? :-/ )
+ wcursor.execute("SELECT MAX(message_table_id) FROM chat_list")
try:
- lenline = len(str(line))
+ mtableid = wcursor.fetchone()[0]
+ except:
+ print "\n\t- Error getting MAX(message_table_id), skipping file..."
+ continue
+
+ # get all key_remote_jid from the current file
+ rcursor.execute("SELECT DISTINCT key_remote_jid FROM chat_list")
- if str(line)[:6] == "CREATE":
- line = str(line).replace("UNIQUE","")
- line = str(line).replace("PRIMARY KEY AUTOINCREMENT","")
+ # if each item from the above query does not exists, insert it
+ for krjid in rcursor:
+ wcursor.execute("SELECT key_remote_jid FROM chat_list WHERE key_remote_jid=?",krjid)
+ try:
+ if len(wcursor.fetchone()[0]) > 0:
+ continue
except:
- lenline = 0
- pass
-
- len1 += lenline
- len2 += lenline
-
+ try:
+ mtableid += 1 # increments message_table_id
+ data = (krjid[0], mtableid)
+ wcursor.execute("INSERT INTO chat_list (key_remote_jid,message_table_id) VALUES (?,?)", data)
+ ccontacts += 1
+ except:
+ pass
+
+ # get all messages from messages table
+ rcursor.execute("SELECT key_remote_jid,key_from_me,key_id,status,needs_push,data,timestamp,media_url,media_mime_type,media_wa_type,media_size,media_name,latitude,longitude,thumb_image,remote_resource,received_timestamp,send_timestamp,receipt_server_timestamp,receipt_device_timestamp,raw_data FROM messages")
+ messages = rcursor.fetchall()
+ for msg in messages:
try:
- output.execute(line)
- except sqlite3.OperationalError, msg:
+ wcursor.execute("INSERT INTO messages(key_remote_jid,key_from_me,key_id,status,needs_push,data,timestamp,media_url,media_mime_type,media_wa_type,media_size,media_name,latitude,longitude,thumb_image,remote_resource,received_timestamp,send_timestamp,receipt_server_timestamp,receipt_device_timestamp,raw_data) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",msg)
+ cmessages += 1
+ except:
pass
-
- # print progress
- for i in range(backwards):
- sys.stdout.write("\b")
-
- backwards = len(str(len1)) + len(str(len2)) + len("R: W: / Bytes") + 1
-
- print "R: %d / W: %d Bytes" % (len1,len2) ,
- sys.stdout.flush()
-
+
output.commit()
+
+ print " (Merged %d contacts and %d messages)" % (ccontacts,cmessages) ,
+ sys.stdout.flush()
+
orig.close()
-
- print ""
- output.close()
+ if output is not None:
+ output.close()
return
if __name__ == "__main__":
print """
#######################################
- # SQLite3 Database Merge Tool 0.1 #
+ # WhatsApp Msgstore Merge Tool 0.2b #
#-------------------------------------#
- # Merges SQLite3 database files into #
+ # Merges WhatsApp message files into #
# a single one. #
# This tool is part of WForensic #
- # https://github.com/sch3m4/wforensic #
- #######################################\n
+ # https://sch3m4.github.com/wforensic #
+ #######################################
"""
-
+
if len(sys.argv) != 4:
print "Usage: %s /path/to/databases/to/be/merged/ files_pattern /path/to/output\n" % sys.argv[0]
sys.exit(-1)
-
+
if sys.argv[1][-1:] != '/':
sys.argv[1] += '/'
-
- if not os.path.isdir(os.path.dirname(sys.argv[3])):
+
+ dir = os.path.dirname(sys.argv[3])
+
+ if len(dir) > 0 and not os.path.isdir(dir):
print "[e] Error: Directory \"%s\" does not exists\n" % sys.argv[3]
sys.exit(-2)
-
+
if not os.path.isdir(sys.argv[1]):
print "[e] Error: \"%s\" is not a directory\n" % sys.argv[1]
sys.exit(-3)
-
+
print "[i] Origin: %s%s" % ( sys.argv[1] , sys.argv[2] )
- print "[i] Output file: %s\n" % sys.argv[3]
-
+ print "[i] Output file: %s" % sys.argv[3]
+
merge(sys.argv[1],sys.argv[2], sys.argv[3])
- print ""
- sys.exit(0)
+ print "\n"
+ sys.exit(0)
View
2 wforensic/manage.py
@@ -12,4 +12,4 @@
import settings
if __name__ == "__main__":
- execute_manager(settings)
+ execute_manager(settings)
View
22 wforensic/settings.py
@@ -1,16 +1,22 @@
# Django settings for wforensic project.
import os
-from os.path import dirname
+
DEBUG = True
TEMPLATE_DEBUG = DEBUG
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
-CONTACTS_PER_PAGE = 15
-CHATS_PER_PAGE = 15
-MESSAGES_PER_PAGE = 20
-LATEST_PEERS = 10
-TOP_PEERS = 10
+# tool version
+VERSION = '0.2b'
+
+# YOU CAN EDIT THE BELOW SETTINGS
+##############################################################
+CONTACTS_PER_PAGE = 15 # number of contacts to show per page
+CHATS_PER_PAGE = 15 # number of chats to show per page
+MESSAGES_PER_PAGE = 20 # number of messages to show per page
+LATEST_PEERS = 10 # number of peers to show as latest
+TOP_PEERS = 10 # number of peers to show as top
+##############################################################
ADMINS = (
('Chema Garcia', 'chema@safetybits.net'),
@@ -21,11 +27,11 @@
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': dirname(SITE_ROOT) + '/wa.db',
+ 'NAME': os.path.dirname(SITE_ROOT) + '/databases/wa.db',
},
'msgstore': {
'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': dirname(SITE_ROOT) + '/msgstore.db',
+ 'NAME': os.path.dirname(SITE_ROOT) + '/databases/msgstore.db',
}
}
View
BIN wforensic/static/img/favicon.ico
Binary file not shown.
View
BIN wforensic/static/img/powered/firefox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
26 wforensic/templates/base.html
@@ -1,4 +1,4 @@
-{% load gettemplateheader_tag %}
+{% load gettemplateheader %}
<html>
<head>
<script type="text/javascript" src="{{ STATIC_URL }}css/lib/jquery-1.7.1.min.js" ></script>
@@ -35,7 +35,23 @@
<h2><img src="{{STATIC_URL}}img/logo.png" width="104px" height="142px"/></br>WhatsApp Forensic</h2>
{% gettemplateheader %}
-<a href="{% url home %}">Home</a> | <a href="{% url contacts %}">Contacts</a> ({{theader.contacts}}) | <a href="{% url chats %}">Chat list</a> ({{theader.chats}}) | <a href="{% url messages %}">Messages</a> ({{theader.messages}}) | <a href="{% url messages_media %}">Media</a> ({{theader.media}}) |<a href="{% url messages_gps %}">GPS</a> ({{theader.gps}})
+<a href="{% url home %}">Home</a> |
+{% if theader.contacts > 0 %}
+<a href="{% url contacts %}">Contacts</a> ({{theader.contacts}}) |
+{% endif %}
+{% if theader.chats > 0 %}
+<a href="{% url chats %}">Chat list</a> ({{theader.chats}}) |
+{% endif %}
+{% if theader.messages > 0 %}
+<a href="{% url messages %}">Messages</a> ({{theader.messages}}) |
+{% endif %}
+{% if theader.media > 0 %}
+<a href="{% url messages_media %}">Media</a> ({{theader.media}}) |
+{% endif %}
+{% if theader.gps > 0 %}
+<a href="{% url messages_gps %}">GPS</a> ({{theader.gps}}) |
+{% endif %}
+<a href="{% url search %}">Search</a>
<div class="ym-wrapper">
<div class="ym-wbox">
@@ -44,17 +60,19 @@
</div>
<p></br></p>
+<hr>
<div align="center">
<div class="footer">
<em>
- <a href="https://github.com/sch3m4/wforensic" target="_blank">WhatsApp Forensic v0.1b</a><br>
+ <a href="http://sch3m4.github.com/wforensic" target="_blank">WhatsApp Forensic v0.2</a><br>
Written by <a href="mailto:chema@safetybits.net">Chema Garc&iacute;a</a> (aka <a href="https://twitter.com/sch3m4" target="_blank">sch3m4</a>) / <a href="http://safetybits.net" target="_blank">SafetyBits</a>
</em><br/><br/>
<img src="{{ STATIC_URL }}img/powered/django.png">
<img src="{{ STATIC_URL }}img/powered/sqlite.png">
+ <img src="{{ STATIC_URL }}img/powered/firefox.png">
<p></p>
</div>
</div>
</center>
-</body>
+</body>
View
18 wforensic/urls.py
@@ -14,12 +14,14 @@
# Uncomment the next line to enable the admin:
# url(r'^admin/', include(admin.site.urls)),
- url(r'^$' , 'whatsapp.views.index',name='home'),
- url(r'^contacts/$' , 'whatsapp.views.contacts',name='contacts'),
- url(r'^chats/$' , 'whatsapp.views.chatlist',name='chats'),
- url(r'^messages/$' , 'whatsapp.views.messages',name='messages'),
- url(r'^messages/(?P<key>[\-?0-9a-zS\.\@]+)$' , 'whatsapp.views.single_chat',name='single_chat'),
- url(r'^mediamsg/$' , 'whatsapp.views.messages_media',name='messages_media'),
- url(r'^gpsmsg/$' , 'whatsapp.views.messages_gps',name='messages_gps'),
- url(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/img/favicon.ico'}),
+ url(r'^$', 'whatsapp.views.index', name='home'),
+ url(r'^contacts/download$', 'whatsapp.views.contacts_download', name='download_contacts'),
+ url(r'^contacts/$', 'whatsapp.views.contacts', name='contacts'),
+ url(r'^contacts/(?P<key>[\-?0-9a-zS\.\@]+)$', 'whatsapp.views.contact_profile', name='contact_profile'),
+ url(r'^chats/$', 'whatsapp.views.chatlist', name='chats'),
+ url(r'^messages/$', 'whatsapp.views.messages', name='messages'),
+ url(r'^messages/(?P<key>[\-?0-9a-zS\.\@]+)$', 'whatsapp.views.single_chat', name='single_chat'),
+ url(r'^mediamsg/$', 'whatsapp.views.messages_media', name='messages_media'),
+ url(r'^gpsmsg/$', 'whatsapp.views.messages_gps', name='messages_gps'),
+ url(r'^favicon.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/img/favicon.ico'}),
)
View
22 wforensic/whatsapp/models.py
@@ -1,11 +1,18 @@
from django.db import models
+from django.forms import ModelForm, Textarea
+
class AndroidMetadata(models.Model):
locale = models.TextField(blank=True)
+
class Meta:
db_table = u'android_metadata'
+
class WaContacts(models.Model):
+ """
+ Contact model
+ """
_id = models.IntegerField(primary_key=True, blank=True)
jid = models.TextField()
is_whatsapp_user = models.IntegerField()
@@ -18,10 +25,15 @@ class WaContacts(models.Model):
phone_label = models.TextField(blank=True)
unseen_msg_count = models.IntegerField(null=True, blank=True)
photo_ts = models.IntegerField(null=True, blank=True)
+
class Meta:
db_table = u'wa_contacts'
+
class Messages(models.Model):
+ """
+ Message model
+ """
_id = models.IntegerField(primary_key=True, blank=True)
key_remote_jid = models.TextField()
key_from_me = models.IntegerField(null=True, blank=True)
@@ -43,13 +55,19 @@ class Messages(models.Model):
send_timestamp = models.IntegerField(null=True, blank=True)
receipt_server_timestamp = models.IntegerField(null=True, blank=True)
receipt_device_timestamp = models.IntegerField(null=True, blank=True)
+ raw_data = models.TextField(blank=True)
class Meta:
db_table = u'messages'
+
class ChatList(models.Model):
- _id = models.IntegerField( primary_key=True, blank=True)
+ """
+ Chat list model
+ """
+ _id = models.IntegerField(primary_key=True, blank=True)
key_remote_jid = models.TextField(unique=True, blank=True)
message_table_id = models.IntegerField(null=True, blank=True)
+
class Meta:
- db_table = u'chat_list'
+ db_table = u'chat_list'
View
73 wforensic/whatsapp/templates/whatsapp/chat.html
@@ -1,22 +1,7 @@
{% extends "base.html" %}
-{% if activity %}
- {% block head %}
- {% include "activitychart.html" %}
- {% endblock %}
-{% endif %}
-
{% block body %}
-{% if activity %}
-<script type="text/javascript" src="{{STATIC_URL}}js/highcharts.js"></script>
-<script type="text/javascript" src="{{STATIC_URL}}js/modules/exporting.js"></script>
-
-<div class="ym-g60">
- <div id="wa_activity" style="margin: 0 auto"></div>
-</div>
-{% endif %}
-
<div class="ym-g50">
<div style="background: red;">
<h6><span style="color: white;">{{PAG_TITLE}}</span></h6>
@@ -27,35 +12,49 @@
{% for item in chatmessages.object_list %}
+
{% ifequal item.key_from_me 0 %}
<div class="ym-fbox-text" style="background: #e8ff99;">
<div class="ym-gr">{{item.received_timestamp}}</div>
- <div class="ym-gl" align="left"><h6>
- {% if item.media_url %}
- <a target="_new" href="{{item.media_url}}"><img src="{{STATIC_URL}}img/media.png" width="24px" height="24px" /></a>
- {% endif %}
- {% if item.longitude %}
- <a target="_new" href="https://maps.google.com/maps?q={{item.latitude}},+{{item.longitude}}+({{item.key_remote_jid}} / {{item.received_timestamp}})"><img src="{{STATIC_URL}}img/map.png" width=24px" height="24px" alt="{{item.latitude}},{{item.longitude}}"></a>
- {% endif %}
- <img src="{{STATIC_URL}}img/thumb.png" width="24px" height="24px" /><a href="{% url single_chat key=item.key_remote_jid %}">{{item.display_name}}</a></h6>
-
+ <div class="ym-gl" align="left">
{% else %}
<div class="ym-fbox-text" style="background: #d1ffff;">
<div class="ym-gl">{{item.received_timestamp}}</div>
- <div class="ym-gr" align="right"><h6>
- <img src="{{STATIC_URL}}img/thumb.png" width="24px" height="24px" /><a href="{% url single_chat key=item.key_remote_jid %}">{{item.display_name}}</a> &lArr; Me
- {% if item.media_url %}
- <a target="_new" href="{{item.media_url}}"><img src="{{STATIC_URL}}img/media.png" width="24px" height="24px"/></a>
- {% endif %}
- {% if item.longitude %}
- <a target="_new" href="https://maps.google.com/maps?q={{item.latitude}},+{{item.longitude}}+(Me / {{item.received_timestamp}})"><img src="{{STATIC_URL}}img/map.png" width=24px" height="24px" alt="{{item.latitude}},{{item.longitude}}"></a>
- {% endif %}
- </h6>
+ <div class="ym-gr" align="right">
+ {% endifequal %}
+ <h6>
+
+ {% if item.media_url %}
+ <a target="_new" href="{{item.media_url}}"><img src="{{STATIC_URL}}img/media.png" width="24px" height="24px" /></a>
+ {% endif %}
+
+
+ {% if item.longitude %}
+ {% ifequal item.key_from_me 0 %}
+ <a target="_new" href="https://maps.google.com/maps?q={{item.latitude}},+{{item.longitude}}+({{item.key_remote_jid}} / {{item.received_timestamp}})"><img src="{{STATIC_URL}}img/map.png" width=24px" height="24px" alt="{{item.latitude}},{{item.longitude}}"></a>
+ {% else %}
+ <a target="_new" href="https://maps.google.com/maps?q={{item.latitude}},+{{item.longitude}}+(Me / {{item.received_timestamp}})"><img src="{{STATIC_URL}}img/map.png" width=24px" height="24px" alt="{{item.latitude}},{{item.longitude}}"></a>
+ {% endifequal %}
+ {% endif %}
+
+
+ <a href="{% url contact_profile key=item.key_remote_jid %}">{{item.display_name}}</a>
+
+ {% ifequal item.key_from_me 1 %}
+ &lArr; Me
{% endifequal %}
- <div>{% if item.img|length > 0 %}<img src="{{item.img}}"/>{% else %}{{item.data}}{% endif %}</div>
- </div>
- </div><br>
+ </h6>
+
+ <div>
+ {% if item.img|length > 0 %}
+ <img src="{{item.img}}"/>
+ {% else %}
+ {{item.data|default:"No data available"}}
+ {% endif %}
+ </div>
+ </div>
+ </div><br>
{% endfor %}
@@ -74,4 +73,4 @@
</div>
-{% endblock %}
+{% endblock %}
View
11 wforensic/whatsapp/templates/whatsapp/chatlist.html
@@ -18,8 +18,13 @@
</div>
<div class="ym-gl" align="left">[<u>{{item.timestamp}}</u>]
<h6>
- <img src="{{STATIC_URL}}img/thumb.png" width="24px" height="24px" />
- <a href="{% url single_chat key=item.jid %}">{{item.display_name}}</a>
+ <a href="{% url contact_profile key=item.jid %}">
+ {% if item.display_name|length_is:"0" %}
+ {{ item.jid }}
+ {% else %}
+ {{item.display_name}}
+ {% endif %}
+ </a>
</h6>
<div align="left">
{% if item.img|length > 0 %}<img src="{{item.img}}"/>{% else %}{{item.latest}}{% endif %}
@@ -43,4 +48,4 @@
</div>
-{% endblock %}
+{% endblock %}
View
12 wforensic/whatsapp/templates/whatsapp/contacts.html
@@ -1,14 +1,16 @@
{% extends "base.html" %}
{% block head %}
-{% ifequal contactslist.number 1 %}
+{% ifequal contactslist.number 1 %}
{% include "havewhatsappchart.html" %}
{% endifequal %}
{% endblock %}
{% block body %}
-<div class="ym-g60">
+<a href="{% url download_contacts %}">Download as XML</a>
+</br>
+<div class="ym-g80">
<div class="ym-gbox">
<table class="bordertable" style="background: #e8ff99;">
<thead>
@@ -26,7 +28,7 @@
{% ifequal item.messages 0 %}
{{item.number}}
{% else %}
- <a href="{% url single_chat key=item.jid %}">{{item.number}}</a>
+ <a href="{% url contact_profile key=item.jid %}">{{item.number}}</a>
{% endifequal %}
</td>
<td>{% ifequal item.is_whatsapp_user 1 %}<img src="{{STATIC_URL}}img/thumb.png" width="24px" height="24px" />{% endifequal %} {{item.display_name}}</td>
@@ -50,6 +52,4 @@
</div>
-</div>
-
-{% endblock %}
+{% endblock %}
View
10 wforensic/whatsapp/templates/whatsapp/contacts.xml
@@ -0,0 +1,10 @@
+<!--
+ This file was automatically generated on {{ date }} by WhatsApp Forensic Tool
+ http://sch3m4.github.com/wforensic
+-->
+
+<contacts>
+ {% for item in contacts %}
+ <contact name="{{ item.display_name }}" number="{{ item.number }}" status="{{ item.status }}" whatsapp="{{ item.whatsapp }}" messages="{{ item.messages }}" />
+ {% endfor %}
+</contacts>
View
82 wforensic/whatsapp/templates/whatsapp/index.html
@@ -1,7 +1,12 @@
{% extends "base.html" %}
+{% load gotcontactsdb %}
+
{% block head %}
-{% include "havewhatsappchart.html" %}
+{% gotcontactsdb %}
+{% ifequal gotcontacts 1 %}
+ {% include "havewhatsappchart.html" %}
+{% endifequal %}
{% include "messagesfrom.html" %}
{% include "activitychart.html" %}
{% endblock %}
@@ -11,14 +16,26 @@
<script type="text/javascript" src="{{STATIC_URL}}js/highcharts.js"></script>
<script type="text/javascript" src="{{STATIC_URL}}js/modules/exporting.js"></script>
-<div class="ym-g50">
- <table class="bordertable" style="background: #e8ff99;" >
+<div class="ym-g80">
+ <table>
+ <tr border="0">
+ {% gotcontactsdb %}
+ {% ifequal gotcontacts 1 %}
+ <td><div id="whatsappusers" style="width: 480px; height: 200px; margin: 0 auto"></div></td>
+ {% endifequal %}
+ <td><div id="messagesfrom" style="width: 480px; height: 200px; margin: 0 auto"></div></td>
+ </tr>
+ </table>
+ <div id="wa_activity" style="margin: 0 auto"></div>
+
+ <table class="bordertable" style="background: #e8ff99; ">
<thead>
<tr>
<th> Filename </th>
<th> Size (Bytes) </th>
<th> MD5 </th>
<th> SHA1 </th>
+ <th> Writeable? </th>
</tr>
</thead>
<tbody>
@@ -27,47 +44,56 @@
<td>{{msgsize}}</td>
<td>{{msgmd5}}</td>
<td>{{msgsha1}}</td>
+ {% autoescape off %}
+ <td>{{msgperm}}</td>
+ {% endautoescape %}
</tr>
<tr>
<td>{{wafile}}</td>
<td>{{wasize}}</td>
<td>{{wamd5}}</td>
<td>{{washa1}}</td>
+ {% autoescape off %}
+ <td>{{waperm}}</td>
+ {% endautoescape %}
</tr>
</tbody>
</table>
</div>
<br/>
-<div class="ym-g60">
- <div id="wa_activity" style="margin: 0 auto"></div>
- <table>
- <tr>
- <td><div id="whatsappusers" style="width: 500px; height: 200px; margin: 0 auto"></div></td>
- <td><div id="messagesfrom" style="width: 500px; height: 200px; margin: 0 auto"></div></td>
- </tr>
- </table>
-</div>
-
-
-<div class="ym-g60">
+<div class="ym-g80">
<div style="background: red;">
<h6><span style="color: white;">General Activity</span></h6>
</div>
<div class="ym-gbox">
<form class="ym-form">
<h6><i>Latest Contacted</i></h6>
-
- {% for peer in latest %}
- <div class="ym-gbox" align="left" style="background: #e8ff99;">
- <ul>
- <li>
- <b>{{peer.timestamp}}</b> / <a href="{% url single_chat key=peer.key_remote_jid %}">{{peer.display_name}}</a> : {% if peer.img|length > 0 %}<img src="{{peer.img}}"/>{% else %}{{peer.data}}{% endif %}
- </li>
- </ul>
- </div>
- {% endfor %}
+ <div class="ym-gbox" align="left">
+ <table class="bordertable" style="background: #e8ff99;">
+ <thead align="center">
+ <tr>
+ <th> Date </th>
+ <th> Display Name </th>
+ <th> Phone </th>
+ <th> Status </th>
+ <th> Message </th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for peer in latest %}
+ <tr>
+ <td>{{peer.timestamp}}</td>
+ <td>{{peer.display_name}}</td>
+ <td><a href="{% url contact_profile key=peer.key_remote_jid %}">{{peer.number}}</a></td>
+ <td>{{peer.status}}</td>
+ <td>{% if peer.img|length > 0 %}<img src="{{peer.img}}"/>{% else %}{{peer.data}}{% endif %}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
<h6><i>Top Contacted</i></h6>
<div class="ym-gbox" align="left">
@@ -83,8 +109,8 @@
<tbody>
{% for peer in toppeers %}
<tr>
- <td>{{peer.number}}</td>
- <td><a href="{% url single_chat key=peer.jid %}">{{peer.display_name}}</a></td>
+ <td><a href="{% url contact_profile key=peer.jid %}">{{peer.number}}</a></td>
+ <td>{{peer.display_name}}</td>
<td>{{peer.status}}</td>
<td>{{peer.msgs}}</td>
</tr>
@@ -96,4 +122,4 @@
<div>
</div>
-{% endblock %}
+{% endblock %}
View
49 wforensic/whatsapp/templates/whatsapp/profile.html
@@ -0,0 +1,49 @@
+{% extends "base.html" %}
+
+{% block head %}
+ {% include "activitychart.html" %}
+{% endblock %}
+
+{% block body %}
+
+<script type="text/javascript" src="{{STATIC_URL}}js/highcharts.js"></script>
+<script type="text/javascript" src="{{STATIC_URL}}js/modules/exporting.js"></script>
+
+<div class="ym-g60">
+ <div id="wa_activity" style="margin: 0 auto"></div>
+</div>
+
+<div class="ym-g40">
+ <div style="background: red;">
+ <h6><span style="color: white;">Contact Profile</span></h6>
+ </div>
+
+ <div class="ym-gbox">
+ <div class="ym-gbox" align="left">
+ <ul>
+ <li><b>Name:</b> {{ contact.name }}</li>
+ <li><b>Status:</b> {{ contact.status }}</li>
+ <li><b>Number:</b> {{ contact.number }}</li>
+ <li><b>Is WhatsApp user:</b>
+ {% ifequal contact.whatsapp 1 %}
+ Yes
+ {% else %}
+ No
+ {% endifequal %}
+ </li>
+ <li><b>Messages:</b> {{ contact.messages }}</li>
+ <li><b>First Seen:</b> {{ contact.first_seen }}</li>
+ <li><b>Last seen:</b> {{ contact.last_seen }}</li>
+ {% if contact.media_messages > 0 %}
+ <li><b>Media messages:</b> {{ contact.media_messages }}</li>
+ {% endif %}
+ {% if contact.gps_messages > 0 %}
+ <li><b>GPS Messages:</b> {{ contact.gps_messages }}</li>
+ {% endif %}
+ </ul>
+ </br>
+ </div>
+ <a href="{% url single_chat key=contact.jid %}">View conversation</a>
+ </div>
+</div>
+{% endblock %}
View
23 wforensic/whatsapp/templates/whatsapp/search.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block body %}
+
+<div class="ym-g80">
+<form class="ym-form" action="" method="post">
+ {% csrf_token %}
+ {# Include the hidden fields #}
+ {% for hidden in form.hidden_fields %}
+ {{ hidden }}
+ {% endfor %}
+ {# Include the visible fields #}
+ {% for field in form.visible_fields %}
+ <div class="fieldWrapper">
+ {{ field.errors }}
+ {{ field.label_tag }}: {{ field }}
+ </div>
+ {% endfor %}
+ <p><input type="submit" value="Search" /></p>
+</form>
+</div>
+
+{% endblock %}
View
20 ...app/templatetags/gettemplateheader_tag.py → ...hatsapp/templatetags/gettemplateheader.py
@@ -1,32 +1,34 @@
from django import template
from django.db.models import Q
-from wforensic.whatsapp.models import WaContacts,ChatList,Messages
+from wforensic.whatsapp.models import WaContacts, ChatList, Messages
register = template.Library()
+
class LoadHeader(template.Node):
- def __init__(self,varname):
+ def __init__(self, varname):
self.varname = varname
-
- def render(self,context):
+
+ def render(self, context):
try:
contacts = WaContacts.objects.count()
except:
contacts = Messages.objects.using('msgstore').values('key_remote_jid').distinct().count()
-
+
ctx = {
'contacts': contacts,
'messages': Messages.objects.using('msgstore').count(),
'chats': ChatList.objects.using('msgstore').count(),
- 'gps': Messages.objects.using('msgstore').exclude((Q(longitude = '0.0') | Q(latitude = '0.0'))).count(),
- 'media': Messages.objects.using('msgstore').exclude(media_url__isnull = True).count()
+ 'gps': Messages.objects.using('msgstore').exclude((Q(longitude='0.0') | Q(latitude='0.0'))).count(),
+ 'media': Messages.objects.using('msgstore').exclude(media_url__isnull=True).count()
}
-
+
context[self.varname] = ctx
return ''
+
@register.tag
-def gettemplateheader(parser,token):
+def gettemplateheader(parser, token):
_var = 'theader'
return LoadHeader(_var)
View
23 wforensic/whatsapp/templatetags/gotcontactsdb.py
@@ -0,0 +1,23 @@
+from django import template
+from django.db import connection
+
+register = template.Library()
+
+
+class GotContactsDB(template.Node):
+
+ def __init__(self, varname):
+ self.varname = varname
+
+ def render(self, context):
+ if not 'wa_contacts' in connection.introspection.table_names():
+ context[self.varname] = 0
+ else:
+ context[self.varname] = 1
+
+ return ''
+
+
+@register.tag
+def gotcontactsdb(parser, token):
+ return GotContactsDB('gotcontacts')
View
340 wforensic/whatsapp/utils.py
@@ -1,241 +1,353 @@
try:
+ import sys
+ import string
import base64
import hashlib
- import sys
from operator import itemgetter
- from os.path import basename,isfile
+ from os.path import basename, isfile
from datetime import datetime
from django.db import models
from django.db.models import Q
- from models import WaContacts,ChatList,Messages
- from wforensic.settings import LATEST_PEERS,TOP_PEERS,THUMBS_ROOT,THUMBS_URL
-except ImportError,e:
+ from django.db import connection
+ from models import WaContacts, ChatList, Messages
+ from wforensic.settings import LATEST_PEERS, TOP_PEERS, THUMBS_ROOT, THUMBS_URL
+except ImportError, e:
print "[f] Required module missing. %s" % e.args[0]
sys.exit(-1)
-
+
+
def timestamp2utc(timestamp):
- return datetime.utcfromtimestamp(timestamp).strftime ("%Y/%m/%d %H:%M:%S")
+ return datetime.utcfromtimestamp(timestamp).strftime("%Y/%m/%d %H:%M:%S")
+
+
+def set_media(wa_type, data, jid, idmsg, raw = False):
+ if jid[:1] == '+':
+ jid = jid[1:]
+ if wa_type != '0':
+ path = THUMBS_ROOT + jid.replace('@','.') + '.' + str(idmsg) + '.png'
+ if not isfile(path):
+ try:
+ if raw is False:
+ content = base64.b64decode(data)
+ else:
+ content = data
+ except:
+ return ''
+ fout = open(path, "w")
+ fout.write(content)
+ fout.close()
+
+ return THUMBS_URL + basename(path)
+ else:
+ return ''
-def set_media(wa_type,data,idmsg):
- if wa_type != '0':
- path = THUMBS_ROOT + idmsg + '.png'
- if isfile(path):
- THUMBS_URL + basename(path)
- else:
- content = base64.b64decode(data)
- fout = open(path, "w")
- fout.write(content)
- fout.close()
- return THUMBS_URL + basename(path)
- else:
- return ''
def get_md5_file(path):
md5 = hashlib.md5()
try:
- with open(path,'rb') as f:
+ with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(8192), ''):
md5.update(chunk)
except:
return 'Cannot access file'
return md5.hexdigest()
+
def get_sha1_file(path):
md5 = hashlib.sha1()
try:
- with open(path,'rb') as f:
+ with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(8192), ''):
md5.update(chunk)
except:
return 'Cannot access file'
return md5.hexdigest()
-def get_latest_peers():
- peers = [c['key_remote_jid'] for c in Messages.objects.using('msgstore').values('key_remote_jid').exclude(Q(key_remote_jid=-1)).annotate(models.Max('timestamp')).order_by('-timestamp__max')[:LATEST_PEERS]]
+
+def get_latest_peers(latest=LATEST_PEERS):
+ if latest > 0:
+ peers = [c['key_remote_jid'] for c in Messages.objects.using('msgstore').values('key_remote_jid').exclude(Q(key_remote_jid=-1) | Q(key_remote_jid__icontains='-') | Q(key_remote_jid__startswith='Server')).annotate(models.Max('timestamp')).order_by('-timestamp__max')[:latest]]
+ else:
+ peers = [c['key_remote_jid'] for c in Messages.objects.using('msgstore').values('key_remote_jid').exclude(Q(key_remote_jid=-1) | Q(key_remote_jid__icontains='-') | Q(key_remote_jid__startswith='Server')).annotate(models.Max('timestamp')).order_by('-timestamp__max')]
+
ret = []
-
+
for peer in peers:
- data = Messages.objects.using('msgstore').filter(key_remote_jid = peer).values('data','_id','media_wa_type').annotate(models.Max('timestamp')).order_by('-timestamp__max')[:1][0]
-
+ data = Messages.objects.using('msgstore').filter(key_remote_jid=peer).values('data', '_id', 'media_wa_type','raw_data').annotate(models.Max('timestamp')).order_by('-timestamp__max')[:1][0]
+
try:
- peer_data = WaContacts.objects.filter(jid=peer).values('display_name')[0]
+ peer_data = WaContacts.objects.filter(jid=peer).values('display_name', 'status')[0]
except:
peer_data = {}
- peer_data['display_name'] = peer
-
- display_name = peer_data['display_name']
-
+ peer_data['display_name'] = 'N/A'
+ peer_data['status'] = 'N/A'
+
newdata = {'key_remote_jid': peer,
'media_wa_type': data['media_wa_type'],
'_id': data['_id'],
- 'timestamp': timestamp2utc(float(data['timestamp__max'])/1000),
- 'img': set_media(data['media_wa_type'],data['data'],str(data['_id'])),
+ 'timestamp': timestamp2utc(float(data['timestamp__max']) / 1000),
'data': data['data'],
- 'display_name': display_name,
+ 'display_name': peer_data['display_name'],
+ 'status': peer_data['status'],
+ 'number': '+' + peer.split('@')[0]
}
- ret.append(newdata)
+ if data['data'] is not None:
+ newdata['img'] = set_media(data['media_wa_type'], data['data'], peer , str(data['_id']), False)
+ elif data['raw_data'] is not None:
+ newdata['img'] = set_media(data['media_wa_type'], data['raw_data'], peer, str(data['_id']), True)
- return ret
+ if newdata['data'] is None:
+ newdata['data'] = ''
+
+ ret.append(newdata)
-def get_top_peers():
- _tmp = Messages.objects.using('msgstore').values('key_remote_jid').exclude((Q(key_remote_jid = -1) | Q(key_remote_jid__startswith="Server"))).annotate(models.Count('key_remote_jid')).order_by('-key_remote_jid__count')[:TOP_PEERS]
+ return ret
+
+
+def get_top_peers(top=TOP_PEERS):
+ if top > 0:
+ _tmp = Messages.objects.using('msgstore').values('key_remote_jid').exclude((Q(key_remote_jid=-1) | Q(key_remote_jid__icontains='-') | Q(key_remote_jid__startswith="Server"))).annotate(models.Count('key_remote_jid')).order_by('-key_remote_jid__count')[:top]
+ else:
+ _tmp = Messages.objects.using('msgstore').values('key_remote_jid').exclude((Q(key_remote_jid=-1) | Q(key_remote_jid__icontains='-') | Q(key_remote_jid__startswith="Server"))).annotate(models.Count('key_remote_jid')).order_by('-key_remote_jid__count')
ret = []
for item in _tmp:
-
+
try:
- _aux = WaContacts.objects.filter(jid = item['key_remote_jid']).values('number','display_name','status','jid')[0]
+ _aux = WaContacts.objects.filter(jid=item['key_remote_jid']).values('number', 'display_name', 'status', 'jid')[0]
except:
_aux = {}
- _aux['number'] = item['key_remote_jid']
- _aux['display_name'] = "Not in contacts"
- _aux['status'] = "Not in contacts"
+ _aux['number'] = '+' + item['key_remote_jid'].split('@')[0]
+ _aux['display_name'] = "N/A"
+ _aux['status'] = "N/A"
_aux['jid'] = item['key_remote_jid']
-
-
+
_aux['msgs'] = item['key_remote_jid__count']
ret.append(_aux)
return ret
+
+def get_contacts_xml():
+
+ ret = []
+
+ # wa.db not available
+ if not 'wa_contacts' in connection.introspection.table_names():
+ _aux = Messages.objects.using('msgstore').values('key_remote_jid').exclude((Q(key_remote_jid=-1) | Q(key_remote_jid__icontains='-') | Q(key_remote_jid__startswith="Server"))).annotate(models.Count('key_remote_jid'))
+ for item in _aux:
+ item['number'] = item['key_remote_jid'].split('@')[0][2:]
+ item['display_name'] = 'N/A'
+ item['messages'] = item['key_remote_jid__count']
+ item['whatsapp'] = "Yes"
+ ret.append(item)
+ else:
+ _aux = WaContacts.objects.values('number', 'display_name', 'status', 'jid', 'is_whatsapp_user')
+ for item in _aux:
+ if item['status'] is None:
+ continue
+ item['messages'] = Messages.objects.using('msgstore').filter(key_remote_jid=item['jid']).count()
+ if item['is_whatsapp_user']:
+ item['whatsapp'] = 'Yes'
+ else:
+ item['whatsapp'] = 'No'
+ ret.append(item)
+
+ return ret
+
+
def get_contacts_list():
-
- try:
- _list = WaContacts.objects.exclude(number__isnull = True).values('jid','number','display_name','status','is_whatsapp_user').order_by('display_name')
- except:
- _list = Messages.objects.using('msgstore').values('key_remote_jid').distinct()
+
+ if not 'wa_contacts' in connection.introspection.table_names():
+ _list = Messages.objects.using('msgstore').exclude(Q(key_remote_jid=-1) | Q(key_remote_jid__startswith="Server") | Q(key_remote_jid__icontains='-')).values('key_remote_jid').distinct()
+ _non = 1
+ else:
+ _list = WaContacts.objects.exclude(Q(number__isnull=True) | Q(status__isnull=True)).values('jid', 'number', 'display_name', 'status', 'is_whatsapp_user').order_by('display_name')
+ _non = 0
_ret = []
for item in _list:
+ if _non == 1:
+ item['is_whatsapp_user'] = 1
+ item['display_name'] = 'N/A'
+ item['status'] = 'N/A'
+ item['number'] = '+' + item['key_remote_jid'].split('@')[0]
+ item['jid'] = item['key_remote_jid']
+
try:
- item['messages'] = Messages.objects.using('msgstore').filter(key_remote_jid = item['jid']).count()
+ item['messages'] = Messages.objects.using('msgstore').filter(key_remote_jid=item['jid']).count()
except:
item['messages'] = 0
+
_ret.append(item)
-
+
return _ret
+
def get_chat_list():
_tmp = ChatList.objects.using('msgstore').values('key_remote_jid')
_aux = [d['key_remote_jid'] for d in _tmp]
_ret = []
for item in _aux:
- _name = WaContacts.objects.filter(jid = item).values('display_name','number')
- _count = Messages.objects.using('msgstore').filter(key_remote_jid = item).count()
+ if not 'wa_contacts' in connection.introspection.table_names():
+ num = item.split('@')[0]
+ _name = {'display_name': '', 'number': '+' + num if item[:1] in string.digits else num }
+ else:
+ _name = WaContacts.objects.filter(jid=item).values('display_name', 'number')[0]
- _tmp = Messages.objects.using('msgstore').filter(key_remote_jid = item).values('data','media_wa_type','_id').annotate(models.Max('timestamp')).order_by('-timestamp__max')
+ _count = Messages.objects.using('msgstore').filter(key_remote_jid=item).count()
try:
- _latest = _tmp[0]['data']
+ _tmp = Messages.objects.using('msgstore').filter(key_remote_jid=item).values('data', 'raw_data' 'media_wa_type', '_id').annotate(models.Max('timestamp')).order_by('-timestamp__max')[0]
except:
- _latest=''
-
+ continue
+
try:
- _tstamp = timestamp2utc(float(_tmp[0]['timestamp__max'])/1000)
+ _latest = _tmp['data']
+ if _latest is None:
+ _latest = ''
except:
- _tstamp='Unknown'
+ _latest = ''
try:
- _ret.append({'jid': item ,'display_name': _name[0]['display_name'],'number':_name[0]['number'],'count': _count, 'latest': _latest,'timestamp': _tstamp , 'img': set_media(_tmp[0]['media_wa_type'],_tmp[0]['data'],str(_tmp[0]['_id']))})
- except:
- _ret.append({'jid': item ,'display_name': 'NOT IN CONTACTS','number': '+' + item.split('@')[0],'count': _count, 'latest': _latest,'timestamp': _tstamp , 'img': ''})
+ _tstamp = timestamp2utc(float(_tmp['timestamp__max']) / 1000)
+ except Exception,e:
+ print "ERROR: %s" % str(e)
+ _tstamp = 'Unknown'
+
+ set_media(_tmp['media_wa_type'], _tmp['data'], str(_tmp['_id']))
+
+ toadd = {'jid': item, 'display_name': _name['display_name'], 'number': _name['number'], 'count': _count, 'latest': _latest, 'timestamp': _tstamp}
+
+ if _tmp['data'] is not None:
+ toadd['img'] = set_media(_tmp['media_wa_type'], _tmp['data'], item , str(_tmp['_id']), False)
+ elif _tmp['raw_data'] is not None:
+ toadd['img'] = set_media(_tmp['media_wa_type'], _tmp['raw_data'], item, str(_tmp['_id']), True)
+
+ _ret.append(toadd)
return _ret
+
def get_chat_messages(jid = None):
-
+
if jid is not None:
- _msgs = Messages.objects.using('msgstore').filter(key_remote_jid = jid).values('media_wa_type','_id','key_remote_jid','key_from_me','data','timestamp','received_timestamp','media_url','latitude','longitude').order_by('-timestamp')
+ _msgs = Messages.objects.using('msgstore').filter(key_remote_jid=jid).values('media_wa_type', '_id', 'key_remote_jid', 'key_from_me', 'data', 'timestamp', 'received_timestamp', 'media_url', 'latitude', 'longitude', 'raw_data').order_by('-received_timestamp')
else:
- _msgs = Messages.objects.using('msgstore').exclude(key_remote_jid = -1).values('media_wa_type','_id','key_remote_jid','key_from_me','data','timestamp','received_timestamp','media_url','latitude','longitude').order_by('-timestamp')
-
+ _msgs = Messages.objects.using('msgstore').exclude(key_remote_jid=-1).values('media_wa_type', '_id', 'key_remote_jid', 'key_from_me', 'data', 'timestamp', 'received_timestamp', 'media_url', 'latitude', 'longitude', 'raw_data').order_by('-received_timestamp')
+
_aux = []
for item in _msgs:
- try:
- _peer = WaContacts.objects.filter(jid = item['key_remote_jid']).values('display_name')[0]
- except:
+ if not 'wa_contacts' in connection.introspection.table_names():
_peer = {}
- _peer['display_name'] = jid
-
+ _peer['display_name'] = '+' + item['key_remote_jid'].split('@')[0]
+ else:
+ _peer = WaContacts.objects.filter(jid=item['key_remote_jid']).values('display_name')[0]
+
item['display_name'] = _peer['display_name']
- item['timestamp'] = timestamp2utc(float(item['timestamp'])/1000)
- item['received_timestamp'] = timestamp2utc(float(item['received_timestamp'])/1000)
- item['img'] = set_media(item['media_wa_type'],item['data'],str(item['_id']))
+ item['timestamp'] = timestamp2utc(float(item['timestamp']) / 1000)
+ item['received_timestamp'] = timestamp2utc(float(item['received_timestamp']) / 1000)
+
+ if item['data'] is not None:
+ item['img'] = set_media(item['media_wa_type'], item['data'], item['key_remote_jid'], str(item['_id']), False)
+ elif item['raw_data'] is not None:
+ item['img'] = set_media(item['media_wa_type'], item['raw_data'], item['key_remote_jid'], str(item['_id']), True)
+
+
_aux.append(item)
-
+
return _aux
+
def get_messages_media():
-
- _msgs = Messages.objects.using('msgstore').exclude((Q(key_remote_jid = -1) | Q(media_url__isnull = True))).values('media_wa_type','_id','key_remote_jid','key_from_me','data','timestamp','received_timestamp','media_url','latitude','longitude').order_by('-timestamp')
+
+ _msgs = Messages.objects.using('msgstore').exclude((Q(key_remote_jid=-1) | Q(media_url__isnull=True))).values('media_wa_type', '_id', 'key_remote_jid', 'key_from_me', 'data', 'raw_data', 'timestamp', 'received_timestamp', 'media_url', 'latitude', 'longitude').order_by('-timestamp')
_aux = []
for item in _msgs:
- _peer = WaContacts.objects.filter(jid = item['key_remote_jid']).values('display_name')[0]
+ if not 'wa_contacts' in connection.introspection.table_names():
+ _peer = {}
+ _peer['display_name'] = '+' + item['key_remote_jid'].split('@')[0]
+ else:
+ _peer = WaContacts.objects.filter(jid=item['key_remote_jid']).values('display_name')[0]
+
item['display_name'] = _peer['display_name']
- item['timestamp'] = timestamp2utc(float(item['timestamp'])/1000)
- item['received_timestamp'] = timestamp2utc(float(item['received_timestamp'])/1000)
- item['img'] = set_media(item['media_wa_type'],item['data'],str(item['_id']))
+ item['timestamp'] = timestamp2utc(float(item['timestamp']) / 1000)
+ item['received_timestamp'] = timestamp2utc(float(item['received_timestamp']) / 1000)
+ if item['data'] is not None:
+ item['img'] = set_media(item['media_wa_type'], item['data'], item['key_remote_jid'], str(item['_id']), False)
+ elif item['raw_data'] is not None:
+ item['img'] = set_media(item['media_wa_type'], item['raw_data'], item['key_remote_jid'], str(item['_id']), True)
+
_aux.append(item)
-
+
return _aux
+
def get_messages_gps():
-
- _msgs = Messages.objects.using('msgstore').exclude((Q(key_remote_jid = -1) | Q(longitude = '0.0') | Q(latitude = '0.0'))).values('media_wa_type','_id','key_remote_jid','key_from_me','data','timestamp','received_timestamp','media_url','latitude','longitude').order_by('-timestamp')
+
+ _msgs = Messages.objects.using('msgstore').exclude((Q(key_remote_jid=-1) | Q(longitude='0.0') | Q(latitude='0.0'))).values('media_wa_type', '_id', 'key_remote_jid', 'key_from_me', 'data', 'raw_data', 'timestamp', 'received_timestamp', 'media_url', 'latitude', 'longitude').order_by('-timestamp')
_aux = []
for item in _msgs:
- _peer = WaContacts.objects.filter(jid = item['key_remote_jid']).values('display_name')[0]
+ if not 'wa_contacts' in connection.introspection.table_names():
+ _peer = {}
+ _peer['display_name'] = '+' + item['key_remote_jid'].split('@')[0]
+ else:
+ _peer = WaContacts.objects.filter(jid=item['key_remote_jid']).values('display_name')[0]
+
item['display_name'] = _peer['display_name']
- item['timestamp'] = timestamp2utc(float(item['timestamp'])/1000)
- item['received_timestamp'] = timestamp2utc(float(item['received_timestamp'])/1000)
- item['img'] = set_media(item['media_wa_type'],item['data'],str(item['_id']))
+ item['timestamp'] = timestamp2utc(float(item['timestamp']) / 1000)
+ item['received_timestamp'] = timestamp2utc(float(item['received_timestamp']) / 1000)
+
+ if item['data'] is not None:
+ item['img'] = set_media(item['media_wa_type'], item['data'], item['key_remote_jid'], str(item['_id']), False)
+ else:
+ item['img'] = set_media(item['media_wa_type'], item['raw_data'], item['key_remote_jid'], str(item['_id']), True)
+
_aux.append(item)
-
+
return _aux
+
def get_activity_data(key=None):
-
+
if key is None:
- peers = ChatList.objects.using('msgstore').values('key_remote_jid').exclude(Q(key_remote_jid = -1))
+ peers = ChatList.objects.using('msgstore').values('key_remote_jid').exclude(Q(key_remote_jid=-1))
else:
peers = [{'key_remote_jid': key}]