转载自:https://blog.lc044.love/post/13

耗时4个月我把微信研究了个遍,以下内容仅代表个人经验或猜想,转载请注明出处。

一、💡 引言


前言

我深信有意义的不是微信,而是隐藏在对话框背后的一个个深刻故事。未来,每个人都能拥有AI的陪伴,而你的数据能够赋予它有关于你过去的珍贵记忆。我希望每个人都有将自己的生活痕迹👨‍👩‍👦👚🥗🏠️🚴🧋⛹️🛌🛀留存的权利,而不是遗忘💀。

AI的发展不仅仅是技术的提升,更是情感💞的延续。每一个对话、每一个互动都是生活中独一无二的片段,是真实而动人的情感交流。因此,我希望AI工作者们能够善用这些自己的数据,用于培训独特的、属于个体的人工智能。让个人AI成为生活中的朋友,能够理解、记录并分享我们的欢笑、泪水和成长。

那天,AI不再是高不可攀的存在,而是融入寻常百姓家的一部分。因为每个人能拥有自己的AI,将科技的力量融入生活的方方面面。这是一场关于真情实感的革命,一场让技术变得更加人性化的探索,让我们共同见证未来的美好。

所以《留痕》


二、✨ 功能特点

MemoTrace 支持解析、导出、分析三大功能,支持微信3和微信4全版本,支持导出多种格式的聊天记录、支持导出几乎全部类型的消息类型,既有适合开发者二次开发的后端接口又有适于小白用户使用的界面应用程序。

  • 支持Windows微信3.x和微信4.0

  • 获取手机号、微信昵称、wxid

  • 解析微信聊天记录数据库

  • 增量解析微信聊天记录数据库

  • 导出聊天记录

    • 导出联系人

      • 微信号、昵称、备注、性别、地区、签名、标签、电话
    • 导出HTML

      • 支持的消息类型:文本、图片、表情包、视频、转账、分享的卡片链接、语音、转账、音视频通话记录、合并转发的聊天记录、引用消息、名片分享、小程序、视频号、拍一拍等系统消息
      • 支持时间轴跳转
      • 支持日期跳转
      • 链接支持跳转到原文
      • 引用消息支持定位到原文
    • 导出TXT

    • 导出docx

    • 导出csv

    • 导出Excel(xlsx)

      • 字段:消息ID、类型、发送人、内容、备注、昵称

      • 支持的消息类型

        • 文本、图片、位置分享、分享的卡片链接、引用消息、名片分享、转账、音视频通话记录、小程序、视频号、拍一拍等系统消息
        • 语音、视频、文件都是以超链接的形式加到内容里,图片嵌入到单元格里
    • 批量导出聊天记录
    • 导出跟企业微信联系人的聊天记录
    • 导出JSON(用于训练AI)
      • 支持Alpaca、GLM3、GLM4格式的多轮对话数据集
    • 导出markdown格式
    • 导出群成员信息(微信号、昵称、备注、性别、地区)
    • 导出公众号文章链接
      • 支持导出格式:HTML、Excel(xlsx)(仅会员可用)、TXT
  • 年度聊天报告

    • 年度聊天好友
    • 年度关键词
    • 双人年度聊天报告
    • AI分析
  • 统计数据

    • 聊天统计图
    • 消息数量分布饼图
    • 星期分布图

后面内容供学习使用,为避免头疼,普通用户请转至官网 memotrace.cn 下载使用

三、⛏️ 微信分析

3.1 微信数据库目录结构

3.1.1 微信3

微信3数据库目录结构

text
├─Msg
│ │ Applet.db # 小程序
│ │ BizChat.db #
│ │ BizChatMsg.db
│ │ ChatMsg.db
│ │ ChatRoomUser.db
│ │ ClientGeneral.db
│ │ CustomerService.db
│ │ Emotion.db # 表情包
│ │ Favorite.db # 微信收藏
│ │ FTSContact.db # 搜索联系人
│ │ FTSFavorite.db # 搜索收藏
│ │ FunctionMsg.db
│ │ HardLinkFile.db # 微信文件
│ │ HardLinkImage.db # 微信图片
│ │ HardLinkVideo.db # 微信视频
│ │ ImageTranslate.db
│ │ LinkHistory.db
│ │ MicroMsg.db # 微信联系人
│ │ Misc.db # 微信联系人头像
│ │ MultiSearchChatMsg.db
│ │ NewTips.db
│ │ OpenIMContact.db # 企业微信联系人
│ │ OpenIMMedia.db # 企业微信语音
│ │ OpenIMMsg.db # 企业微信聊天记录
│ │ OpenIMResource.db # 企业微信联系人公司信息
│ │ PreDownload.db
│ │ PublicMsg.db # 公众号消息
│ │ PublicMsgMedia.db # 公众号语音消息
│ │ Sns.db # 朋友圈数据
│ │ StoreEmotion.db
│ │ SyncMsg.db
│ │ Voip.db
│ └─Multi # Multi文件夹里的数据库采用了分库操作,将一个大数据库分成多个便于提高检索效率
│ FTSMSG0.db # 搜索聊天记录
│ FTSMSG1.db
│ MediaMSG0.db # 语音消息 0
│ MediaMSG1.db # 语音消息 1
│ MSG0.db # 聊天记录 0
│ MSG1.db          # 聊天记录 1

3.1.2 微信4

微信4数据库目录结构

