Contents

Matrix, QQ and Wechat

背景

对于我这种潜水党来说, 查看多个聊天工具的消息一直是个头大的问题, 需要安装多个客户端, 跨平台的体验不佳, 以及都懒得吐嘈的微信聊天记录同步备份等, 很是困扰.

后来发现了 EH Forwarder Bot 后真的是打开了新世界的大门, 原来还可以这样子玩的, 直接通过 Telegram 来收发 QQ 和微信的消息!

当时的 Plugin 还是通过 WebQQ 和微信网页版协议来实现的, 局限蛮多, 不少消息格式都不支持, 所以干脆 Hook 了这俩的 Mac 应用对接起来, 扔在我的老 Macbook 上7x24小时跑着, 就这么用了好多年…

当然, 这个方案对我来说, 还是有一些缺点的, 主要是受限于 Telegram 的机制, 所有消息的收发都是通过 Bot 进行的, 导致会将所有的消息混杂在一起, 虽然可以通过绑定群的方式来规避掉一些, 但类似群内各人的消息还是同个 Bot 发出来的, 还是不够完美.

月初的时候看了下 Matrix 的文档, 发现它的 Application Services 机制非常强大, 可以方便实现到各外部系统的桥接, 而这个就是我想要的! 于是照着目前实现的比较完善的 Mautrix 相关的代码, 整出了 matrix-qqmatrix-wechat.

这次的话, 就不走 Mac Hook 的方式了, QQ 这边选的是 MiraiGo, 微信则是走的 Win 的 ComWeChatRobot.

最终的效果如下图:

/images/matrix-qq-wechat.png

部署

因为是个人使用的, 不想整的太复杂, 简单和稳定就好, Matrix 服务端选的 Synapse, 代理是 Caddy2, 通过 Dokcer Compose 部署.

下面的步骤是以服务器名 matrix.example.com 搭建, 通过 Delegation 机制, 用户和房间的 ID 都为 *:example.com 的形式.

