通知与 Webhook
在电脑上接收手机的通知:等待某一条、响应每一条,回复、打开或清除通知,并把它们转发到 Webhook。
本页为译文。如与英文版不一致,以英文版为准。 English
Droidline 可以把手机收到的通知传到电脑。它使用 Android 的通知使用权,能拿到完整的标题和正文(只靠无障碍服务只能看到截短的版本)。
开启
- 在手机上打开 Droidline 的设置标签页,允许通知使用权。
- 选择哪些应用可以把通知发送到电脑。在你选择之前不会发送任何通知,私人消息因此会留在手机上。可以在应用的状态标签页中的发送到电脑的通知下选择应用,也可以在代码中设置:
d.notify_filter(["com.google.android.gm", "com.google.android.apps.messaging"])
print(d.notify_filter()) # 读取当前列表await d.notifyFilter(["com.google.android.gm", "com.google.android.apps.messaging"]);
console.log(await d.notifyFilter()); // 读取当前列表droidline notify_filter com.google.android.gm,com.google.android.apps.messaging
droidline notify_filter等待一条通知
wait_notification 会一直阻塞,直到匹配的通知到达,然后返回这条通知:
n = d.wait_notification("textContains", "verification code", 60)
print(n["package"], n["title"], n["text"])const n = await d.waitNotification("textContains", "verification code", 60);
console.log(n.package, n.title, n.text);droidline wait_notification textContains "verification code" 60第一个参数指定比较什么:text 或 textContains 同时查看标题和正文,title 只看标题,package 匹配应用。同时传入 package=,就只接受来自某一个应用的通知。
等待由服务器完成,所以在此期间,发给同一部手机的其他命令照常执行。它等待的是调用之后到达的通知。要查看已经显示的通知,请使用 notifications() 或 has_notification(by, value)。
响应每一条通知
stop = d.on_notification(package="com.google.android.gm",
callback=lambda n: print(n["title"], n["text"]))
# ... 程序会继续运行;调用 stop() 结束接收const stop = await d.onNotification({ package: "com.google.android.gm" },
(n) => console.log(n.title, n.text));
// ... 调用 stop() 结束接收;在此之前进程会一直运行droidline on_notification --package com.google.android.gmCLI 每收到一条通知就打印一行 JSON,直到你按下 Ctrl+C,因此很容易通过管道交给其他工具处理。一条通知的格式如下:
{"event":"notification","device":"k7d2q9xa","key":"0|com.google.android.gm|1|null|10123","package":"com.google.android.gm","title":"New message","text":"Lunch at 12?","time":1791360000000,"actions":["reply","mark_read"]}
处理通知
每条通知都有一个 key。用它来打开、回复或清除这条通知:
for n in d.notifications():
if "Bank" in n["title"]:
d.notification_click(n["key"]) # 打开通知指向的内容
d.notification_reply(key, "On my way") # 仅适用于带回复操作的通知
d.notification_dismiss(key)for (const n of await d.notifications()) {
if (n.title.includes("Bank")) await d.notificationClick(n.key);
}
await d.notificationReply(key, "On my way");
await d.notificationDismiss(key);KEY=$(droidline --json notifications | jq -r '.value[0].key')
droidline notification_click "$KEY"actions 列出这条通知提供的操作。只有其中包含 reply 时,notification_reply 才有效。
手机离线时
手机无法连接电脑时,会保留最多 500 条最近的通知,并在重新连接后按顺序发送。
Webhook
服务器可以把匹配的通知 POST 到任意 URL,这样其他系统不需要 SDK 也能做出响应。在 config.toml 中为每个 Webhook 添加一节,然后重启 droidline serve:
[[webhooks]]
url = "https://example.com/hooks/droidline"
package = "com.google.android.gm" # 可选
text_contains = "order" # 可选,匹配标题或正文
device = "shelf-01" # 可选
请求体是 JSON 格式的通知,外加 device 和 device_name。每个请求都带有签名,你可以据此确认它来自你的服务器:
X-Droidline-Timestamp: 1791360000
X-Droidline-Signature: sha256=<hex HMAC-SHA256 of timestamp + "." + body>
用 droidline webhook secret 获取签名密钥。在接收端验证签名:
import hashlib, hmac
def valid(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
mac = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest("sha256=" + mac, signature)import { createHmac, timingSafeEqual } from "node:crypto";
function valid(secret, timestamp, body, signature) {
const mac = "sha256=" + createHmac("sha256", secret).update(`${timestamp}.`).update(body).digest("hex");
return mac.length === signature.length && timingSafeEqual(Buffer.from(mac), Buffer.from(signature));
}投递失败时会重试两次,分别在 1 秒和 4 秒之后。
Android 15 及以上的验证码
Android 15 会对非系统自带的应用隐藏通知中的一次性验证码。你仍然能收到通知,但验证码被遮盖了。可以把通知当作信号,然后在应用内读取验证码:
d.wait_notification("package", "com.example.bank", 120)
d.launch("com.example.bank")
code = d.get_text("id", "otp_value")await d.waitNotification("package", "com.example.bank", 120);
await d.launch("com.example.bank");
const code = await d.getText("id", "otp_value");droidline wait_notification package com.example.bank 120
droidline launch com.example.bank
droidline get_text id otp_value