text
├─db_storage
│ │
│ ├─biz
│ │ biz.db
│ ├─contact # 联系人
│ │ contact.db
│ │ contact_fts.db
│ │ fmessage_new.db
│ │ wa_contact_new.db
│ ├─emoticon # 表情包
│ │ emoticon.db
│ ├─favorite # 微信收藏
│ │ favorite.db
│ │ favorite_fts.db
│ ├─hardlink # 微信图片、视频、文件
│ │ hardlink.db
│ ├─head_image # 微信头像
│ │ head_image.db
│ ├─ilinkvoip
│ │ ilinkvoip.db
│ ├─message # 聊天记录
│ │ biz_message_0.db # 公众号消息
│ │ media_0.db # 语音消息
│ │ message_0.db # 聊天记录
│ │ message_1.db
│ │ message_fts.db
│ │ message_resource.db
│ │ message_revoke.db
│ ├─newtips
│ │ newtips.db
│ ├─session # 聊天会话窗口
│ │ session.db
│ ├─sns # 朋友圈
│ │ sns.db
│ ├─teenager
│ │ teenager.db
│ ├─tencentpay
│ │ tencentpay.db
│ ├─wcfinder
│ │ wcfinder.db
│ └─websearch
│ websearch.db

3.1.3 数据库结构说明

不管是微信3还是微信4,聊天数据、图片、视频、文件、表情包、语音都是存储在不同的数据库中的,因此解析一条聊天记录要综合查询多个数据库的内容。

为了避免跨数据库连接操作,MemoTrace为每个数据库都建立一个连接,各数据库之间互相不产生依赖,由上层模块综合调用各个数据库,组装出每一条完整的数据。

微信3和微信4的数据库不尽相同,部分数据库字段有很大调整,所以MemoTrace把v3和v4分成独立的两个部分,把v3和v4的聊天记录(Message)和联系人(Contact)抽象得出相同的一个数据结构,通过ManagerV3和ManagerV4实现不同数据库模块的分布管理和扩展,通过DataBaseInterface层对外提供统一接口,隔离内部实现。

详细设计将在后面给出。

数据库模块架构

3.2 微信部分数据解析

3.2.1 微信图片加密方式

微信3

微信4.0之前的图片都采用了最简单的异或加密,即采用一个异或密钥(一个0-255的整数)与图片文件的每个字节进行异或运算。由于异或运算具有可逆性,采用相同的密钥再做一次异或运算即可得到原始图片文件。

异或运算性质

  1. 交换律: A ^ B = B ^ A
  2. 结合律: ( A ^ B ) ^ C = A ^ ( B ^ C )
  3. 自反性: A ^ B ^ B = A

假设:

  • 加密后数据:C
  • 已知的原始文件头(Magic Number):P
  • 密钥:K
  • 密钥长度:L
  • 第 i 个字节:i

不同格式的图片通常有固定的文件头,例如:

  • PNG: 89 50 4E 47 0D 0A 1A 0A
  • JPG: FF D8 FF E0 或 FF D8 FF DB
  • BMP: 42 4D
  • GIF: 47 49 46 38 39 61 或 47 49 46 38 37 61

根据异或运算的性质:

Ci​=Pi​⊕Ki​

可得:

Ki​=Ci​⊕Pi​

所以,我们可以通过已知的加密数据 C 和已知的文件头 P 计算密钥的前 L 个字节:

K0​=C0​⊕P0​

K1​=C1​⊕P1​

...

KL−1​=CL−1​⊕PL−1​

密钥是一个0-255范围内的整数所以: key=K0​=K1​=K2​⋅⋅⋅=KL−1​


微信4

微信4的图片采用AES-EBC和异或混合加密方式。

.dat文件头(15个字节)

大小(字节) 内容/类型 说明
6 0x07085631 .dat文件标识符
4 int (小端序) AES-EBC128 加密长度
4 int (小端序) 异或加密长度
1 0x01 未知

dat文件头

文件末尾采用异或加密,加密长度最大为1MB,多余部分未加密。

例如:一个文件小于1kB,则全部是AES加密,如果大于1kB且小于1MB(实际是1MB零1KB),则前1KB部分采用AES加密,剩余部分采用异或加密。如果文件大于1MB,则前1KB采用AES加密,后面1MB采用异或加密,中间部分未加密。

dat文件结构

  • AES 密钥为:0xcfcd208495d565ef
  • 根据异或运算的可逆性,结合jpg文件末尾的 FF D9 两个字节,可以找一张微信生成的缩略图(一般为_t.dat结尾),两次异或运算即可得到异或密钥。

3.2.2 微信文件、文件夹命名规则

微信3

text
├─Applet
├─Backup
├─FileStorage # 存储微信图片、视频、文件
│ ├─Cache # 缩略图缓存
│ ├─CustomEmotion # 表情包
│ ├─Fav # 收藏
│ ├─File # 微信文件
│ ├─MsgAttach # 微信里与每个人的聊天数据
│ │ └─aefd036248e0d4d0f07900a6239272e4 # 该联系人wxid的md5加密
│ │ ├─Image # 聊天图片
│ │ │ └─2024-10
│ │ ├─Thumb # 聊天中产生的缩略图
│ │ │ └─2024-10
│ │ ├─File # 合并转发的聊天记录里的文件
│ │ │ └─2024-10
│ │ └─Video # 合并转发的聊天记录里的视频
│ │ └─2024-10
│ ├─Sns # 朋友圈数据
│ ├─Video # 聊天视频
│ │ └─2025-02
│ └─XEditor
├─Msg # 聊天记录数据库
└─ResUpdateV2

微信4

text
├─business
├─cache # 缓存
├─config # 配置文件
├─db_storage # 聊天记录数据库
├─msg # 与好友的聊天数据
│ ├─attach # 微信里与每个人的聊天数据
│ │ └─aefd036248e0d4d0f07900a6239272e4 # 该联系人wxid的md5加密
│ │ │ ├─2022-01 # 日期
│ │ │ │ ├─Img # 聊天图片
│ │ │ │ └─Rec # 合并转发的聊天记录数据
│ │ │ │ └─2090_136022
│ │ │ │ └─Img
│ ├─file # 微信文件
│ └─video # 微信视频
├─Msg # 聊天记录数据库
└─temp

