diff --git a/README.md b/README.md index 5e6acfc..27f2d13 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,20 @@ + python-csscompressor(optional) #### Get Necessary Data: -+ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from root filesystem. -+ Get WeChat user resource directory from user filesystem, for example: storage/tencent/MicroMsg/long-long-name. -+ Get Wechat uin: - + login to [web-based wechat](https://wx.qq.com); get wxuin=1234567 from `document.cookie` - + Or get ``default_uin`` from /data/data/com.tencent.mm/shared_prefs/system_config_prefs.xml. -+ Get phone IMEI: ++ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from root filesystem, possible ways are: + + `adb root`, `adb pull /xxx/xxx.db` + + Use your rooted file system manager app ++ Get WeChat user resource directory from user filesystem, for example: sdcard:/tencent/MicroMsg/long-long-name. (could be different) ++ Get Wechat uin, possible ways are: + + Login to [web-based wechat](https://wx.qq.com); get wxuin=1234567 from `document.cookie` + + Get ``default_uin`` from /data/data/com.tencent.mm/shared_prefs/system_config_prefs.xml. ++ Get phone IMEI, possible ways are: + Call `*#06#` on your phone - + Or find IMEI in system settings - + Or use `adb shell dumpsys iphonesubinfo | grep 'Device ID' | grep -o '[0-9]*'` + + Find IMEI in system settings + + `adb shell dumpsys iphonesubinfo | grep 'Device ID' | grep -o '[0-9]*'` #### Run: -+ Decrypt database and get decrypted_db.db (for now, Linux x64 only): ++ Decrypt database, will produce decrypted_db.db (for now, Linux x64 only): ``` ./decrypt_db.sh ``` @@ -29,12 +31,12 @@ ``` ./dump_msg.py decrypted_db.db output_dir ``` -+ Dump messages of one contact to single-file html, containing voice messages and image thumbnail: ++ Dump messages of one contact to single-file html, containing voice messages and images: ``` ./dump_html.py decrypted_db.db output.html ``` ### TODO -+ parse links, custom emoji, and other message types -+ show name of unicode emoji in txt -+ given uid/username.. ++ Parse custom emoji, and other message types ++ Show name of emoji in text ++ Search by uid/username.. diff --git a/lib/emoji.py b/lib/emoji.py index 0ee2e4d..60e7e69 100755 --- a/lib/emoji.py +++ b/lib/emoji.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: emoji.py -# Date: Tue Dec 16 23:52:13 2014 +0800 +# Date: Sat Dec 20 15:08:45 2014 +0800 # Author: Yuxin Wu import os diff --git a/lib/msg.py b/lib/msg.py index 75a0835..c480ef1 100644 --- a/lib/msg.py +++ b/lib/msg.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: msg.py -# Date: Sun Nov 23 20:44:33 2014 +0800 +# Date: Sat Dec 20 15:12:34 2014 +0800 # Author: Yuxin Wu from datetime import datetime @@ -11,10 +11,11 @@ from .utils import ensure_bin_str, ensure_unicode TYPE_MSG = 1 TYPE_IMG = 3 TYPE_SPEAK = 34 +TYPE_NAMECARD = 42 TYPE_VIDEO = 43 TYPE_EMOJI = 47 TYPE_LOCATION = 48 -TYPE_LINK = 49 # link share or file from web +TYPE_LINK = 49 # link share OR file from web TYPE_VOIP = 50 TYPE_SYSTEM = 10000 @@ -36,6 +37,8 @@ class WeChatMsg(object): self.talker_name = None if self.content: self.content = ensure_unicode(self.content) + else: + self.content = u"" def msg_str(self): # TODO: fix more types @@ -58,11 +61,22 @@ class WeChatMsg(object): url = soup.find('url').text if not url: title = soup.find('title').text - assert title, "No title or url found in TYPE_LINK" + if not title: + print self.content + from IPython import embed; embed() + assert title, \ + u"No title or url found in TYPE_LINK: {}".format(self.content) return u"FILE:{}".format(title) return u"URL:{}".format(url) elif self.type == TYPE_VIDEO: return "VIDEO" + elif self.type == TYPE_NAMECARD: + soup = BeautifulSoup(self.content) + msg = soup.find('msg').attrs + name = msg.get('nickname', '') + if not name: + name = msg.get('alias', '') + return u"NAMECARD: {}".format(name) else: return self.content diff --git a/lib/parser.py b/lib/parser.py index 3c883ce..ab5862c 100644 --- a/lib/parser.py +++ b/lib/parser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: parser.py -# Date: Fri Dec 12 22:35:56 2014 +0800 +# Date: Wed Dec 17 23:04:54 2014 +0800 # Author: Yuxin Wu import sqlite3 @@ -43,7 +43,7 @@ SELECT username,conRemark,nickname FROM rcontact else: self.contacts[username] = ensure_unicode(nickname) - logger.info("Got {} contacts.".format(len(self.contacts))) + logger.info("Found {} contacts.".format(len(self.contacts))) def _parse_msg(self): msgs_tot_cnt = 0 @@ -62,7 +62,7 @@ SELECT {} FROM message for msg in v: msg.talker_name = ensure_unicode(k) msgs_tot_cnt += len(v) - logger.info("Got {} messages in total.".format(msgs_tot_cnt)) + logger.info("Found {} message records.".format(msgs_tot_cnt)) def _parse_userinfo(self): userinfo_q = self.cc.execute(""" SELECT id, value FROM userinfo """) @@ -74,7 +74,7 @@ SELECT {} FROM message imginfo_q = self.cc.execute("""SELECT msgSvrId, bigImgPath FROM ImgInfo2""") self.imginfo = dict([(k, v) for (k, v) in imginfo_q if not v.startswith('SERVERID://')]) - logger.info("Got {} big images.".format(len(self.imginfo))) + logger.info("Found {} big images records.".format(len(self.imginfo))) def _find_msg_by_type(self, msgs=None): ret = [] diff --git a/lib/render.py b/lib/render.py index 9b1e9d2..564dc26 100755 --- a/lib/render.py +++ b/lib/render.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: render.py -# Date: Wed Dec 17 00:04:02 2014 +0800 +# Date: Sat Dec 20 15:40:19 2014 +0800 # Author: Yuxin Wu import os @@ -41,7 +41,7 @@ class HTMLRender(object): csss = glob.glob(os.path.join(LIB_PATH, 'static/*.css')) css_string = [] for css in csss: - logger.info("Load {}".format(os.path.basename(css))) + logger.info("Loading {}.".format(os.path.basename(css))) css = ensure_unicode(css_compress(open(css).read())) css = u''.format(css) css_string.append(css) @@ -50,7 +50,7 @@ class HTMLRender(object): jss = glob.glob(os.path.join(LIB_PATH, 'static/*.js')) js_string = [] for js in jss: - logger.info("Load {}".format(os.path.basename(js))) + logger.info("Loading {}".format(os.path.basename(js))) js = ensure_unicode(open(js).read()) # TODO: add js compress js = u''.format(js) @@ -68,39 +68,39 @@ class HTMLRender(object): def render_msg(self, msg): """ render a message, return the html block""" sender = 'you' if not msg.isSend else 'me' - # TODO - try: - if msg.type == TYPE_VIDEO: - # send a video file - raise - template = ensure_unicode(TEMPLATES[msg.type]) - if msg.type == TYPE_SPEAK: - audio_str, duration = self.res.get_voice_mp3(msg.imgPath) - return template.format(sender_label=sender, - voice_duration=duration, - voice_str=audio_str) - elif msg.type == TYPE_IMG: - # imgPath was original THUMBNAIL_DIRPATH://th_xxxxxxxxx - imgpath = msg.imgPath.split('_')[-1] - bigimgpath = self.parser.imginfo.get(msg.msgSvrId) - - bigimg, smallimg = self.res.get_img([imgpath, bigimgpath]) - return template.format(sender_label=sender, - small_img=smallimg, - big_img=bigimg) - else: - raise - except: + def fallback(): template = ensure_unicode(TEMPLATES[1]) content = msg.msg_str() content = self.emoji.replace_emojicode(content) return template.format(sender_label=sender, content=content) + if msg.type not in TEMPLATES: + return fallback() + + template = ensure_unicode(TEMPLATES[msg.type]) + if msg.type == TYPE_SPEAK: + audio_str, duration = self.res.get_voice_mp3(msg.imgPath) + return template.format(sender_label=sender, + voice_duration=duration, + voice_str=audio_str) + elif msg.type == TYPE_IMG: + # imgPath was original THUMBNAIL_DIRPATH://th_xxxxxxxxx + imgpath = msg.imgPath.split('_')[-1] + bigimgpath = self.parser.imginfo.get(msg.msgSvrId) + fnames = [k for k in [imgpath, bigimgpath] if k is not None] + bigimg, smallimg = self.res.get_img(fnames) + assert smallimg + # TODO do not show fancybox when no bigimg found + return template.format(sender_label=sender, + small_img=smallimg, + big_img=bigimg) + return fallback() def render_msgs(self, msgs): """ render msgs of the same friend""" talker_name = msgs[0].talker - logger.info(u"Rendering messages of {}".format(talker_name)) + logger.info(u"Rendering {} messages of {}({})".format( + len(msgs), self.parser.contacts[talker_name], talker_name)) avatars = self.get_avatar_pair(talker_name) blocks = [self.render_msg(m) for m in msgs] diff --git a/lib/res.py b/lib/res.py index ac6f8e0..601609d 100644 --- a/lib/res.py +++ b/lib/res.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: res.py -# Date: Wed Dec 17 00:02:13 2014 +0800 +# Date: Sat Dec 20 15:40:33 2014 +0800 # Author: Yuxin Wu import glob @@ -110,17 +110,19 @@ class Resource(object): else: logger.warn("Found big image but not thumbnail: {}".format(fname)) return (name, "") - big, small = cands[-1], cands[0] - if not name_is_thumbnail(small[0]): + big = cands[-1] + ths = filter(name_is_thumbnail, [k[0] for k in cands]) + if not ths: return (big[0], "") - return (big[0], small[0]) + return (big[0], ths[0]) + def get_img(self, fnames): """ return two base64 jpg string""" big_file, small_file = self.get_img_file(fnames) def get_jpg_b64(img_file): - if not big_file: + if not img_file: return None if not img_file.endswith('jpg'): # possibly not jpg diff --git a/lib/static/wx.css b/lib/static/wx.css index bc9f30f..e751bea 100644 --- a/lib/static/wx.css +++ b/lib/static/wx.css @@ -1361,8 +1361,7 @@ a.btnSecondary:active { background-color: #EFF3F7; } .chatPanel .chatContent { - padding: 10px; - width: 100%; + padding-left: 10px; overflow: hidden; position: relative; }