Cuiji

炊记:单个菜品分享协议 v1

更新:2026-10-11。适用 Bundle ID com.nanliu.cuiji。

每次分享是一道菜。成功后新增独立菜品,排到列表顶部,滚动显示并短暂高亮、标注“刚刚分享”;不会替换已有菜品。同名或相同内容可以重复分享。图片按传入顺序保存,文字保留原有换行。添加后触发现有 iCloud 自动备份(用户已开启时)。没有新增的应用内导入入口。

选择接入方式

场景方式效果
网页按钮、另一 App 跳转cuiji://share URL scheme打开炊记,立即新增一道菜;图片通过 HTTPS 下载
原生 App 完整分享,包括离线图片.cuijidish 文件 + 系统分享菜单用户选择“炊记”,扩展接收;返回炊记时自动添加
第三方普通分享菜单系统 Share Extension接收宿主实际提供的文字、图片、网页链接;精确字段建议用文件协议
文件、AirDrop打开 .cuijidish 文件直接在炊记添加一道菜

系统分享扩展不强制跳转主 App。分享完成提示“已保存到炊记”;用户打开/返回炊记后自动处理收件箱,无需点击恢复。主 App 已在前台时也会监听接收通知。依据 Apple 的扩展生命周期说明。

.cuijibackup 是完整备份格式,仍按原有规则合并恢复;.cuijidish 是本协议的单菜品包,不能通过改后缀互换。

1. URL scheme

基础地址:cuiji://share。

参数必须说明
v是固定 1
title是菜名,首尾空白会去掉,非空,最多 200 个 Swift Character
text否UTF-8 纯文字做法,缺省为空;保留换行
source否原始来源 HTTP/HTTPS URL
image否图片 HTTPS URL;重复该参数传多张,最多 6 张,按参数顺序

必须有正文、图片或来源链接中的至少一种。已定义的非 image 参数只能出现一次。未知参数保留为扩展数据、不影响当前行为;不支持的版本、路径、URL 片段和重复的已定义参数会拒绝;根路径 / 也接受。UTF-8 URL 编码后的整条链接最多 16,384 字节。长做法或大量图片请用文件包。

最小示例:

cuiji://share?v=1&title=%E5%8C%85%E9%A5%BA%E5%AD%90&text=250g%20%E9%9D%A2%E7%B2%89%0A%E9%86%9220%E5%88%86%E9%92%9F

网页 JavaScript(应由用户点击按钮触发;仅编码一次):

const url = new URL('cuiji://share');
url.searchParams.set('v', '1');
url.searchParams.set('title', '包饺子');
url.searchParams.set('text', '250g 面粉,125ml 温水。\n醒20分钟,再揉匀。');
url.searchParams.set('source', 'https://example.com/recipes/dumplings');
url.searchParams.append('image', 'https://example.com/photos/step-1.jpg');
url.searchParams.append('image', 'https://example.com/photos/step-2.jpg');
// URLSearchParams 使用 + 表示空格。协议使用 RFC 3986,需转为 %20。
location.href = url.toString().replace(/\+/g, '%20');

原生 Swift:

var parts = URLComponents()
parts.scheme = "cuiji"
parts.host = "share"
parts.queryItems = [
    URLQueryItem(name: "v", value: "1"),
    URLQueryItem(name: "title", value: "包饺子"),
    URLQueryItem(name: "text", value: "250g 面粉\n醒20分钟"),
    URLQueryItem(name: "image", value: "https://example.com/step-1.jpg")
]
UIApplication.shared.open(parts.url!)

URLComponents 保留字面 +;接收端不把 + 当空格,不做二次百分号解码。调用 App 如果使用 canOpenURL 预检查,需要在其 LSApplicationQueriesSchemes 中加 cuiji。未安装炊记时由调用方提供下载提示,不存在通用跨浏览器可靠安装检测。自定义 scheme 不应作为身份认证机制。

图片必须可匿名直接访问,不依赖 Cookie 或用户登录,不接收 data:、file:、HTTP 图片链接或带用户名/密码的 URL。允许 HTTPS 重定向,禁止降级到 HTTP。下载成功后图片保存在本机,后续查看不依赖原网址。链接图片任一失败时整道菜不添加,可修正后重新分享。