微信4的文件命名方式跟之前有些微区别,但其数据存放是类似的(文件、视频单独存放在独立的文件夹中,图片按联系人分组存放到不同的文件夹中)

3.3 技术选型(Python、SQLite、protobuf解析等)

3.4 主要依赖库

四、🔭 系统架构设计

4.1 概述

该系统由多个模块组成,主要用于管理聊天会话、历史记录、联系人树以及数据导出与分析。系统的主要组成部分包括 GUI(用户界面)、wxManager(微信管理器)和 application(应用层),各个部分协同工作,实现聊天数据的存储、管理、分析和导出。

系统架构图

4.2 设计原则

  1. 面向对象设计

    • 系统主要采用面向对象编程(OOP)思想,通过抽象类和接口设计保证数据管理的灵活性。例如,DataBaseInterface 作为数据库访问接口,可以适配不同版本的数据库。
    • 整个系统采用模块化设计,每个功能单元相对独立,彼此之间通过接口进行通信,便于扩展和维护。
  2. 设计模式

    • 策略模式:wxManager 采用策略模式,根据不同的数据库版本选择合适的管理器(ManagerV3 或 ManagerV4)。
    • 外观模式:提供统一的数据访问接口,使 GUI 和 application 无需直接操作数据库。
  3. 数据流与通信

    1. GUI 发送请求给 wxManager。
    2. wxManager 通过数据库接口访问数据。
    3. application 获取 wxManager 提供的数据并进行处理。
    4. 处理后的数据返回给 GUI 或以报告形式导出。

4.3 系统模块

4.3.1 wxManager(微信管理器)

wxManager 作为系统的核心数据管理层,负责数据的存储、检索和管理。该模块采用策略模式和外观模式,使得不同版本的数据管理模块可以独立实现。

  1. 数据库接口(DataBaseInterface)

    该接口提供对数据库的统一访问方法,包括:

    • getMessages(params): List[Message] 获取聊天消息。
    • getContacts(params) 获取联系人信息。
    • other() 其他数据库操作。
  2. 数据管理模块

    wxManager 支持多个数据库管理模块:

    • ManagerV3:负责管理 V3 版本数据库,包含以下子模块:

      • MicroMsgDB:存储微信消息。
      • MsgDB:存储消息的具体信息。
      • 其他相关数据库。
    • ManagerV4:负责管理 V4 版本数据库,包含以下子模块:

      • contactDB:存储联系人信息。
      • messageDB:存储聊天消息。
      • 其他相关数据库。

4.3.2 application(应用层)

应用层提供数据导出、统计分析和年度报告功能。

  1. 数据导出

    • HtmlExporter:导出为 HTML 格式。
    • TxtExporter:导出为文本格式。
    • 其他格式导出功能。
  2. 统计分析

    • wordcloud:生成聊天词云。
    • chat analysis:进行聊天数据分析。
    • 其他统计分析功能。
  3. 年度报告

    • 2024 annual report:2024 年度聊天数据总结。
    • 其他年度数据报告。

4.3.3 GUI(用户界面)

GUI 负责提供用户交互界面,包括以下功能模块:

  • chat session(聊天会话):提供与聊天相关的实时会话功能。
  • chat history(聊天历史):提供聊天记录查询和管理功能。
  • contact tree(联系人树):显示联系人组织结构。
  • annual report(年度报告):提供年度聊天数据统计与分析。
  • exporter(数据导出):支持将聊天数据导出为不同格式。

五、🔎 详细设计

5.1 消息设计

设计思路

微信聊天消息的类型有很多(文本、图片、视频、语音、链接、合并转发的聊天记录等近二十种类型),除了文本消息,其他类型在数据库里基本都是以xml(或者压缩后的xml)的形式存储的,每种类型的xml格式都不相同,所以解析方式也不相同,所以需要有大量的if-else来处理不同的类型。为了避免这种麻烦,MemoTrace采用面向对象的设计原则,对消息进行抽象封装,基类定义各种消息的共同属性和方法,由子类针对特定消息类型进行单独处理。

5.1.1 设计目标

  1. 统一接口:提供统一的消息处理接口(如 to_text 和 to_json)。
  2. 可扩展性:支持通过继承添加新消息类型。
  3. 类型安全:利用 @dataclass 提供字段验证和类型提示。
  4. 多样化展示:支持消息的多种展示形式(文本、JSON)。
  5. 高效排序:实现基于 sort_seq 的消息排序功能。

消息类图

5.1.2 核心类:Message

Message 是所有消息类型的基类,定义了消息的基本属性和方法。子类通过继承并扩展此类,添加各自特有的功能和数据。

属性:

Message属性描述

  • local_id: 消息的本地 ID,用于标识消息的本地存储。
  • server_id: 消息的唯一 ID,用于全局唯一标识消息。
  • sort_seq: 用于排序的标识符。
  • timestamp: 消息的发送时间(秒级时间戳)。
  • str_time: 格式化后的时间字符串(例如:2024-12-01 12:00:00)。
  • type: 消息类型,基于 MessageType 枚举(如文本、图片、视频等)。
  • talker_id: 聊天对象的唯一标识(例如好友的 wxid 或群聊的 wxid)。
  • is_sender: 布尔值,标识当前用户是否为消息发送者。
  • sender_id: 消息发送者的唯一 ID。
  • display_name: 消息发送者的显示昵称(可以是备注名或群昵称)。
  • avatar_src: 消息发送者头像的资源路径或 URL。
  • status: 消息的状态标识(例如已发送、已读等)。
  • xml_content: 消息的 XML 格式数据。

方法:

  1. is_chatroom(self) -> bool: 判断消息是否来自群聊,通过 talker_id 是否以 @chatroom 结尾来实现。
  2. to_json(self): 转换为 JSON 格式(需子类实现)。
  3. to_text(self): 转换为纯文本(需子类实现)。
  4. __lt__(self, other): 定义消息的排序逻辑,基于 sort_seq 属性。

