From 337c1f8acf305e4436af767195fe700c952564de Mon Sep 17 00:00:00 2001 From: ppwwyyxx Date: Sat, 20 Dec 2014 20:30:46 +0800 Subject: [PATCH] emoji works --- README.md | 4 +++- lib/msg.py | 13 ++++++++++++- lib/parser.py | 12 +++++++++++- lib/render.py | 16 +++++++++++----- lib/res.py | 31 +++++++++++++++++++++++++------ lib/static/wx.css | 3 --- 6 files changed, 62 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 27f2d13..8030882 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ ``` ### TODO -+ Parse custom emoji, and other message types ++ Parse uncommon message types + Show name of emoji in text + Search by uid/username.. ++ Fix emoji sizes.. ++ Sometimes beautiful soup failed on links.. diff --git a/lib/msg.py b/lib/msg.py index c480ef1..1f218db 100644 --- a/lib/msg.py +++ b/lib/msg.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: msg.py -# Date: Sat Dec 20 15:12:34 2014 +0800 +# Date: Sat Dec 20 18:38:59 2014 +0800 # Author: Yuxin Wu from datetime import datetime @@ -77,6 +77,9 @@ class WeChatMsg(object): if not name: name = msg.get('alias', '') return u"NAMECARD: {}".format(name) + elif self.type == TYPE_EMOJI: + # TODO add emoji name + return self.content else: return self.content @@ -95,3 +98,11 @@ class WeChatMsg(object): def __lt__(self, r): return self.createTime < r.createTime + + def get_emoji_product_id(self): + assert self.type == TYPE_EMOJI, "Wrong call to get_emoji_product_id()!" + soup = BeautifulSoup(self.content) + emoji = soup.find('emoji') + if emoji is None: + return None + return emoji.get('productid', None) diff --git a/lib/parser.py b/lib/parser.py index 601fafc..46208bf 100644 --- a/lib/parser.py +++ b/lib/parser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: parser.py -# Date: Sat Dec 20 15:58:16 2014 +0800 +# Date: Sat Dec 20 19:40:31 2014 +0800 # Author: Yuxin Wu import sqlite3 @@ -30,6 +30,7 @@ class WeChatDBParser(object): self.cc = self.db_conn.cursor() self.contacts = {} self.msgs_by_talker = defaultdict(list) + self.emojis = {} self.parse() def _parse_contact(self): @@ -86,8 +87,17 @@ SELECT {} FROM message ret.append(msg) return sorted(ret) + def _parse_emoji(self): + # wechat provided emojis + emojiinfo_q = self.cc.execute( +""" SELECT md5, desc, groupid FROM EmojiInfoDesc """) + for row in emojiinfo_q: + md5, desc, group = row + self.emojis[md5] = (group, desc) + def parse(self): self._parse_userinfo() self._parse_contact() self._parse_msg() self._parse_imginfo() + self._parse_emoji() diff --git a/lib/render.py b/lib/render.py index 7a96727..c5691b8 100644 --- a/lib/render.py +++ b/lib/render.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: render.py -# Date: Sat Dec 20 17:48:48 2014 +0800 +# Date: Sat Dec 20 20:15:47 2014 +0800 # Author: Yuxin Wu import os @@ -24,7 +24,8 @@ from smiley import SmileyProvider TEMPLATES_FILES = {TYPE_MSG: "TP_MSG", TYPE_IMG: "TP_IMG", - TYPE_SPEAK: "TP_SPEAK"} + TYPE_SPEAK: "TP_SPEAK", + TYPE_EMOJI: "TP_EMOJI"} TEMPLATES = dict([(k, open(os.path.join( LIB_PATH, 'static/{}.html'.format(v))).read()) for k, v in TEMPLATES_FILES.iteritems()]) @@ -96,10 +97,15 @@ class HTMLRender(object): big_img=bigimg) elif msg.type == TYPE_EMOJI: imgpath = msg.imgPath - print imgpath - return fallback() - # custom emoji + if imgpath in self.parser.emojis: + group, _ = self.parser.emojis[imgpath] + else: + group = None + emoji_img, format = self.res.get_emoji(imgpath, group) + return template.format(sender_label=sender, + emoji_format=format, + emoji_img=emoji_img) return fallback() def render_msgs(self, msgs): diff --git a/lib/res.py b/lib/res.py index da98915..80a97dd 100644 --- a/lib/res.py +++ b/lib/res.py @@ -1,22 +1,26 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: res.py -# Date: Sat Dec 20 16:26:51 2014 +0800 +# Date: Sat Dec 20 20:14:40 2014 +0800 # Author: Yuxin Wu import glob import os +import re import Image import cStringIO import base64 import logging -import eyed3 +import imghdr logger = logging.getLogger(__name__) +import eyed3 + from lib.avatar import AvatarReader VOICE_DIRNAME = 'voice2' IMG_DIRNAME = 'image2' +EMOJI_DIRNAME = 'emoji' JPEG_QUALITY = 50 class Resource(object): @@ -25,6 +29,7 @@ class Resource(object): 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, IMG_DIRNAME) + self.emoji_dir = os.path.join(res_dir, EMOJI_DIRNAME) assert os.path.isdir(self.img_dir), \ "No such directory: {}".format(self.img_dir) self.avt_reader = AvatarReader(self.res_dir) @@ -78,7 +83,7 @@ class Resource(object): jpeg_str = buf.getvalue() return base64.b64encode(jpeg_str) - def get_img_file(self, fnames): + def _get_img_file(self, fnames): """ fnames: a list of filename to search for return (filename, filename) of (big, small) image. could be empty string. @@ -120,13 +125,13 @@ class Resource(object): def get_img(self, fnames): """ return two base64 jpg string""" - big_file, small_file = self.get_img_file(fnames) + big_file, small_file = self._get_img_file(fnames) def get_jpg_b64(img_file): if not img_file: return None - if not img_file.endswith('jpg'): - # possibly not jpg + if not img_file.endswith('jpg') and \ + imghdr.what(img_file) != 'jpeg': im = Image.open(open(img_file, 'rb')) buf = cStringIO.StringIO() im.save(buf, 'JPEG', quality=JPEG_QUALITY) @@ -140,3 +145,17 @@ class Resource(object): pass return (big_file, small_file) + def get_emoji(self, md5, pack_id): + path = self.emoji_dir + if pack_id: + path = os.path.join(path, pack_id) + candidates = glob.glob(os.path.join(path, '{}*'.format(md5))) + candidates = [k for k in candidates if \ + not k.endswith('_thumb') and not k.endswith('_cover')] + if len(candidates) > 1: + # annimation + candidates = [k for k in candidates if not re.match('.*_[0-9]+$', k)] + assert len(candidates) == 1 + fname = candidates[0] + return Resource.get_file_b64(fname), imghdr.what(fname) + diff --git a/lib/static/wx.css b/lib/static/wx.css index e751bea..619cb38 100644 --- a/lib/static/wx.css +++ b/lib/static/wx.css @@ -5242,7 +5242,6 @@ a.btnBlue:active .btnBluePanel { } .cloudImg .cloudContent { min-height: 30px; - max-height: 200px; } .cloudImg .cloudContent img { min-width: 50px; @@ -5447,8 +5446,6 @@ a.btnBlue:active .btnBluePanel { } .you .cloudImg .img_wrap{ display: block; - max-width: 280px; - max-height: 150px; overflow: hidden; } .you .cloudImg .cloudArrow {