1. 缘起

理论上讲, Apple 生态下的通用剪贴板 + AirDrop 应该是手机和电脑之间互相传送消息的最优解, 没有之一. 然而, 残酷的现实是, 通用剪贴板正常运作的概率在很多时候几乎是一个随机事件, 甚至有时候正常运作了居然还能带给我惊喜, 就离大谱.
与通用剪贴板不同, APNS (Apple 推送通知服务) 就异常稳定, 因此诞生了很多有用的工具, 如 Bark, Chanify 就是利用 APNS 将消息从电脑上推送到手机上, 并可以设置自动复制. 我尤其钟爱 Chanify, 除了对文字消息的支持, 还能发送图片和文件, 配合 Alfred Workflow 简直是方便到无敌.
那反过来呢? 如果想把消息快速从手机端发送到电脑端, 有没有这类快捷的方式呢?
其实手头如果有台 VPS, 配合 iOS 捷径是可以很方便地实现这一功能的:
使用 iOS 捷径在分享菜单里获取消息, 通过 http post 到 VPS 的 http server, VPS http server 同时也是一个 websocket client, 收到消息后进一步发送给 VPS 本机的 websocket server, 本机的 websocket server 再把消息广播给所有连接的 websocket client. 这里, 我们的本地电脑作为 websocket client, 事先已连接上 VPS, 在收到广播消息后执行消息复制等后处理就可以了.

2. 效果预览

我们先来看看效果.
(1) 场景 1: 文本、链接通过系统分享接口快捷发送到电脑端.
左: macOS 剪贴板 右: iOS 录屏 (2.1MB)
(2) 场景 2: 文本、链接通过小组件快捷发送到电脑端.
左: macOS 剪贴板 右: iOS 录屏 (1.3MB)
(3) 场景 3: 图像文件通过系统分享接口快捷发送到电脑端, 保存到 Downloads 文件夹下, 保存成功后并自动打开 finder.
左: macOS 桌面 右: iOS 录屏 (1MB)

3. Let’s Do It!

条件准备

一台 VPS + 一点点 Python 代码 (以下采用 Python 3.9 环境配置, 其他版本也可以)
Python 依赖配置: pip3 install -U flask websockets asyncio

Step1: Websocket server + client 配置

VPS 在 8183 端口开启一个 websocket 服务端, 收到任一客户端的消息后, 广播给其他所有客户端. 完整代码如下:
websocket_server.py (VPS 后台执行此脚本)

注意:

代码中 L16 设置了用户名和密码, 默认用户名 username 和密码 password, 记得修改, 后同.
asyncio.run 是 Python ≥ 3.7 后的新语法, 低版本需要修改为:
start_server = websockets.serve(main_logic, "0.0.0.0", 8183, create_protocol=UserInfoProtocol)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
macOS (其实 win 本也可) 作为 websocket 客户端, 连接到 VPS websocket server. 逻辑为: 保持和服务器的连接, 实现断线重连 (默认 5s 缓冲期). 在收到 server 广播来的消息后 (json 消息), 调用 callback_fn 函数对消息做后处理.
完整代码如下:
websocket_client.py (电脑端后台执行此脚本)
L82: 对于文本消息, 将消息发送给 pbcopy 实现剪贴板自动复制 (pbcopy 是 macOS 剪贴板复制逻辑, 其他操作系统需要做相应修改).
L87: 对于图片消息, 获取图片 base64 编码数据, 图片名称和后缀, 将 base64 数据还原为图片后, 写入到 Downloads 文件夹下, 并调用 osascript 实现自动打开 Finder 的 Downloads 目录并显示在前台.
倒数第 2 行需要修改用户名、密码和 VPS 公网 IP.

Step2: HTTP Server 配置

VPS 在 8182 端口开启一个 http server, 并开放 /api/v1.0/sendtxt/api/v1.0/sendimg 两个 restful api, 支持客户端通过 post 上传文本和图片. 同时, 该 http server 也是一个 websocket client, 收到 post 上传的消息后, 将消息序列化为 json 包发送给 VPS 本机的 websocket server, 从而触发本机 websocket server 的消息广播.
完整代码如下:
flask_server.py (VPS 后台执行此脚本)
L7 是 http server 的认证密码, L15 是作为 client 连接 websocket server 的认证密码, 这里作为演示设置二者保持一致.

Step3: iOS 捷径设置.

这里我一共设置了三个捷径:
notion image
Shortcuts
Shortcuts
Shortcuts

其中 发送到 mac 对应场景 2 通过小组件手动输入文字. 分享文本到 mac 对应场景 1 通过分享菜单分享文字, 分享媒体到 mac 对应场景 3 通过分享菜单分享图片.

这里以稍微复杂的 分享媒体到 mac 为例展开:
点击展开截图:
通过共享表单获取图片输入, 然后将图像转换为 JPEG 减小体积并剔除敏感图片元信息, 最后将图像通过 base64 编码后 post 到 http server.
这里目前存在的局限性是: 由于对 iOS 捷径不够了解, 我并未找到更好的 post 图像文件的方式, 因此这里用 base64 编码后 post 到服务器. 当图片太大时, post 有一定几率失败 (可能是 iOS 捷径的限制, 具体原因不详, 待 debug).

4. 总结

个人认为此方式还是比较方便的.
优点:
  • iOS 端不需要额外安装任何第三方客户端, 而且系统分享菜单使用体验极佳, 有时候(基本上)体验好于 AirDrop
  • 操作简单, 编程上一共只需要 3 个 Python 脚本, VPS 运行一个 Websocket server 和一个 Flask Http Server, 电脑端只需要运行一个 Websocket client. 直接无脑 tmux 挂后台就好.
  • 除了对文本的支持, 也支持图像等多媒体文件.
  • 电脑端不限于 macOS, 只要能跑 Python 就行. 对于 Win + iPhone 搭配的用户非常方便.
  • 总算不用打开微信文件传输助手了 (也算一个优点?)
缺点:
  • 需要一台 VPS
其他:
  • 以上脚本安全认证设置较为简单, 不过个人用问题不大
  • 可以适当扩展, 如不采用现在的广播消息机制, 可以指定发送消息到哪台设备; 消息后处理也可以进一步扩展, 不只是局限于文本自动复制和图片自动保存.
  • 本人对 iOS 捷径还是不够了解, 上传图像文件采用 base64 编码后 post, 可能还有更优方案.

原文:https://wizyoung-blog.vercel.app/yet-another-method-to-fix-universal-clipboard