为 Homepage 自制 MT Photos 小组件:从抓包到部署全记录
背景
Homepage 是目前最流行的自托管仪表盘之一,内置支持 100+ 服务的 Widget。可惜 MT Photos 不在其列——Homepage 没有原生的 MT Photos Widget。
好在 Homepage 提供了 customapi 类型的 Widget,只要你的服务能暴露一个返回 JSON 的 HTTP 端点,就能把任意数据展示在仪表盘上。问题在于:MT Photos 的 API 登录用的是 RSA 公钥加密,Homepage 的 customapi 无法处理这种复杂认证。所以需要一个中间代理层。
第一步:抓包,摸清 MT Photos API
MT Photos 完全没有公开 API 文档。直接在浏览器里打开 http://192.168.50.88:8063/,F12 看 Network 面板。
认证流程
MT Photos 的登录分三步:
1. 获取 RSA 公钥
POST /auth/rsa
Body: {}
返回:
{
"publicKey": "-----BEGIN RSA PUBLIC KEY-----\n...\n-----END RSA PUBLIC KEY-----",
"ver": 2
}
ver=2 意味着后续加密走 V2 流程,用户名固定传 __MT_RSA_ENC_V2。
2. RSA 加密凭据
raw = '{"u":"用户名","p":"密码"}'
encoded = urllib.parse.quote(raw) # encodeURIComponent
ciphertext = rsa_public_encrypt(encoded)
3. 登录拿 Token
POST /auth/login
Body: {
"username": "__MT_RSA_ENC_V2",
"password": "",
"otp": ""
}
返回 access_token,后续所有 API 通过 Header jwt: 鉴权(不是常规的 Authorization: Bearer)。
我们需要的数据接口
| 接口 | 方法 | 用途 |
|---|---|---|
/gateway/timeline |
GET | 按月份的照片统计,汇总可得总照片数 |
/gateway/myGalleryList |
GET | 相册列表,取长度即相册数 |
存储空间是个遗憾——翻遍了 100+ 个端点,MT Photos v1.55.0 没有磁盘用量 API。最后用 shutil.disk_usage() 直接读取数据目录的磁盘信息来兜底。
第二步:写代理服务
架构很简单:
Homepage ──GET /api/stats──> Python Proxy (:8765) ──jwt──> MT Photos (:8063)
│
└── shutil.disk_usage() 读磁盘
代理负责:RSA 登录 → 缓存 token → 定时刷新 → 暴露 /api/stats。
选型踩坑
第一版用了 Flask,结果在 Python 3.14 上直接炸了:
ImportError: cannot import name 'url_quote' from 'werkzeug.urls'
Python 3.14 太新,Werkzeug 移除了 url_quote 而 Flask 还在引用。与其追版本兼容,不如直接用 stdlib。HTTP 服务的核心无非就是路由 + JSON 响应,http.server 完全够用。
最终代码
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse
import json, time, threading, os, shutil, base64
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
# ... RSA 加密、登录、token 刷新逻辑 ...
class Handler(BaseHTTPRequestHandler):
def _send_json(self, code, data):
body = json.dumps(data, ensure_ascii=False).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if urlparse(self.path).path == "/api/stats":
try:
stats = get_stats() # 调 MT Photos API 汇总数据
self._send_json(200, stats)
except Exception as e:
self._send_json(500, {"error": str(e)})
核心依赖只有两个:requests 发 HTTP,pycryptodome 做 RSA。
返回的数据结构:
{
"photo_count": 20749,
"gallery_count": 2,
"storage_total_gb": 450.0,
"storage_used_gb": 312.5,
"storage_free_gb": 137.5,
"storage_usage_pct": 69.4
}
第三步:配置 Homepage
编辑 Homepage 的 services.yaml:
- Media:
- MT Photos:
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@main/png/mt-photos.png
href: http://192.168.50.88:8063
description: Photo Management
widget:
type: customapi
url: http://PROXY_IP:8765/api/stats
method: GET
refreshInterval: 60000
mappings:
- field: photo_count
label: Photos
format: number
- field: gallery_count
label: Albums
format: number
- field: storage_used_gb
label: Used
format: float
suffix: " GB"
mappings 里的 field 对应代理返回的 JSON 字段,label 是 Homepage 卡片上显示的文字。
第四步:部署
在代理所在的 Linux 机器上:
cd /home/mtphotos-proxy
pip install -r requirements.txt
python main.py
推荐配上 systemd 实现开机自启:
[Unit]
Description=MT Photos Proxy for Homepage
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/mtphotos-proxy/main.py
WorkingDirectory=/home/mtphotos-proxy
Restart=always
[Install]
WantedBy=multi-user.target
效果
Homepage 仪表盘上多了一张 MT Photos 卡片,显示照片总数、相册数、存储用量,每分钟自动刷新。点卡片直接跳转 MT Photos。
总结
整个过程做下来,核心工作量其实不在写代码,而在搞清楚 API 的认证方式。MT Photos 这种 RSA 加密登录在自托管应用里不太常见,一旦搞明白,后面就是标准的"代理 + 数据映射"套路。
同样的思路也适用于其他 Homepage 尚未适配的自托管服务——只要能用 HTTP 拿到数据,就能用 customapi 展示。
本文的完整代码已开源在 GitHub: [链接待补充]
(内容由AI生成,仅供参考)
文章评论