赞
踩
异想之旅:本人原创博客完全手敲,绝对非搬运,全网不可能有重复;本人无团队,仅为技术爱好者进行分享,所有内容不牵扯广告。本人所有文章仅在CSDN、掘金和个人博客(一定是异想之旅域名)发布,除此之外全部是盗文!
相关内容:
Multi文件夹中的文件解码和之前的其它数据库操作相同。
该文件夹中文件结构比较简单,只有三种:FTSMSG、MediaMSG和MSG。这里说是三种不是三个,是因为这里的数据库达到一定大小后会拆分。
看过《总述》一文的应该很熟悉 FTS 这一前缀了——这代表的是搜索时所需的索引。
其内主要的内容是这样的两张表:
FTSChatMsg2_content表中的 docid 对应MSG数据库中的内容对应FTSChatMsg2_content表中的 c1entityId 对应特别地,表名中的这个数字2,个人猜测可能是当前数据库格式的版本号。
这里存储了所有的语音消息。数据库中有且仅有Media一张表,内含三个有效字段:
其中Reserved0字段与MSG数据库中消息的MsgSvrID一一对应。
第三项即语音的二进制数据,观察头部即可发现这些文件是以 SILK 格式存储的。这是一种微软为 Skype 开发并开源的语音格式,具体可以自行 Google。
下面是将 Buf 字段中的数据导出为文件的代码:
import sqlite3 def writeTofile(data, filename): with open(filename, 'wb') as file: file.write(data) print("Stored blob data into: ", filename, "\n") def readBlobData(key): try: sqliteConnection = sqlite3.connect('dbs/decoded_MediaMSG0.db') cursor = sqliteConnection.cursor() print("Connected to SQLite") sql_fetch_blob_query = """SELECT * from Media where Key = ?""" cursor.execute(sql_fetch_blob_query, (key, )) record = cursor.fetchall() for row in record: print("Key = ", row[0], "Reserved0 = ", row[1]) file = row[2] print("Storing on disk \n") path = f'{row[0]}.silk' writeTofile(file, path) cursor.close() except sqlite3.Error as error: print("Failed to read blob data from sqlite table", error) finally: if sqliteConnection: sqliteConnection.close() print("sqlite connection is closed") readBlobData(1099511630953)
如果需要通过MSG数据库中的MsgSvrID找文件,则改一下 SQL 查询语句,再遍历所有数据库即可。
下面是将 silk 文件转为 wav 的代码(实现思路是先转为 pcm 再转 wav;wav 的采样率数据是个人试出来的):
KEY = 1099511630953 import wave from pathlib import Path import pilk def pcm2wav(pcm_file, wav_file, channels=1, bits=16, sample_rate=24000): pcmf = open(pcm_file, 'rb') pcmdata = pcmf.read() pcmf.close() if bits % 8 != 0: raise ValueError("bits % 8 must == 0. now bits:" + str(bits)) wavfile = wave.open(wav_file, 'wb') wavfile.setnchannels(channels) wavfile.setsampwidth(bits // 8) wavfile.setframerate(sample_rate) wavfile.writeframes(pcmdata) wavfile.close() duration = pilk.decode(f"{KEY}.silk", f"{KEY}.pcm") # print("语音时间为:", duration) Path(f"{KEY}.silk").unlink() pcm2wav(f"{KEY}.pcm", f"{KEY}.wav") Path(f"{KEY}.pcm").unlink()
这两个代码没有加详细解释,自己读一下吧。
终于到了整个文件,不,整个工程最重要的位置了——聊天记录核心数据库!
内部主要的两个表是MSG和Name2ID。
其中Name2ID这张表只有一列,内容格式是微信号或群聊ID@chatroom,作用是使MSG中的某些字段与之对应。虽然表中没有 ID 这一列,但事实上微信默认了第几行 ID 就是几(从1开始编号)。
下面主要来说MSG这张表(加粗是用于提醒自己内容待补充,并非是重要信息):
Name2ID对应。这里面的猜测内容比较多,还有很多标注了应该进一步实验的还没有完成,是因为由于数据库解开后不可能随新收到的消息实时更新,而每次新发送一条消息也不知道它会出现在拆分后的哪个数据库中,因此实验效率极低。
表1:MSG.Type字段数值与含义对照表(可能可以扩展到其它数据库中同样标记消息类型这一信息的字段)
| 分类 | 子分类 | 对应类型 |
|---|---|---|
| 1 | 0 | 文本 |
| 3 | 0 | 图片 |
| 34 | 0 | 语音 |
| 43 | 0 | 视频 |
| 47 | 0 | 动画表情(第三方开发的表情包) |
| 49 | 1 | 类似文字消息而不一样的消息,目前只见到一个阿里云盘的邀请注册是这样的。估计和 57 子类的情况一样 |
| 49 | 5 | 卡片式链接,CompressContent 中有标题、简介等,BytesExtra 中有本地缓存的封面路径 |
| 49 | 6 | 文件,CompressContent 中有文件名和下载链接(但不会读),BytesExtra 中有本地保存的路径 |
| 49 | 8 | 用户上传的 GIF 表情,CompressContent 中有 CDN 链接,不过似乎不能直接访问下载 |
| 49 | 19 | 合并转发的聊天记录,CompressContent 中有详细聊天记录,BytesExtra 中有图片视频等的缓存 |
| 49 | 33/36 | 分享的小程序,CompressContent 中有卡片信息,BytesExtra 中有封面缓存位置 |
| 49 | 57 | 带有引用的文本消息(这种类型下 StrContent 为空,发送和引用的内容均在 CompressContent 中) |
| 49 | 63 | 视频号直播或直播回放等 |
| 49 | 87 | 群公告 |
| 49 | 88 | 视频号直播或直播回放等 |
| 49 | 2000 | 转账消息(包括发出、接收、主动退还) |
| 49 | 2003 | 赠送红包封面 |
| 10000 | 0 | 系统通知(居中出现的那种灰色文字) |
| 10000 | 4 | 拍一拍 |
| 10000 | 8000 | 系统通知(特别包含你邀请别人加入群聊) |
本文参考资料(排名不分先后):
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。