用途:

  • 统一定义消息的基础属性,便于子类继承和扩展。

5.1.2 子类设计

以下是从Message类派生的各类子类,针对不同的消息内容或功能进行设计。

  • 5.1.2.1. TextMessage(文本)

    • 表示纯文本消息。

    • 作为文本相关消息的父类,进一步细分为以下几类:

      • SystemMessage:系统生成的消息(如通知类消息)。
      • QuoteMessage:引用其他消息的文本消息。
      • AnnouncementMessage:用于广播公告的信息。
  • 5.1.2.2. FileMessage(文件)

    • 表示包含文件或多媒体内容的消息。

    • 作为媒体类消息的父类,进一步细分为以下几类:

      • ImageMessage:图片。
      • VideoMessage:视频。
      • VoiceMessage:语音消息。
      • EmojiMessage:表情符号的特殊媒体消息。
  • 5.1.2.3. LinkMessage(链接)

    • 表示包含超链接或其他URL内容的消息。

    • 作为链接相关消息的父类,进一步细分为以下几类:

      • ShareCardMessage:用于分享卡片或模板。
      • MusicShareMessage:用于分享音乐或音频内容。
      • AppletMessage:用于分享小程序或嵌入式应用。
      • MediaMessage:用于分享外部媒体链接。
  • 5.1.2.4. 其他特殊消息类型:

    • MergedMessage:合并转发的聊天记录。
    • PositionMessage: 地理位置信息。
    • TransferMessage: 转账消息。
    • RedEnvelopeMessage: 红包消息。
    • BusinessCardMessage:联系人名片。
    • FavNoteMessage:收藏笔记。
    • PatMessage:拍一拍。
    • VoipMessage:语音或视频通话相关的消息。

5.1.3 类层次结构

类层次结构体现了明确的职责分离:

  • Message是根类,提供通用的属性和方法。
  • 专门的消息类型(如TextMessage、FileMessage、LinkMessage)按功能分组。
  • 更细化的子类(如SystemMessage、ImageMessage、MusicShareMessage)针对特定消息类型提供功能。

5.2 消息创建

相关信息

上面说到每种消息类型的xml结构都不相同,需要单独解析,而且一个聊天记录的解析往往需要联合查询多个数据库,因此从数据库对象转成Message对象需要大量的if-else操作,为了避免这种麻烦,MemoTrace使用 工厂方法模式 创建聊天消息,针对每一种消息类型为其创建单独的工厂方法,每种消息的创建都是独立的,具有极强的扩展性。工厂方法模式通过定义创建对象的接口来实现消息对象的灵活构建,同时结合单例模式共享工厂实例,提升了系统的扩展性和性能。

消息创建流程


5.2.1 设计目标

  1. 解耦消息类型的创建:通过工厂类实现创建逻辑,避免在调用代码中直接初始化具体消息对象。
  2. 支持动态扩展消息类型:通过注册表机制,可以方便地新增消息类型的工厂。
  3. 提高性能:工厂类使用单例模式,确保只存在一个实例,减少不必要的实例化操作。
  4. 简化依赖管理:通过 Singleton 管理共享数据(如联系人信息)。

5.2.2 核心组件

5.2.2.1. 抽象工厂类:MessageFactory

  • MessageFactory 是所有具体工厂类的基类,定义了统一的 create 方法,用于创建消息对象。
  • 方法:
    • create(data, username, database_manager):抽象方法,由具体工厂类实现,负责根据输入数据创建消息对象。

5.2.2.2. 单例基类:Singleton

  • 为工厂类提供单例功能,确保每种消息类型的工厂只存在一个实例。

  • 功能:

    • 共享数据管理:通过类方法 set_shared_data 和 get_shared_data,管理工厂之间共享的数据。
    • 联系人缓存:通过 contacts 字典缓存联系人信息,避免重复查询数据库。
    • 聊天记录缓存:通过 messages 字典缓存聊天记录,方便查询引用消息。
    • 联系人获取逻辑:get_contact(wxid, database_manager) 方法从缓存或数据库获取联系人。
  • 缓存的设计:

    • 缓存的大小:确保不超过设定的 k 条目。超出时会自动删除最早插入的条目,这样可以保持最新的数据,有效节省内存。
    • 局部性原理:适合用于需要快速访问最近使用数据的场景,认为只会引用最近出现的聊天消息,如果没有就去数据库里查找。

5.2.2.3. 工厂实现类 每个工厂实现类继承自 MessageFactory 和 Singleton,用于创建具体的消息对象。

  1. TextMessageFactory

    • 用于创建文本消息(TextMessage)。
    • 实现 create 方法,根据输入的数据构造一个 TextMessage 实例。
  2. ImageMessageFactory

    • 用于创建图片消息(ImageMessage)。
    • 实现 create 方法,根据输入的数据(包括时间戳、发送者 ID、图片 URL 和分辨率)构造一个 ImageMessage 实例。

5.2.2.4. 工厂注册表:FACTORY_REGISTRY

  • 定义一个注册表,用于动态关联消息类型与对应的工厂实例。

  • 功能:

    • 存储消息类型(如 text, image)到具体工厂类实例的映射。
    • 可通过动态扩展注册表支持新的消息类型。

示例注册表内容:

python
# 工厂注册表
FACTORY_REGISTRY = {
-1: UnknownMessageFactory(),
MessageType.Text: TextMessageFactory(),
MessageType.Image: ImageMessageFactory(),
MessageType.Audio: AudioMessageFactory(),
MessageType.Video: VideoMessageFactory(),
···
MessageType.Quote: QuoteMessageFactory(),
MessageType.Transfer: TransferMessageFactory(),
MessageType.RedEnvelope: RedEnvelopeMessageFactory(),
}

