Pular para o conteúdo principal
S

dsh-phone-drop

songzijianlove/dsh-phone-drop

LAN phone drop: a QR code on the computer screen opens a page on the phone, and photos or scanned documents land in a computer folder over the local network, with no tunnel and no cloud in between. Document mode binarizes locally in the browser (adaptive threshold plus speckle removal and edge crop) so an ordinary phone photo comes out as a black-and-white scan. Adds a "phone drop" entry at the foot of the DSH sidebar that opens the QR page, plus a phone_drop tool for the assistant; the same CLI also runs standalone without DSH.

Instalar

dsh plugin --profile web add github:songzijianlove/dsh-phone-drop

README

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.4.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

第一次装会弹个设置窗口

npm install -g dsh-phone-drop 装完之后,Windows 上会先弹一个小窗口,让你一次定三件事:

  1. 照片存到哪儿(默认 图片/dsh-phone-drop,可点「浏览…」另选);
  2. 要不要在桌面建「手机直传」快捷方式;
  3. 装完要不要立刻打开一次二维码页面。

点「就这样」生效;点「跳过」或直接关窗口,什么都不改,用默认值。以后随时能重开这个窗口:

dsh-phone-drop --setup

或者跳过它:

dsh-phone-drop --no-setup

只有在全局安装(npm install -g)时才会弹;作为别人的依赖被装进去时不弹。这套设置只出现一次,选过的路径会记住,下次启动不再问。

新版 npm 会拦 postinstall(重要):npm 11.16+ 起对「还没被 allowScripts 授权的安装脚本」默认拦截,装的时候会看到一行 npm warn install-scripts 1 package has install scripts not yet covered by allowScripts: dsh-phone-drop@0.4.1, 这时安装弹窗不会自己弹。三条路,任选其一:

  1. 装的时候带 --allow-scripts=dsh-phone-drop:
    npm install -g dsh-phone-drop --allow-scripts=dsh-phone-drop
    
  2. 或者永久放行这个包:
    npm config set allow-scripts=dsh-phone-drop --location=user
    
  3. 什么都不管,装完第一次运行 dsh-phone-drop 时照样会弹(CLI 每次启动前都检查「设置过没有」,没设置过就弹同一个窗口)。所以「没弹窗」不等于「没法用」,跑一下命令就行。

用

  1. 手机连上同一个 Wi-Fi(和电脑同一个路由器,不是流量)。
  2. 电脑上打开 http://127.0.0.1:9800/qr,屏幕上是一个大二维码。用手机的相机或扫一扫对着它,点弹出来的链接就进去了——不用在手机上敲 IP,也不用记端口。 (也可以在手机浏览器里手动输日志里那个地址,或者问助手「手机怎么传图给你」,它会跑 phone_drop 工具把地址和二维码念出来。)
  3. 点「📷 拍照」拍一张,或点「🖼 相册 / 文件」挑一张。
    • 就这样点「上传图片」 → 原始文件直接落到电脑,不做任何处理。
    • 想要扫描件,就先拨开「文档模式」开关:页面立刻把图裁边、转成黑白,还能拖「墨色」滑块调深浅、点「↻ 旋转 90°」摆正,然后按钮会变成「上传扫描件」。
  4. 文件就出现在电脑的接收目录里,文件名带时间戳。

手机浏览器里那个页面可以「添加到主屏幕」,之后点桌面图标直接进,跟装了 App 一样。

在 DSH 页面里点一个按钮就开

装了插件之后,DSH 侧边栏底部(设置齿轮旁边)会多一个「手机直传」按钮。点它就直接打开二维码页面,不用记 9800 这个端口,也不用收藏链接。

按钮是通过 dsh.client 声明 + lib/client.js 提供的浏览器半边,挂在 sidebar.footer.action 插槽上——这是 root 级、list 类型的插槽,只往后追加一项,不会覆盖别人的入口。

按钮背后是一个只对本机开放的接口:

GET /api/phone-drop/info  →  { running, dir, port, deskUrl }

它只接受来自回环地址(127.0.0.1 / ::1)的请求,别的来源一律 403;非 GET 返回 405。所以局域网里的手机拿不到这个接口,也不需要它——手机走的是二维码那个页面。

侧边栏按钮是从 0.4.3 开始有的。0.4.2 的 npm 包先发了包、后补的界面,lib/client.js 没进 tarball,所以装 0.4.2 看不到按钮。

扫描是怎么来的(勾上文档模式之后)

手机相机没有对网页开放「文档扫描」模式——那是相机 App 自己的功能,<input type=file capture> 只能拿到一张普通彩色照片。所以扫描这件事由页面自己做:

  1. 去色:按 Rec.601 亮度转灰度。
  2. 自适应二值化:每个像素跟它周围 28×28 邻域的平均亮度比,暗过一截就判成墨点。用积分图算局部均值,所以整页 2000px 也只要几十毫秒。这一步是关键——全局阈值在灯光不均时会一半黑一半白,局部阈值不会。
  3. 去噪:孤零零一个黑点周围没有同伴,判成纸屑丢掉,笔画留着。
  4. 裁边:先在灰度里估出「这一帧的纸有多亮」(取 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 会直接把二维码页面开到浏览器里。

命令行选项

选项作用
--port <n>监听端口(默认 9800)
--host <addr>绑定地址(默认 0.0.0.0)
--dir <path>本次运行的接收目录(不写进设置文件)
--token <str>访问口令
--title <str>页面标题
--open启动后自动打开二维码页面
--no-qr不在终端打印字符二维码
--setup打开设置窗口(选目录 / 快捷方式 / 自动打开)
--no-setup跳过首次设置窗口

安全边界

  • 监听 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 转译产物。

Plugins relacionados