dsh-photo2dsh-by-lan
Verifieddsh-photo2dsh-by-lan · v0.1.3 · MIT · Web UI
DSH 手机照片收件箱:手机(iOS 快捷指令 / Android HTTP Shortcuts)一键把照片经**局域网**传到这台 PC 的可配置目录并弹通知,Agent 直接可读;不依赖云、不用装手机 App、不改 app.asar。配置界面在侧栏 Plugins 本包页面。 | DSH LAN photo inbox: one tap on your phone (iOS Shortcuts / Android HTTP Shortcuts) sends photos over the LAN in
Install
dsh plugin add dsh-photo2dsh-by-lan Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Readme
dsh-photo2dsh-by-lan
English | 中文
手机拍照 → 局域网 → 这台 PC 的文件夹 → Agent 直接可读。
手机端用系统自带能力(iPad/iPhone「快捷指令」、Android「HTTP Shortcuts」)一键把照片 POST 到 PC; 插件负责接收、落盘到你自己指定的目录、弹 Windows 通知。不需要云、不需要飞书/微信、不需要装手机 App。
配置界面就在 DSH 侧栏 插件(Plugins)→ 本包页面里。
亮点
- 手机一键 —— iOS 快捷指令或 Android HTTP Shortcuts 直接 POST 照片,手机侧不用装任何 App。
- 落地目录你说了算 —— 在插件面板里改;照片立刻出现在那个目录。
- Agent 直接可读 —— 落地目录就是普通目录,DSH 的 Agent 可以立刻读取/分析。
- 到达即通知 —— 每张照片弹一条 Windows 通知(可点击跳回)。
- 不依赖云、不用 IM、不装手机 App、不改
app.asar。 - 可选 token 闸门 —— 默认关闭;关闭时以"局域网"为信任边界(见第 6 节)。
- 离线验证台 —— 50 条断言,含我们真机上踩过的每一种故障。
1. 安装
方式 A:按包名(推荐,可迁移)
dsh plugin --profile desktop add dsh-photo2dsh-by-lan
dsh plugin 会自动把本 bundle 记进 profile 的 dsh.profile.bundles,不需要手工编辑 profile 文件。
方式 B:从本地源码装
dsh plugin --profile desktop add file:D:\path\to\dsh-photo2dsh-by-lan
⚠️ 两种方式只选一种。若 profile 里已经有指向本插件绝对路径的
insert行,再按包名装一次会出现两行同名 entry。
装完之后:给端口放行一次防火墙
这是唯一需要管理员权限的步骤。默认端口 8787:
netsh advfirewall firewall add rule name="DSH Photo2DSH" dir=in action=allow protocol=TCP localport=8787 profile=any
撤销:
netsh advfirewall firewall delete rule name="DSH Photo2DSH"
⚠️
profile=any不能省。省略Profile=字段才是"对所有网络类别生效"; 若只写profile=public而你的网络被 Windows 判成"专用",入站包会被静默丢弃, 手机端表现为"服务器已停止响应"(超时),极难定位。这是本项目实测踩过的坑。顺带一提:别指望借用系统已有的放行端口。实测全机唯一"不限程序 + 不限类别"的 TCP 端口 被
http.sys独占(EACCES),抢不过来。
装完重启 DSH(客户端半边需要重新装配)。
2. 在插件面板里配置
侧栏 插件(Plugins) → 本包页面 → 配置卡片:
| 配置项 | 说明 |
|---|---|
| 落地目录 | 照片落在哪里。会在此目录下自动建 <日期>\ 子目录 |
| 端口 | 默认 8787。改完要给新端口重新放行防火墙 |
| 单文件上限(MB) | 默认 64 |
| 收到照片后弹 Windows 通知 | 默认开 |
| 每张照片写一张元数据卡(.json) | 默认开。嫌目录里文件翻倍就关掉——见 §4 |
| 只接受局域网来源 | 默认开。非私网地址的上传一律 403;关掉 token 时它是唯一来源闸门 |
| 要求 token | 默认开,强烈建议保持开启(理由见下) |
| 手机要连的 IP | 默认「自动」:枚举本机网卡、跳过 Hyper-V/VMware/WSL 等虚拟网卡、优先 192.168 网段。选错可手动指定 |
| 手机端要填的地址 | 只读,点「复制」直接拷走 |
| token | 只读 |
为什么落地目录建议填"当前会话工作区内的目录":DSH 的文件沙箱在
workspace-write下只允许写工作区。 落在工作区外,宿主仍能写(插件用的是原生 fs),但 Agent 用工具读它可能受限。
3. 手机端配置
iPad / iPhone(快捷指令)—— 四步,第 3 步最容易漏
- 新建快捷指令,加 「选择照片」(或「拍摄照片」)。
- 加 「转换图像」 → 「转换为」选 JPEG、质量 80%。
- 不加这步也能用,但实测同一张 8MP 图会从 ~1.5 MB 变成 ~12 MB(快捷指令按无损 PNG 递送)。
- 加 「获取 URL 内容」:
- URL 填配置卡片里复制的那串(形如
http://192.168.x.x:8787/u/<token>); - 点「显示更多」→ 方法 POST → 请求体 文件;
- ⚠️ 把「文件」栏的值改指「转换后的图像」 —— 漏这步就继续发原图,转换动作等于白加;
- 最后加一个 「快速查看」 看返回。
- URL 填配置卡片里复制的那串(形如
- 运行。返回是中文短句,一眼能看懂:
✅ 照片已收到
格式:JPEG(1.63 MB)
文件:20261003232612-4d2b993e-image.jpg
时间:2026-10-03 23:26:12
已存入 PC 的收件箱文件夹
首次访问局域网时 iPadOS 会弹「允许访问本地网络设备」——必须允许。 这与防火墙无关,是 iOS 独立的一道闸;拒绝后请求会被系统直接掐断。
想让相册里"只传我想传的那几张":快捷指令 ⓘ → 打开「在分享表单中显示」→ 接收类型勾「图像」, 之后在照片 App 里选图 → 分享 → 本快捷指令。
Android(HTTP Shortcuts)
同一个 URL、同样的 POST + 文件体即可。协议两端完全一致,不另设。
4. 数据怎么落
<你配置的目录>\
└─ 2026-10-03\
├─ 20261003232612-4d2b993e-image.jpg ← 本地时间戳-内容哈希前8位-清洗后的原名
└─ 20261003232612-4d2b993e-image.json ← sidecar:原名/备注/时间/字节数/sha256/格式/来源
- 文件名带内容哈希前 8 位,所以肉眼就能核对两张图是否同一张。
- 同秒同名不会互相覆盖:落盘用
link(),目标已存在就自动追加-1、-2。 (rename会静默覆盖——实测同一秒发多张image.jpg会只剩一张且毫无提示,这条是专门修的。) - 只认魔术字节,不信任
Content-Type:实测快捷指令送来时头写image/jpeg、字节却是 PNG。 - 那张
.json是可关的。它记的是文件名给不出来的东西:哪台设备发的、原始文件名、备注、sha256、嗅探所得格式。 嫌目录里文件翻倍,就在面板里取消勾选「每张照片写一张元数据卡」——之后目录里只剩照片本身。 这些元数据即使在开关打开时也不会影响照片落盘成功(写元数据失败只记日志)。
5. 故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 手机显示"服务器已停止响应"(超时) | 入站包被防火墙静默丢弃 | 按第 1 节加防火墙规则;必须带 profile=any |
返回 ❌ 上传失败:地址里缺少正确的 token |
URL 少了 /u/<token> 这一段 |
从配置卡片重新复制完整地址 |
返回 ❌ 这不是一张能识别的图片 |
发出去的确实不是图片字节 | 检查快捷指令里「文件」栏是否真的选上了图像变量 |
| 返回里写着"仍是 PNG" | 「转换图像」没接到「文件」栏 | 见第 3 节第 3 步 |
| 面板里看不到本包页面 | 那一行 Loader 被关闭了,或 profile 未重启 | 官方契约:客户端半边挂在"说明符恰为包名"的那一行上,关掉那一行卡片就一起消失 |
| 端口启动失败 | 端口被别的程序占用 | 面板里换一个端口,并给新端口放行防火墙 |
日志:$DSH_HOME\photo2dsh-by-lan.log 配置:$DSH_HOME\photo2dsh-by-lan.json
6. 安全边界
默认配置(requireToken: true + lanOnly: true):
- 只监听局域网(
0.0.0.0:<port>),不放公网。 - token 走 URL 路径,128 bit 随机、首次启动自动生成并持久化(这就是"可迁移"——换台机器不用手配)。
401不会回显正确的 token:那等于把它送给任何打错地址的人。- 设置页(
GET /)只对127.0.0.1开放:它含 token,不能让局域网随便看。 /health不含 token(uploadUrl里也含 token,所以被一并剥掉)。
不想用 token?可以,但两道闸门要一起上
在面板里把 「要求 token」关掉 即可(地址就变回 http://<IP>:<port>/,快捷指令里更好填)。但另外两道得在:
| 闸门 | 挡什么 | 默认 |
|---|---|---|
只接受局域网来源(lanOnly) |
非私网地址(公网)的上传一律 403 |
✅ 开 |
拒跨源 Origin |
网页跳板:你浏览的恶意网页借你的浏览器 POST 过来 | ✅ 永远生效,无开关 |
⚠️ 第二道不能省,也不能用第一道代替。 网页跳板走的是你自己的浏览器,服务端看到的来源是
127.0.0.1—— 在"局域网/私网"的定义里完全合法。只有Origin头能识别它:浏览器跨源请求一定带Origin,而 iOS 快捷指令这类原生客户端根本不带。
关掉 token 后仍然成立的保护(与 token 无关):路径清洗(防目录穿越)、只收图片(415)、体积上限(413)、落盘用 link() 防覆盖。
也就是说最坏情况是"局域网里的设备往你的收件箱灌垃圾图片",不可能写到别的目录、不可能覆盖已有文件、不可能放可执行内容。
仍然建议:只在自有可信网络(家里/办公室)里关。咖啡馆、酒店、合租的 Wi-Fi 别关。
7. 已知限制
- 仅 Windows(通知走 Windows PowerShell 5.1 的 WinRT;
package.json已声明os: ["win32"])。 - 通知横幅上的应用名显示为 Windows PowerShell。要显示成自己的名字需要往
HKCU\Software\Classes\AppUserModelId注册 AUMID。 - 不做:云中转 / 飞书微信通道 / 界面里的照片浏览器 / 自动把消息注入会话 / 自动清理。
- 卸载时不会删除你已收到的照片(那是你的数据)。
8. 开发
# 离线验证台(50 条,含全部真机故障复现用例)
node test/photo2dsh.test.mjs
架构:
| 文件 | 职责 |
|---|---|
core.mjs |
纯逻辑(命名/白名单/魔术字节/配置文档解析),无 I/O,可离线测 |
receiver.mjs |
宽容的原始 TCP 接收(不用 node:http,理由见文件头注释) |
store.mjs |
link() 原子落盘 + sidecar |
notify.mjs + scripts/toast.ps1 |
Windows Toast(PS 5.1;脚本纯 ASCII 以绕开 BOM 坑) |
index.mjs |
宿主接线:配置分层、receiver 生命周期、/api/photo2dsh-by-lan.config 路由 |
client.js |
客户端半边:注册进官方槽位 plugins.bundle.config 的配置卡片 |