5.2.3 主要类及方法的详细描述

  • MessageFactory

    • 定义:抽象基类,提供统一接口以创建消息对象。

    • 方法:

      • create(data, username, database_manager):

        • 参数:

          • data:从数据库查询或外部获取的消息数据(通常是元组)。
          • username:聊天对象的唯一标识(如 wxid)。
          • database_manager:用于数据库操作的接口。
        • 返回值:具体的消息对象。

  • Singleton

    • 定义:提供单例功能的基类。

    • 功能:

      • 单例管理:

        • __new__ 方法确保每个子类只有一个实例。
      • 共享数据管理:

        • set_shared_data/get_shared_data 用于设置和获取共享数据。
      • 联系人缓存:

        • contacts:存储联系人缓存信息。
        • set_contacts:初始化联系人缓存。
        • get_contact(wxid, database_manager):从缓存或数据库获取联系人。
  • TextMessageFactory

    • 功能:负责创建 TextMessage 实例。
    • 实现方法:
      • create(data, username, database_manager):
        • 数据处理和转换后,返回 TextMessage 对象。
  • ImageMessageFactory

    • 功能:负责创建 ImageMessage 实例。
    • 实现方法:
      • create(data, username, database_manager):
        • 从输入数据中提取 timestamp、sender_id、image_url 和 resolution,并创建一个 ImageMessage 实例。
  • 工厂注册表

    • 定义:FACTORY_REGISTRY 是一个字典,存储消息类型到具体工厂实例的映射。

    • 作用:

      • 通过注册表,可以动态查找合适的工厂类实例以创建对应的消息对象。
      • 示例:根据消息类型 text,从注册表获取 TextMessageFactory 的实例,并调用其 create 方法。

5.2.4 创建流程

  1. 工厂注册:

    • 将各消息类型对应的工厂类实例注册到 FACTORY_REGISTRY 中。
    • 如 text 类型对应 TextMessageFactory,image 类型对应 ImageMessageFactory。
  2. 消息创建:

    • 根据消息类型从 FACTORY_REGISTRY 获取对应的工厂实例。
    • 调用工厂实例的 create 方法,生成消息对象。

5.2.5 完整设计图

消息创建流程

  1. 抽象基类:MessageFactory

  2. 单例管理:Singleton

  3. 具体工厂类:

    • TextMessageFactory
    • ImageMessageFactory
  4. 注册表:FACTORY_REGISTRY

  5. 消息对象:TextMessage, ImageMessage 等

5.3 联系人设计

设计思路

微信联系人分为普通好友、群聊、企业微信联系人、公众号四种类型

联系人类图

5.4 数据库设计

5.4.1 概述

前面提到一个聊天消息的构建需要联合查询多个数据库的信息,微信v3和微信v4的数据库结构相似但差异也很大。在 MemoTrace 1.0的时候获取聊天记录直接操作MSG和其他相关数据库(如下图所示),任何应用的修改可能会影响多个模块,耦合度较高,不易扩展和维护,获取联系人的时候甚至出现了循环依赖,违反了单一职责原则,这是绝对不允许的。

数据库模块调用关系

MemoTrace 2.0 为了降低模块与应用之间的耦合,增加扩展性,采用一个中间模块统一管理各个子系统,这就是外观模式。每个数据库采用一个模块单独管理,各数据库之间互不依赖,Manager 查询多个子数据库,集中处理业务逻辑,减少各个模块的重复代码。

数据库模块调用关系2

由于微信数据库是固定的,2.0 加一个中间模块好像并没有太大作用,所以到2.1仍然有部分功能没有整合到 Maanager 中(主要是因为1.0的代码能用就懒得改了),直到微信4.0的出现才意识到这种设计的好处。只需要再增加一个与 Manager1 相同接口的 Manager2 即可不更改上层应用程序而增加微信4.0的支持。

数据库模块调用关系3

经历三个阶段才形成了下面的数据库模块架构:

数据库模块架构

5.4.2 核心类

六、📜附录1 微信v4数据库

6.1 联系人contact

这部分存储了微信里的联系人信息,包括好友、群聊、企业微信联系人、公众号等信息

biz_info 公众号数据

sql
CREATE TABLE biz_info(
id INTEGER PRIMARY KEY,
username TEXT,
type INTEGER,
accept_type INTEGER,
child_type INTEGER,
version INTEGER,
external_info TEXT,
brand_info TEXT,
brand_icon_url TEXT,
brand_list TEXT,
brand_flag INTEGER,
belong TEXT,
ext_buffer BLOB
)
名称 类型 说明
id INTEGER 跟name2id表的序号相对应,可以唯一确定一个联系人
username TEXT 原始wxid,可以唯一确定一个联系人
type INTEGER 公众号类型,1是公众号,0是订阅号
accept_type INTEGER
child_type INTEGER
version INTEGER
external_info TEXT 存储了公众号的详细信息,公众号底部菜单信息
brand_info TEXT
brand_icon_url TEXT logo链接
brand_list TEXT
brand_flag INTEGER
belong TEXT
ext_buffer BLOB

biz_session_feed 公众号消息会话

sql
CREATE TABLE biz_session_feeds(
username TEXT,
showname TEXT,
desc TEXT, type INTEGER,
unread_count INTEGER,
update_time INTEGER,
create_time INTEGER,
biz_attr_version INTEGER
)
名称 类型 说明
username TEXT
showname TEXT 屏幕上显示的名字
desc TEXT 消息描述信息
type INTEGER 消息类型
unread_count INTEGER 未读消息的个数
update_time INTEGER 更新时间
create_time INTEGER 创建时间
biz_attr_version INTEGER

chat_room 群聊

