dsh-phone-drop
手机一拍,电脑就有。
手机和电脑连同一个 Wi-Fi,手机浏览器打开一个地址,拍照或选图 → 想扫描就勾一下「文档模式」,自动裁边转黑白 → 落进电脑的一个文件夹。全程局域网直连,不经过任何隧道,毫秒级。
默认什么都不改:不勾选时,手机上拍的照片原样传到电脑,字节都不动(大小、格式、EXIF 全是原样)。
为什么需要它
把手机里的图弄进电脑,常见路子都很别扭:
- 微信/QQ 传文件:压图、要登录、还要手动另存。
- 云盘:上传再下载,两张图等半天。
dsh web自带的隧道:请求要绕一圈公网,每张图几秒钟,而且慢得莫名其妙——手机 CPU 和网速都没跑满,时间全花在跨境往返上。
这个插件走的是最短路:手机和电脑本来就在同一个路由器下,直接连过去就行。
装
dsh plugin install dsh-phone-drop
或者作为 bundle 加进 profile 的 package.json:
{
"dependencies": { "dsh-phone-drop": "^0.3.1" },
"dsh": { "profile": { "bundles": ["dsh-phone-drop"] } }
}
装完重启宿主,日志里会出现:
dsh-phone-drop: 服务已启动,监听 0.0.0.0:9800,保存到 C:\Users\你\Pictures\dsh-phone-drop
dsh-phone-drop: 手机请打开 http://192.168.1.23:9800
用
- 手机连上同一个 Wi-Fi(和电脑同一个路由器,不是流量)。
- 电脑上打开
http://127.0.0.1:9800/qr,屏幕上是一个大二维码。用手机的相机或扫一扫对着它,点弹出来的链接就进去了——不用在手机上敲 IP,也不用记端口。 (也可以在手机浏览器里手动输日志里那个地址,或者问助手「手机怎么传图给你」,它会跑phone_drop工具把地址和二维码念出来。) - 点「📷 拍照」拍一张,或点「🖼 相册 / 文件」挑一张。
- 就这样点「上传图片」 → 原始文件直接落到电脑,不做任何处理。
- 想要扫描件,就先拨开「文档模式」开关:页面立刻把图裁边、转成黑白,还能拖「墨色」滑块调深浅、点「↻ 旋转 90°」摆正,然后按钮会变成「上传扫描件」。
- 文件就出现在电脑的接收目录里,文件名带时间戳。
手机浏览器里那个页面可以「添加到主屏幕」,之后点桌面图标直接进,跟装了 App 一样。
扫描是怎么来的(勾上文档模式之后)
手机相机没有对网页开放「文档扫描」模式——那是相机 App 自己的功能,<input type=file capture> 只能拿到一张普通彩色照片。所以扫描这件事由页面自己做:
- 去色:按 Rec.601 亮度转灰度。
- 自适应二值化:每个像素跟它周围 28×28 邻域的平均亮度比,暗过一截就判成墨点。用积分图算局部均值,所以整页 2000px 也只要几十毫秒。这一步是关键——全局阈值在灯光不均时会一半黑一半白,局部阈值不会。
- 去噪:孤零零一个黑点周围没有同伴,判成纸屑丢掉,笔画留着。
- 裁边:先在灰度里估出「这一帧的纸有多亮」(取 90 分位,避免少数暗角或反光带偏),把够亮的像素算作纸面,再按行/列的占用率找出纸的边界,四周留 2% 白边。这一步必须看灰度,不能看二值图——深色桌面上自适应阈值也会把桌面判成白,那样裁出来的框就是整幅画。找不到纸面、或者纸面几乎铺满整幅,就不裁。
三档结果可选:黑白扫描(默认)、灰度增强(对比拉伸,适合铅笔稿)、原图。
整个扫描过程纯前端、不用联网、不需要 HTTPS(局域网 http:// 没有安全上下文,getUserMedia 用不了),也不上传原图——处理完只传最终那张。
配置
在 profile 的 cordis.patch.yml 里覆盖:
- id: phone-drop
config:
port: 9800 # 监听端口
host: '0.0.0.0' # 绑定地址,默认所有网卡
dir: 'D:/手机图片' # 接收目录
token: 'my-secret' # 访问口令,见下
title: '手机直传' # 页面标题
enabled: true # 关掉整行就停
口令
默认没有口令——同一个 Wi-Fi 下的任何设备都能往目录里丢文件。在家里够用,但咖啡馆/宿舍就不合适了。设了 token 之后,除 /health 外每个路由都要带 ?k=口令,页面上传也一样;比对用的是定长哈希的常数时间比较,不会因为提前返回泄露长度。二维码里会自动带上口令。
换存放文件夹
三种办法,效果一样:
- 跟助手说「把手机传图存到 D 盘某某文件夹」,它会调
phone_drop的setdir——下一次拍照就落到新目录,不用重启。 - 直接改
~/.dsh-phone-drop.json里的dir(文件不存在就自己建一个,格式{"dir": "D:/手机图片"})。 - 任何脚本 POST 到
http://127.0.0.1:9800/api/dir,body{"dir": "D:/手机图片"}。
这个覆盖文件优先于 cordis.patch.yml 里的 dir,删掉它就回到配置里的值。之所以单独放一个文件、而不是让插件去改 profile 补丁:那个文件有好几个写者(loader 会重写托管块),插件背着它改只会打架。/api/dir 只接受来自本机回环的请求,手机上改不了别人电脑的落盘位置。命令行 --dir 只管本次运行,不会写进覆盖文件——否则一次试用会永久劫持之后每一次启动。
工具
插件注册了一个 phone_drop 工具,助手可以调用:
action |
作用 |
|---|---|
url |
返回手机上该打开的地址、接收目录 |
qr |
额外返回一段终端字符二维码,可以直接贴进对话里让用户扫 |
recent |
额外返回最近收到的文件名和大小 |
setdir |
换存放文件夹(配 dir 参数) |
这样用户问「手机怎么传图给你」时,助手直接把地址说出来,不用去翻日志。
不开 DSH 也能跑
npx dsh-phone-drop --port 9800 --dir ./收图 --open
同一个服务器,同一个页面,前台跑着,Ctrl+C 停。--open 会直接把二维码页面开到浏览器里。
安全边界
- 监听
0.0.0.0是刻意的:这个服务的存在意义就是让手机连进来。它不是 DSH 宿主 webserver——那一个明确拒绝通配地址,因为前面挂着整个远程执行面;这里只有几个路由,没有会话、没有 shell、不碰宿主状态。 - 上限 64 MB/次,超了直接 413 并断流。
- 文件名只取 basename 并清掉 Windows 非法字符,落地永远在配置的目录里,不会被
../带出去;/file?name=先按目录列表核对一遍,再核对解析后的真实路径,两道都过才发。 - 上传内容原样落盘,不执行、不解析;下载时带
x-content-type-options: nosniff,不让浏览器猜类型。 /api/dir(换目录)只允许本机回环调用。- 建议只在可信网络里开;公用网络请设
token。
许可
MIT。二维码编码器内置了 Nayuki 的 qrcode-generator(MIT)的 TypeScript 转译产物。
No comments yet. Be the first to write one.