image thumbnail
This commit is contained in:
@@ -3,14 +3,15 @@
|
||||
### How to use:
|
||||
|
||||
#### Install Dependencies:
|
||||
+ numpy
|
||||
+ PIL
|
||||
+ audioread
|
||||
+ python-numpy
|
||||
+ python-PIL
|
||||
+ [eyed3](http://eyed3.nicfit.net/)
|
||||
+ sox
|
||||
+ python-csscompressor(optional)
|
||||
|
||||
#### Get Necessary Data:
|
||||
+ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from rooted phone:
|
||||
+ Get Wechat resource directory, usually at storage:/tencent/MicroMsg/long-long-name
|
||||
+ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from root filesystem .
|
||||
+ Get Wechat resource directory from user filesystem, usually at 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.
|
||||
@@ -28,11 +29,11 @@
|
||||
```
|
||||
./dump_msg.py decrypted_db.db output_dir
|
||||
```
|
||||
+ Dump messages of one contact to html (for now, raw message only):
|
||||
+ Dump messages of one contact to an html including voice messages and image thumbnail:
|
||||
```
|
||||
./dump_html.py decrypted_db.db <resource directory> <contact name> output.html
|
||||
```
|
||||
|
||||
### TODO
|
||||
+ parse audio messages, links, emoji and images
|
||||
+ output to rich-content html
|
||||
+ parse links, full images, and emoji
|
||||
+ Fix UI
|
||||
|
||||
+3
-5
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: UTF-8 -*-
|
||||
# File: msg.py
|
||||
# Date: Sat Nov 22 23:23:52 2014 +0800
|
||||
# Date: Sun Nov 23 20:44:33 2014 +0800
|
||||
# Author: Yuxin Wu <[email protected]>
|
||||
|
||||
from datetime import datetime
|
||||
@@ -19,13 +19,11 @@ TYPE_VOIP = 50
|
||||
TYPE_SYSTEM = 10000
|
||||
|
||||
class WeChatMsg(object):
|
||||
""" fields in concern"""
|
||||
FIELDS = ["msgSvrId","type","isSend","createTime","talker","content","imgPath"]
|
||||
FILTER_TYPES = [TYPE_SYSTEM]
|
||||
|
||||
@staticmethod
|
||||
def filter_types(tp):
|
||||
if tp in WeChatMsg.FILTER_TYPES or tp > 10000 or tp < 0:
|
||||
def filter_type(tp):
|
||||
if tp in [TYPE_SYSTEM] or tp > 10000 or tp < 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
+8
-2
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: UTF-8 -*-
|
||||
# File: parser.py
|
||||
# Date: Sun Nov 23 16:33:20 2014 +0800
|
||||
# Date: Sun Nov 23 20:44:02 2014 +0800
|
||||
# Author: Yuxin Wu <[email protected]>
|
||||
|
||||
import sqlite3
|
||||
@@ -51,7 +51,7 @@ SELECT {} FROM message
|
||||
""".format(','.join(WeChatMsg.FIELDS)))
|
||||
for row in db_msgs:
|
||||
msg = WeChatMsg(row)
|
||||
if msg.type not in WeChatMsg.FILTER_TYPES:
|
||||
if not WeChatMsg.filter_type(msg.type):
|
||||
self.msgs_by_talker[msg.talker].append(msg)
|
||||
self.msgs_by_talker = dict([
|
||||
(self.contacts[k], sorted(v, key=lambda x: x.createTime))
|
||||
@@ -68,6 +68,11 @@ SELECT {} FROM message
|
||||
self.username = userinfo[2]
|
||||
print "Your username is: {}".format(self.username)
|
||||
|
||||
def _parse_imginfo(self):
|
||||
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://')])
|
||||
print "Got {} big images.".format(len(self.imginfo))
|
||||
|
||||
def _find_msg_by_type(self, msgs=None):
|
||||
ret = []
|
||||
if msgs is None:
|
||||
@@ -81,3 +86,4 @@ SELECT {} FROM message
|
||||
self._parse_userinfo()
|
||||
self._parse_contact()
|
||||
self._parse_msg()
|
||||
self._parse_imginfo()
|
||||
|
||||
+26
-7
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: UTF-8 -*-
|
||||
# File: render.py
|
||||
# Date: Sun Nov 23 18:01:55 2014 +0800
|
||||
# Date: Sun Nov 23 22:57:13 2014 +0800
|
||||
# Author: Yuxin Wu <[email protected]>
|
||||
|
||||
import os
|
||||
import base64
|
||||
import audioread
|
||||
import eyed3
|
||||
LIB_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
CSS_FILE = os.path.join(LIB_PATH, 'static/wx.css')
|
||||
HTML_FILE = os.path.join(LIB_PATH, 'static/template.html')
|
||||
@@ -19,7 +19,9 @@ except:
|
||||
from .msg import *
|
||||
from .utils import ensure_unicode
|
||||
|
||||
TEMPLATES_FILES = {TYPE_MSG: "TP_MSG", TYPE_SPEAK: "TP_SPEAK"}
|
||||
TEMPLATES_FILES = {TYPE_MSG: "TP_MSG",
|
||||
TYPE_SPEAK: "TP_SPEAK",
|
||||
TYPE_IMG: "TP_IMG"}
|
||||
TEMPLATES = dict([(k, open(os.path.join(
|
||||
LIB_PATH, 'static/{}.html'.format(v))).read())
|
||||
for k, v in TEMPLATES_FILES.iteritems()])
|
||||
@@ -40,19 +42,23 @@ class HTMLRender(object):
|
||||
return (avt1, avt2)
|
||||
|
||||
def get_voice_mp3(self, imgpath):
|
||||
""" return base64 string"""
|
||||
""" return base64 string, and voice duration"""
|
||||
if self.res is None:
|
||||
return ""
|
||||
amr_fpath = self.res.speak_data[imgpath]
|
||||
mp3_file = os.path.join('/tmp', os.path.basename(amr_fpath)[:-4] + '.mp3')
|
||||
os.system('sox {} {}'.format(amr_fpath, mp3_file))
|
||||
# TODO is there a library to use?
|
||||
ret = os.system('sox {} {}'.format(amr_fpath, mp3_file))
|
||||
if ret != 0:
|
||||
print "Sox Failed!"
|
||||
return ""
|
||||
mp3_string = open(mp3_file, 'rb').read()
|
||||
duration = audioread.audio_open(mp3_file).duration
|
||||
duration = eyed3.load(mp3_file).info.time_secs
|
||||
os.unlink(mp3_file)
|
||||
return base64.b64encode(mp3_string), duration
|
||||
|
||||
def render_msg(self, msg):
|
||||
""" render a message, return the block"""
|
||||
""" render a message, return the html block"""
|
||||
# TODO
|
||||
try:
|
||||
template = ensure_unicode(TEMPLATES[msg.type])
|
||||
@@ -61,6 +67,19 @@ class HTMLRender(object):
|
||||
return template.format(sender_label='you' if not msg.isSend else 'me',
|
||||
voice_duration=duration,
|
||||
voice_str=audio_str)
|
||||
elif msg.type == TYPE_IMG:
|
||||
img = ""
|
||||
svrid = msg.msgSvrId
|
||||
imgpath = msg.imgPath
|
||||
#bigimg = self.parser.imginfo.get(svrid)
|
||||
#if bigimg:
|
||||
#img = self.res.get_img(bigimg, smart=False)
|
||||
if len(img) == 0:
|
||||
#print "Big Image Failed: {}".format(bigimg)
|
||||
img = imgpath.split('_')[-1]
|
||||
img = self.res.get_img(img)
|
||||
return template.format(sender_label='you' if not msg.isSend else 'me',
|
||||
img_data=img)
|
||||
else:
|
||||
raise
|
||||
except:
|
||||
|
||||
+43
-3
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: UTF-8 -*-
|
||||
# File: res.py
|
||||
# Date: Sun Nov 23 15:59:48 2014 +0800
|
||||
# Date: Sun Nov 23 21:08:44 2014 +0800
|
||||
# Author: Yuxin Wu <[email protected]>
|
||||
|
||||
import glob
|
||||
@@ -13,12 +13,14 @@ import base64
|
||||
from lib.avatar import AvatarReader
|
||||
|
||||
VOICE_DIRNAME = 'voice2'
|
||||
JPEG_QUALITY = 50
|
||||
|
||||
class Resource(object):
|
||||
""" multimedia resources in chat"""
|
||||
def __init__(self, res_dir):
|
||||
assert os.path.isdir(res_dir), "No such directory: {}".format(res_dir)
|
||||
self.res_dir = res_dir
|
||||
self.img_dir = os.path.join(res_dir, 'image2')
|
||||
self.avt_reader = AvatarReader(self.res_dir)
|
||||
self.init()
|
||||
|
||||
@@ -38,6 +40,11 @@ class Resource(object):
|
||||
"Error interpreting the protocol, this is a bug!"
|
||||
self.speak_data[key] = full_path
|
||||
|
||||
@staticmethod
|
||||
def get_file_b64(fname):
|
||||
data = open(fname, 'rb').read()
|
||||
return base64.b64encode(data)
|
||||
|
||||
def get_avatar(self, username):
|
||||
""" return base64 string"""
|
||||
ret = self.avt_reader.get_avatar(username)
|
||||
@@ -45,9 +52,42 @@ class Resource(object):
|
||||
return ""
|
||||
im = Image.fromarray(ret)
|
||||
buf = cStringIO.StringIO()
|
||||
im.save(buf, 'JPEG', quality=50)
|
||||
im.save(buf, 'JPEG', quality=JPEG_QUALITY)
|
||||
jpeg_str = buf.getvalue()
|
||||
return base64.b64encode(jpeg_str)
|
||||
|
||||
def get_img_file(self, fname, smart):
|
||||
""" return base64 string"""
|
||||
dir1, dir2 = fname[:2], fname[2:4]
|
||||
if not smart:
|
||||
filename = os.path.join(self.img_dir, dir1, dir2, fname)
|
||||
if os.path.isfile(filename):
|
||||
return filename
|
||||
else:
|
||||
dirname = os.path.join(self.img_dir, dir1, dir2)
|
||||
if not os.path.isdir(dirname):
|
||||
print "Directory not found: {}".format(dirname)
|
||||
return ""
|
||||
maxf, maxsize = "", 0
|
||||
for f in os.listdir(dirname):
|
||||
if fname in f:
|
||||
full_name = os.path.join(dirname, f)
|
||||
size = os.path.getsize(full_name)
|
||||
if size > maxsize:
|
||||
maxsize = size
|
||||
maxf = full_name
|
||||
if maxsize == 0:
|
||||
return ""
|
||||
else:
|
||||
return maxf
|
||||
return ""
|
||||
|
||||
|
||||
def get_img(self, fname, smart=True):
|
||||
img_file = self.get_img_file(fname, smart)
|
||||
if not img_file.endswith('jpg') or not img_file.startswith('th_'):
|
||||
im = Image.open(open(img_file, 'rb'))
|
||||
buf = cStringIO.StringIO()
|
||||
im.save(buf, 'JPEG', quality=JPEG_QUALITY)
|
||||
return base64.b64encode(buf.getvalue())
|
||||
else:
|
||||
return Resource.get_file_b64(img_file)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="chatItem {sender_label}">
|
||||
<div class="chatItemContent">
|
||||
<img class="avatar" src="">
|
||||
<div class="cloud cloudImg">
|
||||
<div class="cloudPannel">
|
||||
<div class="cloudBody">
|
||||
<div class="cloudContent">
|
||||
<span class="img_wrap">
|
||||
<img class="zoomIn imageBorder" src="data:image/jpeg;base64,{img_data}" onclick="popImg(event)">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,14 +10,14 @@
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="chat" class="chatPanel normalPanel">
|
||||
<div class="chatContainer" style="height: 600px;">
|
||||
<div class="chatMainPanel" id="chatMainPanel">
|
||||
<div class="chatContainer" style="min-height:100%;">
|
||||
<div class="chatMainPanel" id="chatMainPanel" style="height:100%;">
|
||||
<div class="chatTitle">
|
||||
<div class="chatNameWrap">
|
||||
<p class="chatName" id="messagePanelTitle">{talker}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chatScorll" style="height: 550px; overflow-y: scroll; position: relative; font-family:initial;">
|
||||
<div class="chatScorll" style="height: 95%; overflow-y: scroll; position: relative; font-family:initial;">
|
||||
<div id="chat_chatmsglist" class="chatContent" style="position: absolute;">
|
||||
{messages}
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -1392,7 +1392,7 @@ a.btnSecondary:active {
|
||||
}
|
||||
/*会è¯å†…容窗体*/
|
||||
.chatPanel .chatScorll {
|
||||
height: 390px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
overflow: hidden;
|
||||
|
||||
Reference in New Issue
Block a user