sql
CREATE TABLE chat_room(
id INTEGER PRIMARY KEY,
username TEXT,
owner TEXT,
ext_buffer BLOB
)
名称 类型 说明
id INTEGER 跟name2id表的序号相对应
username TEXT 群聊的username
owner TEXT 群主的username
ext_buffer BLOB(protobuf) 存储了群成员的username和群昵称
python
syntax = "proto3";
package app.protobuf;
option go_package=".;proto";

message ChatRoomData {
message ChatRoomMember {
string wxID = 1;
string displayName = 2;
int32 state = 3;
}
repeated ChatRoomMember members = 1;
int32 field_2 = 2;
int32 field_3 = 3;
int32 field_4 = 4;
int32 room_capacity = 5;
int32 field_6 = 6;
int64 field_7 = 7;
int64 field_8 = 8;
}

chat_room_info_detail 群聊信息

sql
CREATE TABLE chat_room_info_detail(
room_id_ INTEGER PRIMARY KEY,
username_ TEXT,
announcement_ TEXT,
announcement_editor_ TEXT,
announcement_publish_time_ INTEGER,
chat_room_status_ INTEGER,
room_top_msg_closed_id_list_text_ TEXT,
xml_announcement_ TEXT,
ext_buffer_ BLOB
)
名称 类型 说明
room_id_ INTEGER 跟name2id表的序号相对应
username_ TEXT 群聊的username
announcement_ TEXT 存文本形式的群公告
announcement_editor_ TEXT 群功能编辑者的username
announcement_publish_time_ INTEGER 发布时间
chat_room_status_ INTEGER
room_top_msg_closed_id_list_text_ TEXT
xml_announcement_ TEXT xml格式的群功能,可以解析出来更多信息,如图片、文件之类的信息
ext_buffer_

chatroom_member 群成员表

sql
CREATE TABLE chatroom_member(
room_id INTEGER,
member_id INTEGER,
CONSTRAINT room_member UNIQUE(room_id, member_id)
)
名称 类型 说明
room_id INTEGER 群聊id,跟name2id表的序号相对应
member_id INTEGER 群员id,跟name2id表的序号相对应

contact 联系人表

contact存储了所有的联系人,包括好友、群聊、公众号、企业微信联系人

sql
CREATE TABLE contact(
id INTEGER PRIMARY KEY,
username TEXT,
local_type INTEGER,
alias TEXT,
encrypt_username TEXT,
flag INTEGER,
delete_flag INTEGER,
verify_flag INTEGER,
remark TEXT,
remark_quan_pin TEXT,
remark_pin_yin_initial TEXT,
nick_name TEXT,
pin_yin_initial TEXT,
quan_pin TEXT,
big_head_url TEXT,
small_head_url TEXT,
head_img_md5 TEXT,
chat_room_notify INTEGER,
is_in_chat_room INTEGER,
description TEXT,
extra_buffer BLOB,
chat_room_type INTEGER
)
名称 类型 说明
id INTEGER 序号,跟name2id表的序号相对应
user naI He TEXT 联系人的wixd
local_type INTEGER 类型
alias TEXT 微信里显示的微信号
encrypt_username TEXT
flag INTEGER 好像是联系人的真正类型,跟3.0数据库的Type对应
delete_flag INTEGER
verify_flag INTEGER
remark TEXT 备注名
remark_quan_pin TEXT 备注名全拼
remark_pin_yin_initial TEXT 备注名拼音缩写
nick_name TEXT 微信昵称
pin_yin_initial TEXT 微信昵称拼音缩写
quan_pin TEXT 微信昵称全拼
big_head_url TEXT 好友头像大图
small_head_url TEXT 好友头像小图
head_img_md5 TEXT 头像的md5,可以通过head_image.db查询对应的头像
chat_room_notify INTEGER "chat_room_notify" INTEGER
is_in_chat_room INTEGER "is_in_chat_room" INTEGER
description TEXT "description" TEXT
extra_buffer BLOB(protobuf) 存储了好友的详细信息,性别、地区、签名等
chat_room_type INTEGER "chat_room_type" INTEGER

相关信息

local_type说明:

1:通讯录好友(包括公众号、手动添加到通讯录的群聊)

2:未添加到通讯录的群聊

3:群中的陌生人

5:企业微信好友

6:群聊中的陌生企业微信好友

相关信息

flag说明:

flag要转化为二进制,每一位代表不同的含义

第7位:代表是否是星标好友

第12位: 代表是否是置顶好友

第17位:代表是否屏蔽对方的朋友圈

第24位:代表是否是仅聊天好友

extra_buffer 对应的 .proto 文件描述

text
syntax = "proto3";

package example;

// 顶级消息定义
message ContactInfo {
// varint 类型字段,根据数值范围选用 uint32 或 uint64
uint32 gender = 2; // 性别:1 男 2:女 0:未知
uint32 field3 = 3;
string signature = 4; // 自助者天助!!!
string country = 5; // CN
string province = 6; // Shaanxi
string city = 7; // Xi'an
uint32 field8 = 8;
string field9 = 9;
uint32 field10 = 10; // 4294967295
uint32 field11 = 11;
uint32 field12 = 12;

// 修改后的嵌套消息,对应 JSON 中 field 14 的数据结构
MessageField14 phone_info = 14;

string field15 = 15;
uint32 field16 = 16;
uint32 field17 = 17;
uint32 field18 = 18;
uint32 field19 = 19;
string field20 = 20;
string field21 = 21;
uint32 field22 = 22;
uint32 field23 = 23;
uint32 field24 = 24;
string field25 = 25;
string field26 = 26;

// 嵌套消息,朋友圈背景
MessageField27 moments_info = 27;

string field28 = 28;
string field29 = 29;
string label_list = 30;
string field31 = 31;
string field32 = 32;

// 嵌套消息,对应 JSON 中 field 33 的 length_delimited 数据
MessageField33 field33 = 33;

string field34 = 34;
string field35 = 35;
MessageField36 field36 = 36;
uint32 field37 = 37;
uint32 field38 = 38; // 4294967295
}

