Blog/source/_posts/2021/11/为Matrix加入推送功能.md
2022-03-16 22:24:24 +08:00

7.7 KiB

author title date tags category toc
SouthFox 为 Matrix 加入自定义推送功能 2021-11-22 15:53:18
技术
Matrix
技术 true

Matrix 是一个开源的聊天协议,和 XMPP 一样的联邦制设计也保证了像电子邮件一样的通信便捷。

好的地方很多,但最明显坏处就是推送了,对于即时通信来说,没有推送是非常大的不便……虽然搭建 Matrix 的应用 Synapse 配置文件启用邮箱的话可以启用邮件通知,不过鉴于邮件时效性也不是很高,所以需要个其他的推送手段。(谷歌市场下的 Element 附带了谷歌的消息推送,不过很遗憾在国内处于残废状态。)

所幸 Synapse 自带的 API 可以定义推送规则,定义后帐号收到消息,服务器就可以将消息以 JSON 的形式推送到自定义的地址,来个曲线救国。

Gotify

推送用到的工具为 Gotify ,特点是使用 GO 编写,高效快速,不过缺点就是安卓得常驻后台接收推送,不过至少要比腾讯全家桶吃的电要少。

下载

Releases 处找到对应版本的下载链接,解压到/opt/gotify/

unzip gotify-file -d /opt/gotify/

然后下载配置样板。

mkdir /etc/gotify
wget -O /etc/gotify/config.yml https://raw.githubusercontent.com/gotify/server/master/config.example.yml

编辑 /etc/gotify/config.yml。将 port 改为没有占用的端口, namepass 是管理员的用户名和密码。

Nginx 反代

nginx 的新建一个虚拟主机并在配置文件增加以下内容:

location / {
  proxy_pass         http://localhost:8080;
  rewrite ^/gotify(/.*) $1 break;
  proxy_http_version 1.1;

  # Ensuring it can use websockets
  proxy_set_header   Upgrade $http_upgrade;
  proxy_set_header   Connection "upgrade";
  proxy_set_header   X-Real-IP $remote_addr;
  proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header   X-Forwarded-Proto http;
  proxy_redirect     http:// $scheme://;
}

proxy_set_header Connection "upgrade"; 这一栏不要漏了……要不然开启 WS 时会报错……

设置服务

新建并编辑 /etc/systemd/system/gotify.service

[Unit]
Description=gotify service
After=network.target
Wants=network.target

[Service]
Type=simple
PIDFile=/run/gotify.pid
WorkingDirectory=/opt/gotify
ExecStart=/opt/gotify/gotify-file
RestartPreventExitStatus=23
Restart=always
RestartSec=10s

[Install]
WantedBy=multi-user.target

然后开启服务并设置成开机启动。

systemctl start gotify
systemctl enable gotify

没有报错的话就可以打开 Nginx 里所设置的域名进入管理页面了,(改密码)之后就可以新建应用了,拿到推送令牌,就可以向服务器设置推送规则了。

设置推送

在官方文档里,通过 /_matrix/client/v3/pushers/set API 就可以设置推送规则了,通过 curl ,或者其他方式向服务器对应地址发送 POST 请求即可使用。

curl 'https://server_url/_matrix/client/r0/pushers/set' -H 'Authorization: Bearer access_token' -H 'Content-Type: application/json' -X POST -d '{"lang": "en","kind": "http","app_display_name": "Gotify","device_display_name": "Gotify","pushkey": "Gotify-PushKey","app_id": "zh.xxx.gotify","data": {"url": "https://Push_url/_matrix/push/v1/notify","format": "full_event"}}'

详细说明请查阅官方 文档 , 其中 access_token 可以在应用里拿到,pushkey 设置成 Gotify-PushKey 的样子是为了后续处理方便……现阶段推送地址必须包含 /_matrix/push/v1/notify 路径,否则会报错,所以不得不进行额外处理了……

接收推送

服务器设置推送规则之后就会向对应地址发送数据了,因为推送地址现阶段必须包含 /_matrix/push/v1/notify 路径,所以不得不再设置一个接收服务然后进行处理了。

接下来使用 pythonflask 框架简单搭一个服务,首先先安装环境。

pip3 install flask
pip3 install flask-apscheduler

然后是代码:

import json
import requests
import datetime

from flask import Flask, jsonify, request
from flask_apscheduler import APScheduler
app = Flask(__name__)
scheduler = APScheduler()

scheduler.start()

def push_notification(push_data):
    if push_data["push_way"] == 'Gotify':
        resp = requests.post(f'https://push.xxx.xxx/message?token={push_data["push_token"]}', json={
        "message": f'「{push_data["sender"]}」发送了消息给你。',
        "priority": 8,
        "title": "新消息!"
        })
        print(resp)
        return
    if push_data["push_way"] == 'Bary':
        try:
            response = requests.post(
                url="https://api.day.app/push",
                headers={
                    "Content-Type": "application/json; charset=utf-8",
                },
                data=json.dumps({
                    "body": f'「{push_data["sender"]}」发送了消息给你。',
                    "device_key": push_data["push_token"],
                    "title": "新消息!",
                    "category": "category",
                    "sound": "minuet.caf",
                })
            )
            print('Response HTTP Status Code: {status_code}'.format(
                status_code=response.status_code))
            print('Response HTTP Response Body: {content}'.format(
                content=response.content))
        except requests.exceptions.RequestException:
            print('HTTP Request failed')
        return


# Our route that will receive the webhooks from Duffel's servers
@app.route('/_matrix/push/v1/notify', methods=['POST'])
def hello_world():
    push_data = {}
    event = None

    try:
        event = request.json
        print(event)

    except:
        return jsonify(success=False)

    # Handle the event
    if event["notification"]["type"] == 'm.room.message' or event["notification"]["type"] == 'm.room.encrypted':
        app_id = event["notification"]["devices"][0]["app_id"]
        push_data["push_way"], push_data["push_token"] = event["notification"]["devices"][0]["pushkey"].split('-')
        push_data["sender"] = event["notification"]["sender_display_name"]

        scheduler.add_job(
            func=push_notification,
            args=(push_data,),
            # trigger="date",
            next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=25),
            id=app_id,
            replace_existing=True,
        )

        print('✅add push job!')

    if event["notification"]["type"] == None and event["notification"]["id"] == '':
        app_id = event["notification"]["devices"][0]["app_id"]
        try:
            scheduler.remove_job(id=app_id)
        except:
            pass
        print('✅remove push!')

    return jsonify(success=True)

然后用命令 FLASK_ENV=development FLASK_RUN_PORT=4567 FLASK_APP=webhook.py flask run 就可启用。再然后再用 Nginx 反代一下 4567 端口就行了,因为 Synapse 默认禁止本机访问,为了规范一点就另外开个站点接收请求吧……

基本逻辑就是接收 JSON 拿到发送者名称和推送令牌,判断是哪项服务,然后等待三十秒推送到对应的应用令牌上去,期间要是判断用户已经阅读了消息,就移除推送作业。

到此无意外的话,应该就能正常推送了,麻烦……希望以后能够有更友好便捷的方式吧。

参考

  1. 在树莓派上部署消息推送软件Gotify