先放一下最终的目录结构 (完整版 docker-compose.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.
├── caddy
│   ├── Caddyfile
│   ├── config
│   └── data
├── docker-compose.yml
├── matrix-qq
│   ├── config.yaml
│   └── registration.yaml
├── matrix-wechat
│   ├── config.yaml
│   └── registration.yaml
├── postgres
│   ├── create_db.sh
│   └── data
└── synapse
    ├── example.com.log.config
    ├── example.com.signing.key
    ├── homeserver.yaml
    ├── logs
    ├── media_store
    ├── qq-registration.yaml
    ├── shared_secret_authenticator.py
    ├── uploads
    └── wechat-registration.yaml

Postgres

数据库选择 Postgres, 默认的 SQLite 性能挺差的…

建立对应的目录和文件

1
2
3
mkdir -p postgres/data
touch postgres/create_db.sh
touch docker-compose.yml

然后修改以下文件的内容

postgres/create_db.sh

1
2
3
4
5
#!/bin/sh

createdb -U synapse_user -O synapse_user --encoding=UTF8 --locale=C --template=template0 synapse
createdb -U synapse_user -O synapse_user matrix_qq
createdb -U synapse_user -O synapse_user matrix_wechat

docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: "3.4"
services:
  postgres:
    hostname: postgres
    container_name: postgres
    image: postgres:14
    restart: always
    environment:
      TZ: Asia/Shanghai
      POSTGRES_USER: synapse_user
      POSTGRES_PASSWORD: hello
    volumes:
      - ./postgres/create_db.sh:/docker-entrypoint-initdb.d/20-create_db.sh
      - ./postgres/data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U synapse_user"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - matrix-net

networks:
  matrix-net:
    attachable: true

执行 docker-compose up 启动看看是否正常 (顺带初始化库).

Synapse

建立对应的目录

1
2
3
mkdir -p synapse/logs
mkdir synapse/media_store
mkdir synapse/uploads

生成初始化配置

1
2
3
4
5
docker run --rm \
	-v `pwd`/synapse:/data \
	-e SYNAPSE_SERVER_NAME=example.com \
	-e SYNAPSE_REPORT_STATS=no \
	matrixdotorg/synapse:latest generate

将对应目录的属主改成991

1
sudo chown 991:991 synapse/logs synapse/media_store synapse/uploads

然后修改以下文件的内容

synapse/example.com.log.config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: 1

formatters:
  precise:
    format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'

handlers:
  console:
    class: logging.StreamHandler
    formatter: precise
  file:
    class: logging.handlers.TimedRotatingFileHandler
    formatter: precise
    filename: /data/logs/homeserver.log
    when: midnight
    backupCount: 7
    encoding: utf8
  buffer:
    class: synapse.logging.handlers.PeriodicallyFlushingMemoryHandler
    target: file
    capacity: 10
    flushLevel: 30
    period: 5

loggers:
  synapse.storage.SQL:
    level: INFO

root:
  level: INFO
  handlers: [buffer]

disable_existing_loggers: false

synapse/homeserver.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
server_name: "example.com"
public_baseurl: "https://matrix.example.com"
serve_server_wellknown: true
pid_file: /data/homeserver.pid

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    resources:
      - names: [client, federation]
        compress: false
database:
  name: psycopg2
  txn_limit: 10000
  args:
    user: synapse_user
    password: hello
    database: synapse
    host: postgres
    port: 5432
    cp_min: 5
    cp_max: 10

log_config: "/data/example.com.log.config"
media_store_path: /data/media_store
uploads_path: /data/uploads

registration_shared_secret: "<your shared srcret>"
report_stats: false
macaroon_secret_key: "<your macaroon key>"
form_secret: "<your from secret>"
signing_key_path: "/data/example.com.signing.key"
trusted_key_servers:
  - server_name: "matrix.org"

url_preview_enabled: true
url_preview_ip_range_blacklist:
  - '127.0.0.0/8'
  - '10.0.0.0/8'
  - '172.16.0.0/12'
  - '192.168.0.0/16'
  - '100.64.0.0/10'
  - '192.0.0.0/24'
  - '169.254.0.0/16'
  - '192.88.99.0/24'
  - '198.18.0.0/15'
  - '192.0.2.0/24'
  - '198.51.100.0/24'
  - '203.0.113.0/24'
  - '224.0.0.0/4'
  - '::1/128'
  - 'fe80::/10'
  - 'fc00::/7'
  - '2001:db8::/32'
  - 'ff00::/8'
  - 'fec0::/10'

url_preview_url_blacklist:
  - scheme: 'http'
  - netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'

docker-compose.yml 添加以下内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
synapse:
    hostname: synapse
    container_name: synapse
    image: matrixdotorg/synapse:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      TZ: Asia/Shanghai
    volumes:
      - ./synapse:/data
    healthcheck:
      test: ["CMD", "curl", "-fSs", "http://localhost:8008/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 5s
    networks:
      - matrix-net

继续 docker-compose up 启动看看.

Warning
请保管好 example.com.signing.key 这个文件

Caddy

建立对应的目录和文件

1
2
3
mkdir -p caddy/config
mkdir caddy/data
touch caddy/Caddyfile

然后修改以下文件的内容

caddy/Caddyfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
	email admin@example.com
}

example.com {
	header /.well-known/matrix/* Content-Type application/json
	header /.well-known/matrix/* Access-Control-Allow-Origin *

	respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
	respond /.well-known/matrix/client `{"m.homeserver":{"base_url":"https://matrix.example.com"}}`
}

matrix.example.com {
	log {
		level INFO
		output file /data/access.log {
			roll_size 100MB
			roll_local_time
			roll_keep 10
		}
	}

	reverse_proxy /_matrix/* synapse:8008
	reverse_proxy /_synapse/client/* synapse:8008
}

doocker-compose.yml 添加以下内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
caddy:
    image: caddy:2
    hostname: caddy
    container_name: caddy
    ports:
      - 80:80
      - 443:443
    environment:
      TZ: Asia/Shanghai
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/config:/config
      - ./caddy/data:/data
    networks:
      - matrix-net

运行 docker-compose up 看看.

访问 https://matrix.example.com/_matrix/federation/v1/version 看能否有返回.

然后到 https://federationtester.matrix.org/ 输入服务器名 (比如 exmaple.com) 看看 federation 是否正常, 绿色就可以.

在服务器上注册个帐号

1
docker exec -it synapse register_new_matrix_user http://localhost:8008 -c /data/homeserver.yaml

通过 https://app.element.io/ 登录看看, 服务器选择 matrix.example.com, 用新建的用户登录.

如果没啥问题, 那么一个可用的 Matrix 服务就好了, 接下来就是 QQ 和微信的桥接了.

Shared Secret Authenticator

先把 Shared Secret Authenticator 给启用了, 这样 double puppeting 就不需要手动绑定 Matrix 帐号

1
2
wget https://raw.githubusercontent.com/devture/matrix-synapse-shared-secret-auth/master/shared_secret_authenticator.py -O synapse/shared_secret_authenticator.py
sudo chown 991:991 synapse/shared_secret_authenticator.py

编辑 docker-compose.yml, 在 synapse 的 volumes 加入

1
  - ./synapse/shared_secret_authenticator.py:/usr/local/lib/python3.9/site-packages/shared_secret_authenticator.py

synapse/homeserver.yaml 添加以下内容 (这里的 Key 后续在桥接配置的时候要用到)

1
2
3
4
5
modules:
  - module: shared_secret_authenticator.SharedSecretAuthProvider
    config:
      shared_secret: "<your double puppeting key>"
      m_login_password_support_enabled: true

老规矩, docker-compose up 启动起来看看是否一切正常

Matrix-QQ

执行 mkdir matrix-qq 创建目录

生成初始的 matrix-qq/config.yaml

1
docker run --rm -v `pwd`/matrix-qq:/data:z lxduo/matrix-qq:latest

然后这个文件主要修改的内容是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
homeserver:
    address: http://synapse:8008
    domain: example.com
appservice:
	address: http://matrix-qq:17777

	database:
		uri: postgres://synapse_user:hello@postgres/matrix_qq?sslmode=disable
bridge:
	double_puppet_server_map:
		example.com: https://matrix.example.com

	login_shared_secret_map:
		example.com: "<your double puppeting key>"

	permissions:
	    "example.com": user
		"@admin:example.com": admin

再运行一次生成注册文件

1
docker run --rm -v `pwd`/matrix-qq:/data:z lxduo/matrix-qq:latest

把注册文件拷到 synpase 目录, 同时修改属主

1
2
cp matrix-qq/registration.yaml synapse/qq-registration.yaml
sudo chown 991:991 synapse/qq-registration.yaml

编辑 synapse/homeserver.yaml, 加入

1
2
app_service_config_files:
  - /data/qq-registration.yaml

编辑 docker-compose.yml, 加入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
matrix-qq:
    hostname: matrix-qq
    container_name: matrix-qq
    image: lxduo/matrix-qq:latest
    restart: unless-stopped
    depends_on:
      synapse:
        condition: service_healthy
    volumes:
      - ./matrix-qq:/data
    networks:
      - matrix-net  

启动 docker-compose up

新建和 @qqbot:example.com 的聊天, 输入 help 查看使用帮助

Warning

当密码登录不工作的时候, 可以考虑扫码登录, 或者将 go-cqhttp 或者 MiraiGo-Template 生成的 device.json 和 session.token 各做个 base64 然后用 token 方式登录…

参见: https://github.com/Mrs4s/go-cqhttp/issues/1469

Matrix-Wechat

执行 mkdir matrix-wechat 创建目录

生成初始的 matrix-wechat/config.yaml

1
docker run --rm -v `pwd`/matrix-wechat:/data:z lxduo/matrix-wechat:2

然后这个文件主要修改的内容是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
homeserver:
    address: http://synapse:8008
    domain: example.com
appservice:
	address: http://matrix-wechat:17778

	database:
	    uri: postgres://synapse_user:hello@postgres/matrix_wechat?sslmode=disable
bridge:
	listen_secret: "<your wechat agent key>"

	double_puppet_server_map:
		example.com: https://matrix.example.com

	login_shared_secret_map:
		example.com: "<your double puppeting key>"

	permissions:
		"example.com": user
		"@admin:example.com": admin

再运行一次生成注册文件

1
docker run --rm -v `pwd`/matrix-wechat:/data:z lxduo/matrix-wechat:2

编辑 matrix-wechat/registration.yaml, 修改里面的正则

1
2
3
4
5
6
namespaces:
    users:
        - regex: ^@wechatbot:example\.com$
          exclusive: true
        - regex: ^@_wechat_.+:example\.com$
          exclusive: true

把注册文件拷到 synpase 目录, 同时修改属主

1
2
cp matrix-wechat/registration.yaml synapse/wechat-registration.yaml
sudo chown 991:991 synapse/wechat-registration.yaml

编辑 synapse/homeserver.yaml, 在 app_service_config_files 里加入

1
  - /data/wechat-registration.yaml

编辑 docker-compose.yml, 加入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
matrix-wechat:
    hostname: matrix-wechat
    container_name: matrix-wechat
    image: lxduo/matrix-wechat:2
    restart: unless-stopped
    depends_on:
      synapse:
        condition: service_healthy
    volumes:
      - ./matrix-wechat:/data
    networks:
      - matrix-net  

编辑 caddy/Caddyfile, 加入

1
    reverse_proxy /_wechat/* matrix-wechat:20002

启动 docker-compose up

嗯, 还没完… 这里还需要有个 Agent 来配合…

Matrix-Wechat-Agent

可以用通过 Docoker 部署, 不过毕竟是 Wine 来运行的, 稳定性一般, 也可以用 Windows 的主机来运行

Linux 主机

执行 mkdir matrix-wechat-agent 创建目录

新建 matrix-wechat-agent/configure.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
wechat:
  version: 3.8.1.26
  listen_port: 22222
  init_timeout: 10s
  request_timeout: 30s

service:
  addr: ws://matrix-wechat:20002
  secret: "<your wechat agent key>"
  ping_interval: 30s

log:
  level: info

编辑 docker-compose.yml, 加入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  matrix-wechat-agent:
    hostname: matrix-wechat-agent
    container_name: matrix-wechat-agent
    image: lxduo/matrix-wechat-agent:latest
    restart: unless-stopped
    depends_on:
      - matrix-wechat
    environment:
      TZ: Asia/Shanghai
    volumes:
      - ./matrix-wechat-agent/configure.yaml:/home/user/matrix-wechat-agent/configure.yaml
    #shm_size: "1gb"
    #devices:
    #  - /dev/dri:/dev/dri
    #ports:
    #  - 15905:5905
    networks:
      - matrix-net
Tip

如果遇到 Docker Engine 的兼容性问题, 加上以下配置试试

1
2
    security_opt:
      - seccomp:unconfined

如果微信运行的不稳定, 把注释的 shm_size 打开看看

Windows 主机

同上创建一个 configure.yaml 文件

duo/matrix-wechat-agent 下载得到 matrix-wechat-agent.exe

ljc545w/ComWeChatRobot 下载解压得到 SWeChatRobot.dll 和 wxDriver.dll

configure.yaml, matrix-wechat-agent.exeSWeChatRobot.dll 以及 wxDriver.dll 置于同一目录

安装 Visual C++ Redistributable (https://docs.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170)

安装微信 3.7.0.30 版本

最后执行 matrix-wechat-agent.exe

新建和 @wechatbot:example.com 的聊天,输入 help 查看使用帮助