// 定义 field14 对应的嵌套消息
// 修改后的嵌套消息,用于 field 14
message MessageField14 {
uint32 field1 = 1; // varint 类型字段,存储数字
repeated MessageField14_Result2 field2 = 2; // 这是一个 length_delimited 类型的字段,包含多个结果
}


message MessageField14_Result2 {
string phone_numer = 1; // string 类型字段,存储电话号码
}

// 定义 field27 对应的嵌套消息

message MessageField27 {
uint32 field1 = 1;
string background_url = 2; // 图片 URL
uint64 field3 = 3; // 14588734692813845087(大数,用 uint64)
uint32 field4 = 4; // 6785
uint32 field5 = 5; // 4320
}

// 定义 field33 对应的嵌套消息

message MessageField33 {
string field1 = 1;
}

message MessageField36 {
MessageField36_Result results = 1;
}

message MessageField36_Result {
string field1 = 1;
}

6.2 头像 head_image

head_image 表

sql
CREATE TABLE head_image(
username TEXT PRIMARY KEY,
md5 TEXT,
image_buffer BLOB,
update_time INTEGER
)
名称 类型 说明
username INTEGER wxid
md5 TEXT 头像的md5
image_buffer BLOB 头像缩略图的二进制数据
update_time INTEGER 更新时间

6.3 聊天记录 message

message_x.db 聊天数据

Msg_md5表,每个联系人都单独建了一个表,命名规则:Msg_{md5(wxid)} , 对联系人的wxid用md5编码可以得到聊天记录所在的表

sql
CREATE TABLE Msg_xxxx(
local_id INTEGER PRIMARY KEY AUTOINCREMENT,
server_id INTEGER,
local_type INTEGER,
sort_seq INTEGER,
real_sender_id INTEGER,
create_time INTEGER,
status INTEGER,
upload_status INTEGER,
download_status INTEGER,
server_seq INTEGER,
origin_source INTEGER,
source TEXT,
message_content TEXT,
compress_content TEXT,
packed_info_data BLOB,
WCDB_CT_message_content INTEGER DEFAULT NULL,
WCDB_CT_source INTEGER DEFAULT NULL
)
名称 类型 说明
local_id INTEGER 自增id
server_id INTEGER 服务端的id,每条消息的唯一id
local_type INTEGER 消息类型
sort_seq INTEGER 用于排序的字段,时间戳*100,然后从0计数,如果某一秒发送了多条消息,可以通过该字段区分先后顺序
real_sender_id INTEGER 发送者id,可以通过Name2Id表获取实际的发送者username
create_time INTEGER 秒级时间戳
status INTEGER 消息状态
upload_status INTEGER 上传状态
download_status INTEGER 下载状态
server_SEQ INTEGER 服务端接收的顺序id
origin_source INTEGER
message_content TEXT 消息的实际内容,local_type是1时message_content是文本数据,其他类型都是 Zstandard 压缩后的xml二进制数据
compress_content TEXT 压缩后的内容
packed_info_data BLOB protobuf数据,存储了某些类型的聊天记录的信息(图片文件名、语音转文字记录、合并转发的聊天记录文件名)
WCD B_CT_message_content INTEGER
WCD B_CT_source INTEGER

local_type字段说明

local_type 类型 说明
1 文本
3 图片
34 语音
42 好友名片 包含好友名片和公众号名片
43 视频
47 表情包
48 发送的位置信息
50 音视频通话
66 企业微信好友名片
10000 系统消息 撤销信息,进群通知等
25769803825 文件
21474836529 分享链接
292057776177 分享链接
4294967345 分享链接
326417514545 分享链接
17179869233 分享链接
244813135921 引用消息
81604378673 合并转发的聊天记录
8594229559345 红包
219043332145 视频号
141733920817 小程序
154618822705 小程序
103079215153 转发的收藏笔记
266287972401 拍一拍
12884901937 音乐分享

packed_info_data 字段存储了某些聊天记录的附加信息,例如语音转文字,图片名(2025年3月微信测试版修改了img命名方式才有了这个东西),合并转发的聊天记录文件夹名

  • 图片
    text
    syntax = "proto3";
    // 2025年3月微信测试版修改了img命名方式才有了这个东西
    message PackedInfoDataImg {
    int32 field1 = 1;
    int32 field2 = 2;
    string filename = 3;
    }
  • 语音
    text
    syntax = "proto3";

    package example;

    // 顶级消息定义
    message PackedInfoData {
    // varint 类型字段,根据数值范围选用 uint32 或 uint64
    uint32 field1 = 1;
    uint32 field2 = 2;
    MessageField5 info = 5;
    }

    // 定义 field14 对应的嵌套消息
    // 修改后的嵌套消息,用于 field 14
    message MessageField5 {
    uint32 field1 = 1;
    string audioTxt = 2; // 语音转文字结果
    }
  • 合并转发的聊天记录
    text
    syntax = "proto3";

    message PackedInfoData {
    int32 field1 = 1;
    int32 field2 = 2;
    NestedMessage field7 = 7;
    AnotherNestedMessage info = 9;
    }

    message NestedMessage {
    SubMessage1 field1 = 1;
    SubMessage2 field2 = 2;
    string field3 = 3;
    }

    message SubMessage1 {
    int32 field1 = 1;
    string field2 = 2;
    }

    message SubMessage2 {
    string field1 = 1;
    string field2 = 2;
    string field3 = 3;
    }

    message AnotherNestedMessage {
    string dir = 1;
    }

biz_message_x.db 公众号消息

公众号消息记录,结构同message_x.db

media_0.db 语音数据