2. 单菜品文件包 .cuijidish

文件本体是标准 ZIP,可用 Stored 或 Deflate 压缩,后缀改为 .cuijidish。

结构:

包饺子.cuijidish       # ZIP 文件
  manifest.json
  images/1.jpg
  images/2.png

manifest.json:

{
  "format": "com.nanliu.cuiji.dish",
  "version": 1,
  "title": "包饺子",
  "text": "250g 面粉,125ml 温水。\n醒20分钟,再揉匀。",
  "sourceURL": "https://example.com/recipes/dumplings",
  "images": ["images/1.jpg", "images/2.png"]
}

format、version、title 必须存在;建议发送端完整写入 text、images,旧数据省略时分别按空字符串、空数组处理;sourceURL 可以省略或为 null。正文最多 100,000 UTF-8 字节。来源最多 4096 UTF-8 字节,HTTP/HTTPS、有效主机、无用户名/密码。来源如果没有已出现在正文中,会作为新段落附到正文末尾。空图片数组合法,但标题之外至少有正文或来源。其他 JSON 字段保留结构与类型,在入库后写入菜品的可选 shareMetadata 对象,随备份继续保留。未来功能只能增加可选字段,不改已有字段名、类型或层级,兼容增加字段保持 version = 1;破坏性改版才提高版本,旧接收端会安全拒绝。损坏内容整份拒绝、不部分入库。JSON 重复键、超量嵌套、无效关联、截断图片和不安全 ZIP 路径均拒绝。

可执行生成器:

python3 share/make_dish_share.py \
  --title '包饺子' --text-file method.txt \
  --source 'https://example.com/recipes/dumplings' \
  --image step-1.jpg --image step-2.png \
  --output 包饺子.cuijidish

规范化清单参考 JSON Schema。限制与实际 iOS 解码结果以接收端校验为准。生成器只打包本地文件,不上传。

原生发送文件:

let controller = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
// 在 iPad 上配置 popoverPresentationController 的 sourceView/sourceRect。
present(controller, animated: true)

发送端的 fileURL 必须有 .cuijidish 后缀,不能直接分享 ZIP 内的 manifest。可以通过系统菜单分享给炊记,也可以将文件交给系统“用炊记打开”。文件方式不用联网。

3. 普通系统分享与网页内容

扩展接收文字、图片、网页和单菜品文件,不依赖每个第三方 App 都实现本协议。

你控制的网页可以放以下结构化片段,Safari 分享时优先使用它;无需增设炊记内入口:

<script type="application/json" data-cuiji-dish>
{
  "title": "包饺子",
  "text": "250g 面粉\n醒20分钟",
  "images": ["https://example.com/photos/step-1.jpg"]
}
</script>

图片字段支持相对网页路径,Safari 预处理会转换为 HTTPS 绝对地址。来源使用当前页面 URL。此片段只用于 Safari 分享网页,不等同于可直接打开的 JSON 文件。

4. 保存、失败与对接验收

解包、下载、解码、压缩在后台执行;完成全部验证后原子保存。错误会显示简短提示,原有菜品不变。用户重新发起分享会创建另一道菜,这是有意允许的重复。扩展收件箱内部使用 UUID,进程中断后重试同一个待处理包不会重复添加。

分享时正在编辑的菜品不会关闭或丢失草稿;回到列表后显示新增提示。减少动态效果开启时不播放滚动动画。中英日提示均已适配。

发送端验收建议:中文/英文/日语与换行;多图顺序;空图片纯文字;离线文件包;相同内容分享两次;冷启动;正在编辑时分享;错误版本、损坏图片、下载失败、尺寸超限。不要通过 URL 传本地路径或把 Base64 图片塞进链接。

5. 工程配置

主 App 与 Share Extension 使用共同 App Group group.com.nanliu.cuiji。扩展 Bundle ID com.nanliu.cuiji.share。共享容器只存待接收包,主菜品库仍留在主 App 沙盒。开发者账号中两个 App ID 必须启用并关联同一组,签名描述文件须包含该权限。

Download Python packet generator