微信PC端各个数据库文件结构与功能简述 - Multi文件夹
异想之旅:本人原创博客完全手敲,绝对非搬运,全网不可能有重复;本人无团队,仅为技术爱好者进行分享,所有内容不牵扯广告。本人所有文章仅在CSDN、掘金和个人博客(一定是异想之旅域名)发布,除此之外全部是盗文!
相关内容:
Multi
文件夹中的文件解码和之前的其它数据库操作相同。
该文件夹中文件结构比较简单,只有三种:FTSMSG
、MediaMSG
和MSG
。这里说是三种不是三个,是因为这里的数据库达到一定大小后会拆分。
FTSMSG
看过《总述》一文的应该很熟悉 FTS 这一前缀了——这代表的是搜索时所需的索引。
其内主要的内容是这样的两张表:
- FTSChatMsg2_content:内有三个字段
- docid:从1开始递增的数字,相当于当前条目的 ID
- c0content:搜索关键字(在微信搜索框输入的关键字被这个字段包含的内容可以被搜索到)
- c1entityId:尚不明确用途,可能是校验相关
- FTSChatMsg2_MetaData
- docid:与
FTSChatMsg2_content
表中的 docid 对应 - msgId:与
MSG
数据库中的内容对应 - entityId:与
FTSChatMsg2_content
表中的 c1entityId 对应 - type:可能是该消息的类型
- 其余字段尚不明确
- docid:与
特别地,表名中的这个数字2,个人猜测可能是当前数据库格式的版本号。
MediaMSG
这里存储了所有的语音消息。数据库中有且仅有Media
一张表,内含三个有效字段:
- Key
- Reserved0
- Buf
其中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
终于到了整个文件,不,整个工程最重要的位置了——聊天记录核心数据库!
内部主要的两个表是MSG
和Name2ID
。
其中Name2ID
这张表只有一列,内容格式是微信号
或群聊ID@chatroom
,作用是使MSG
中的某些字段与之对应。虽然表中没有 ID 这一列,但事实上微信默认了第几行 ID 就是几(从1开始编号)。
下面主要来说MSG
这张表(加粗是用于提醒自己内容待补充,并非是重要信息):
- localId:字面意思消息在本地的 ID,暂未发现其功用
- TalkerId:消息所在房间的 ID(该信息为猜测,猜测原因见 StrTalker 字段),与
Name2ID
对应。 - MsgSvrID:猜测 Srv 可能是 Server 的缩写,代指服务器端存储的消息 ID
- Type:消息类型,具体对照见表1
- SubType:消息类型子分类,暂时未见其实际用途
- IsSender:是否是自己发出的消息,也就是标记消息展示在对话页左边还是右边,取值0或1
- CreateTime:消息创建时间的秒级时间戳。此处需要进一步实验来确认该时间具体标记的是哪个时间节点,个人猜测的规则如下:
- 从这台电脑上发出的消息:标记代表的是每个消息点下发送按钮的那一刻
- 从其它设备上发出的/收到的来自其它用户的消息:标记的是本地从服务器接收到这一消息的时间
- Sequence:次序,虽然看起来像一个毫秒级时间戳但其实不是。这是 CreateTime 字段末尾接上三位数字组成的,通常情况下为000,如果在出现两条 CreateTime 相同的消息则最后三位依次递增。需要进一步确认不重复范围是在一个会话内还是所有会话。
- StatusEx、FlagEx、Status、MsgServerSeq、MsgSequence:这五个字段个人暂时没有分析出有效信息
- StrTalker:消息发送者的微信号。特别说明,从这里来看的话,上面的 TalkerId 字段大概率是指的消息所在的房间ID,而非发送者ID,当然也可能和 TalkerId 属于重复内容,这一点待确认。
- StrContent:字符串格式的数据。特别说明的是,除了文本类型的消息外,别的大多类型这一字段都会是一段 XML 数据标记一些相关信息。
- DisplayContent:对于拍一拍,保存拍者和被拍者账号信息
- Reserved0~6:这些字段也还没有分析出有效信息,也有的字段恒为空
- CompressContent:字面意思是压缩的数据,实际上也就是微信任性不想存在 StrContent 里的数据在这里(例如带有引用的文本消息等;这里的消息只能根据二进制分辨出包含什么,但是具体格式规范、如何取出数据本人不知)
- BytesExtra:额外的二进制格式数据
- BytesTrans:目前看这是一个恒为空的字段
这里面的猜测内容比较多,还有很多标注了应该进一步实验的还没有完成,是因为由于数据库解开后不可能随新收到的消息实时更新,而每次新发送一条消息也不知道它会出现在拆分后的哪个数据库中,因此实验效率极低。
表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 | 系统通知(特别包含你邀请别人加入群聊) |
本文参考资料(排名不分先后):
更多推荐
所有评论(0)