sql
CREATE TABLE VoiceInfo(
chat_name_id INTEGER,
create_time INTEGER,
local_id INTEGER,
svr_id INTEGER,
voice_data BLOB,
data_index TEXT DEFAULT '0'
)
名称 类型 说明
chat_name_id INTEGER 结合Name2Id表可以查出发送者的wxid
create_time INTEGER 创建秒级时间戳
local_id INTEGER 对应message数据库的local_id
svr_id INTEGER 对应message数据库的server_id
voice_data BLOB 语音silk的二进制数据
data_index TEXT

video_hardlink_info_v3

sql
CREATE TABLE video_hardlink_info_v3(
md5_hash INTEGER,
md5 TEXT, type INTEGER,
file_name TEXT,
file_size INTEGER,
modify_time INTEGER,
dir1 INTEGER,
dir2 INTEGER,
_rowid_ INTEGER PRIMARY KEY ASC,
extra_buffer BLOB
)
名称 类型 说明
md5hash INTEGER
md5 TEXT 文件的md5
type INTEGER 文件类型:3:正常的文件,5:合并转发的聊天记录里的文件
file_name INTEGER 文件名
file_size INTEGER 文件大小(B)
modeify_time INTEGER 修改时间
dir1 INTEGER 文件夹1的id,dir2id表对应第id行usernamez字段为dir1的文件夹名
dir2 INTEGER 文件夹2的id,type为3时dir2值为0,只需要从msg/video/dir1文件夹中找就行了
_rowid INTEGER
extra_buffer BLOB protocbuf数据,存储另一个文件夹dir3,type为5时有效。.proto 文件描述:syntax = "proto3"; package example; message FileInfoData { string dir3 = 1; uint32 file_size = 2; }
sql
select file_size,type,file_name,dir2id.username,_rowid_,modify_time
from video_hardlink_info_v3
join dir2id on dir2id.rowid = dir1
where md5=10086;

合并转发的聊天记录

  • 文件路径:
    • msg\attach\9e20f478899dc29eb19741386f9343c8\2025-03\Rec\409af365664e0c0d\F\5\xxx.pdf
  • 图片路径:
    • msg\attach\9e20f478899dc29eb19741386f9343c8\2025-03\Rec\409af365664e0c0d\Img\5
  • 视频路径:
    • msg\attach\9e20f478899dc29eb19741386f9343c8\2025-03\Rec\409af365664e0c0d\V\5.mp4

9e20f478899dc29eb19741386f9343c8 是wxid的md5加密,409af365664e0c0d 是上述 extra_buffer 字段里的 dir3

文件夹最后的5代表的该文件是合并转发的聊天记录第5条消息,如果存在嵌套的合并转发的聊天记录,则依次递归的添加上一层的文件名后缀,例如:合并转发的聊天记录有两层

plain
0:文件(文件夹名为0)

1:图片 (文件名为1)

2:合并转发的聊天记录

0:文件(文件夹名为2_0)

1:图片(文件名为2_1)

2:视频(文件名为2_2.mp4)
python
def parser_merged(merged_messages, level):
for index, inner_msg in enumerate(merged_messages):
wxid_md5 = hashlib.md5(username.encode("utf-8")).hexdigest()
if inner_msg.type == MessageType.Image:
inner_msg.path = os.path.join(
'msg', 'attach', wxid_md5, month,
'Rec', dir0, 'Img', f"{level}{'_' if level else ''}{index}")
inner_msg.thumb_path = os.path.join(
'msg', 'attach', wxid_md5, month,
'Rec', dir0, 'Img', f"{level}{'_' if level else ''}{index}_t")
elif inner_msg.type == MessageType.Video:
inner_msg.path = os.path.join(
'msg', 'attach',wxid_md5,month,
'Rec', dir0, 'V', f"{level}{'_' if level else ''}{index}.mp4")
elif inner_msg.type == MessageType.File:
inner_msg.path = os.path.join(
'msg', 'attach',wxid_md5,month,
'Rec', dir0, 'F', f"{level}{'_' if level else ''}{index}",
inner_msg.file_name)
elif inner_msg.type == MessageType.MergedMessages:
parser_merged(
inner_msg.messages,
f'{index}' if not level else f'{level}_{index}'
)

parser_merged(msg.messages, '')

6.2 会话窗口 session

session.db 存储了微信聊天界面显示的会话窗口信息

sql
CREATE TABLE SessionTable(
username TEXT PRIMARY KEY,
type INTEGER,
unread_count INTEGER,
unread_first_msg_srv_id INTEGER,
is_hidden INTEGER,
summary TEXT,
draft TEXT,
status INTEGER,
last_timestamp INTEGER,
sort_timestamp INTEGER,
last_clear_unread_timestamp INTEGER,
last_msg_locald_id INTEGER,
last_msg_type INTEGER,
last_msg_sub_type INTEGER,
last_msg_sender TEXT,
last_sender_display_name TEXT,
last_msg_ext_type INTEGER
)

6.3 表情包 emoticon

emoticon.db 存储了用户收藏的表情包信息

sql
CREATE TABLE kNonStoreEmoticonTable(
type INTEGER,
md5 TEXT,
caption TEXT,
product_id TEXT,
aes_key TEXT,
thumb_url TEXT,
tp_url TEXT,
auth_key TEXT,
cdn_url TEXT,
extern_url TEXT,
extern_md5 TEXT,
encrypt_url TEXT
)
名称 类型 说明
type INTEGER
md5 TEXT 表情包的md5,可以从message数据库里获取
caption TEXT
product_id TEXT
aes_key TEXT
thumb_url TEXT 表情包缩略图url(可以直接引用)
cdn_url TEXT 表情包url(可以直接引用)
extern_url TEXT
extern_md5 TEXT
encrypt_url TEXT

6.4 朋友圈 sns

SnsTimeLine 表存储了每条朋友圈的详细信息

sql
CREATE TABLE SnsTimeLine(
tid INTEGER PRIMARY KEY DESC,
user_name TEXT,
content TEXT
)