Write OpenNote

通过API创建、更新和读取OpenNote笔记,支持图文与标签管理。

已扫描
适合谁
需要高效记录的个人用户、使用OpenNote进行笔记管理的创作者
不适合谁
未订阅OpenNote Pro的用户、无法获取API Token的用户
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @liam-duan/write-opennote

Skill 说明

命令、参数、文件名以原文为准

通过 OpenNote 公共 API 写笔记

使用 https://api.opennote.cc/api/v1 处的公共 API 将笔记写入 [OpenNote](https://opennote.cc)。

快速入门

所需条件

  1. 一个 OpenNote 账户 — 从 [App Store](https://apps.apple.com/app/opennote-notes-journal/id6450187057) 下载应用
  2. 一份 Pro 订阅 — API 访问权限为 Pro 功能
  3. 一个 个人访问令牌(PAT) — 从应用中生成

生成 API 令牌

  1. 打开 OpenNote 应用
  2. 进入 个人资料API 令牌
  3. 点击 创建令牌
  4. 选择所需的权限范围:

- diaries:write — 创建和更新笔记

- images:write — 向笔记上传图片

  1. 设置过期时间(默认:90 天,最大:365 天,或永不)
  2. 立即复制令牌 — 它以 milo_pat_v1_ 开头,且 仅显示一次

设置环境变量

在你的 OpenClaw 环境中设置 OPENNOTE_API_TOKEN

选项 A — OpenClaw 配置(推荐):

在 OpenClaw 设置中添加环境变量:

OPENNOTE_API_TOKEN=milo_pat_v1_your_token_here

选项 B — Shell 配置文件(本地运行时使用):

export OPENNOTE_API_TOKEN="milo_pat_v1_your_token_here"

将此内容添加至 ~/.zshrc~/.bashrc 以在会话间持久化。

安全提示:

  • 永远不要将令牌提交到版本控制系统
  • 该令牌具有对笔记的写入权限 — 请将其视为密码处理
  • 若令牌泄露,请立即在应用中通过 个人资料API 令牌 撤销

令牌生命周期

  • 令牌根据设定的持续时间过期(默认 90 天)
  • 每个账户最多可拥有 5 个活跃令牌
  • 可随时在应用中撤销未使用的令牌
  • 当令牌过期或被撤销后,需生成新令牌并更新 OPENNOTE_API_TOKEN
  • 若收到 401 INVALID_API_TOKEN 错误,说明令牌已过期或被撤销

前置条件

令牌从 OPENNOTE_API_TOKEN 环境变量读取。必须具备 diaries:write 权限。若需上传图片,则还需 images:write 权限。

本地缓存文件

此技能在工作目录下的 .opennote/ 中维护两个本地文件:

  • 标签缓存.opennote/opennote-labels-cache.json — 缓存的用户标签
  • 笔记历史.opennote/opennote-history.json — 通过此技能写入/编辑的所有笔记日志

每次调用开始时都应读取这些文件。若文件缺失,视为为空。若用户询问之前写过的笔记,请查阅历史文件。

指令

步骤 1:加载标签(获取或使用缓存)

读取 .opennote/opennote-labels-cache.json。若文件存在且上次获取时间在 24 小时内,则使用缓存标签。

预期缓存格式:

{
  "fetchedAt": 1741111111111,
  "labels": [
    { "categoryID": 1, "categoryName": "个人", "categoryColor": 4294198070 },
    { "categoryID": 2, "categoryName": "工作", "categoryColor": 4283215696 }
  ]
}

若缓存缺失、为空或过期(超过 24 小时),则从 API 获取标签:

curl -s "https://api.opennote.cc/api/v1/labels" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN"

响应示例:

{
  "labels": [
    { "categoryID": 1, "categoryName": "个人", "categoryColor": 4294198070, "visibility": true, "coverImageName": null, "lastModified": 1741111111111, "fontFamily": null, "backgroundPreset": null }
  ]
}

获取后,将结果写入 .opennote/opennote-labels-cache.json,并使用当前时间戳作为 fetchedAt

步骤 2:选择标签/分类

  • 若用户指定了标签名称或 ID,使用对应的 categoryID
  • 若用户未指定标签,从缓存标签列表中 随机选择一个
  • 若完全无缓存标签,使用 category: 0(无标签)

随机选择标签时,需告知用户选择了哪个标签。

步骤 3:收集笔记内容

向用户询问想写的内容。收集以下信息:

  • 笔记正文内容(必填)
  • 可选:标题
  • 可选:是否添加贴纸
  • 可选:是否上传图片(用户需提供图片文件)

步骤 4:生成时间戳和文件名

const now = Date.now(); // Unix 毫秒时间戳
const fileName = `${now}.json`;

步骤 5:构建 richContent(Quill Delta JSON 字符串)

构建 Quill Delta 操作数组,然后通过 JSON.stringify() 转换为 richContent 字段。

支持的 Quill Delta 操作

纯文本:

{ "insert": "你好世界" }

换行符(必需 — 每个 Delta 必须至少包含一个):

{ "insert": "\n" }

内联样式(在插入文本时设置):

  • bold(布尔值)
  • italic(布尔值)
  • underline(布尔值)
  • strike(布尔值)
  • color(十六进制字符串,如 "#2a7fff")—— 推荐使用下方配色板中的颜色以获得最佳效果
  • size(数字,通常为 2–99;应用默认值为 17)
{ "insert": "重要", "attributes": { "bold": true, "color": "#ff0000", "size": 20 } }

可用颜色配色板(共 36 种颜色):

始终存储 浅色模式下的十六进制值 — 应用会在深色模式下自动映射。

名称浅色模式十六进制
黑色#000000
炭灰色#545454
深灰#616161
温暖青灰#455a64
深红#b71c1c
深红色#c0392b
砖红#bf360c
深琥珀色#a04000
奶油黄#9a6e00
深橄榄绿#558b2f
森林绿#2e7d32
深金色#8b6914
深青绿#00695c
深青色#00838f
皇家蓝#1565c0
蓝色#007aff
靛蓝#5856d6
深紫色#6a1b9a
洋红#e91e63
粉红#ff2d55
李子色#880e4f
梅紫#6a0572
深紫罗兰#4a148c
棕色#8d6e63
红色#ff3b30
深玫瑰红#ad1457
陶土色#8b3a1f
海绿色#007a63
深夜蓝#1a237e
深鼠尾草绿#2e5902
橙赭色#7a5c00
钢灰色#37474f
葡萄酒红#8b0000
枫木色#6d2f1f
深海蓝#005b72
深咖啡色#4e342e

块/行样式(附加在换行操作符后):

  • align: "left"(左对齐)、"center"(居中)、"right"(右对齐)
  • indent: 整数,表示缩进量
  • list: "ordered"(有序列表)、"bullet"(项目符号)、"checked"(已勾选)、"unchecked"(未勾选)
  • code-block: true(启用代码块)
  • blockquote: true(启用引用块)
{ "insert": "一个项目符号项" },
{ "insert": "\n", "attributes": { "list": "bullet" } }

图片嵌入:

{ "insert": { "image": "{\"src\":\"my_photo.jpg\",\"w\":1920,\"h\":1080}" } }

若尺寸未知:

{ "insert": { "image": "my_photo.jpg" } }

拼贴图嵌入(多图布局):

{
  "insert": {
    "collage": "{\"layout\":\"twoHorizontal\",\"images\":[\"img1.jpg\",\"img2.jpg\"]}"
  }
}

可用的拼贴布局:

  • twoHorizontal(2张图横向排列)
  • bigLeft2Right(3张图,左侧大图,右侧小图)
  • bigRight2Left(3张图,右侧大图,左侧小图)
  • threeRow(3张图纵向排列)
  • bigLeft2RectRight(3张图,左侧大图,右侧矩形小图)
  • bigTop2Bottom(3张图,顶部大图,底部小图)
  • bigLeftTopRect2Bottom(4张图,左上角大图,右下角矩形区域)
  • grid2x2(4张图,2×2网格布局)

分隔线嵌入:

{ "insert": { "divider": "split" } }

richContent 字段示例

richContent 字段必须是 JSON 字符串(而非对象):

"[{\"insert\":\"我的标题\\n\",\"attributes\":{\"bold\":true,\"size\":24}},{\"insert\":\"今天是个好日子。\\n\"},{\"insert\":{\"image\":\"photo.jpg\"}},{\"insert\":\"\\n\"}]"

第6步:构建 content 字段(纯文本)

content 是用于主页预览和搜索的纯文本摘要。请移除所有格式化内容,仅保留文字内容。不要在此处放入 JSON 或 Markdown。

第7步:构建 stickerData(可选)

stickerData 是一个 JSON 字符串(非对象),包含一组贴纸叠加对象。贴纸会浮在笔记页面上方,且不属于 richContent 内容。

每个贴纸对象结构如下:

{
  "id": "唯一十六进制标识符",
  "assetPath": "assets/stickers/bunny.svg",
  "dx": 150.0,
  "dy": 200.0,
  "normalizedDx": 0.39,
  "normalizedDy": 0.25,
  "scale": 1.0,
  "rotation": 0.0
}

字段规则:

  • id:唯一字符串,格式为 <十六进制时间戳>_<六位十六进制随机码>
  • assetPath:必须与以下打包贴纸列表中的路径完全匹配
  • dx, dy:相对于笔记画布左上角的像素偏移量(典型画布宽度约 390px)
  • normalizedDx, normalizedDy:比例位置(dx 范围 0.0–1.0;dy 可超过 1.0,适用于长内容)
  • scale:缩放比例,范围 0.3 至 4.0(1.0 表示默认 70px 基础大小)
  • rotation:旋转角度,单位为弧度

stickerData 字段示例值:

"[{\"id\":\"18f0c9d2_a1b2c3\",\"assetPath\":\"assets/stickers/bunny.svg\",\"dx\":150.0,\"dy\":200.0,\"normalizedDx\":0.39,\"normalizedDy\":0.25,\"scale\":1.0,\"rotation\":0.0}]"

可用贴纸资源路径

可爱风格:

assets/stickers/backpack.svg       assets/stickers/bird.svg
assets/stickers/book.svg           assets/stickers/bunny.svg
assets/stickers/burger.svg         assets/stickers/cake_slice.svg
assets/stickers/cat.svg            assets/stickers/cheese.svg
assets/stickers/chicken.svg        assets/stickers/clapboard.svg
assets/stickers/crown.svg          assets/stickers/flower_2.svg
assets/stickers/frog.svg           assets/stickers/garland.svg
assets/stickers/gramophone.svg     assets/stickers/hat.svg
assets/stickers/heart_2.svg        assets/stickers/kiwi.svg
assets/stickers/kitten.svg         assets/stickers/little_girl.svg
assets/stickers/little_girl_2.svg  assets/stickers/meals.svg
assets/stickers/muffin.svg         assets/stickers/piggy_bank.svg
assets/stickers/pizza.svg          assets/stickers/plant.svg
assets/stickers/pop_corn.svg       assets/stickers/rabbit.svg
assets/stickers/rabbit_2.svg       assets/stickers/saturn.svg
assets/stickers/shooting_star.svg  assets/stickers/skirt.svg
assets/stickers/soda_can.svg       assets/stickers/strawberry.svg
assets/stickers/symbol.svg         assets/stickers/tea_pot.svg
assets/stickers/ufo.svg            assets/stickers/wallet.svg
assets/stickers/watermelon.svg     assets/stickers/watermelon_2.svg

动物类: assets/stickers/animals/001-crocodile.svg040-hippopotamus.svg(共 40 个)

自然类: assets/stickers/nature/001-sunflower.svg024-tree.svg(共 24 个)

角色类: assets/stickers/characters/girl_001-girl.svggirl_020-girl.svghippie_001-hippie.svghippie_020-dj.svg

食物类: assets/stickers/food/misc_001-meat.svgmisc_020-dolphin.svgk760_001-cat.svgk760_020-ice_cream.svgk791_001-cat.svgk791_020-book.svg

萌趣生活类: assets/stickers/cute_life/k678_001-badge.svgk678_020-vinyl_record.svgk612_001-teddy_bear.svgk612_020-yogurt.svgk155_001-egg_and_bacon.svgk155_020-rainbow.svg

日常用品类: assets/stickers/everyday/k448_001-cake.svgk448_020-violin.svgk450_001-backpack.svgk450_020-turtle.svg

第8步:上传图片(如有)

如果笔记中包含图片,请先上传每一张:

curl -s -X POST "https://api.opennote.cc/api/v1/images" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN" \
  -F "image=@/path/to/photo.jpg"

响应结果:

{ "imageName": "photo.jpg", "sizeInMB": 0.42 }

Skill: Write OpenNote

Version: 1.0.0

Chunk: 3/3

使用返回的 imageName 填入 imageListrichContent 中的图片嵌入,以及可选的 diaryCoverImageName

图片限制:

  • 支持格式:jpg、jpeg、png、gif、webp、heic、heif
  • 单个文件最大大小:15 MB
  • 用户存储配额适用

步骤 9:组装并发送 API 请求

curl -s -X POST "https://api.opennote.cc/api/v1/diaries" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "<timestamp>.json",
    "time": <timestamp>,
    "content": "<plain text summary>",
    "richContent": "<JSON string of Quill Delta ops>",
    "stickerData": "<JSON string of sticker array or null>",
    "diaryCoverImageName": null,
    "category": <categoryID>,
    "title": "<optional title>",
    "imageList": [],
    "isDeleted": false,
    "lastModified": <timestamp>,
    "hideTitle": false
  }'

如果 API 返回 409 ALREADY_EXISTS,请改用 PUT 请求重试:

curl -s -X PUT "https://api.opennote.cc/api/v1/diaries/<fileName>" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "content": "...", "richContent": "...", "lastModified": <now> }'

步骤 10:记录到笔记历史

创建或更新成功后,将一条记录追加至 .opennote/opennote-history.json 文件中。

先读取现有文件(若不存在则初始化为空数组 []),然后追加新条目并写回。

每条历史记录格式如下:

{
  "fileName": "1741111111111.json",
  "action": "created",
  "timestamp": 1741111111111,
  "title": "Morning Walk",
  "contentPreview": "Woke up early and went for a walk in the park...",
  "categoryID": 2,
  "categoryName": "Personal",
  "hasStickers": true,
  "hasImages": false,
  "imageList": []
}

历史记录规则:

  • action:值为 "created""updated"
  • contentPreview:取 content 的前 150 个字符
  • categoryName:从缓存的标签列表中查找,若 category 为 0 则显示 "No Label"
  • hasStickers:当 stickerData 非空时为 true
  • hasImages:当 imageList 非空时为 true
  • 更新操作时新增一条 action: "updated" 的记录(不覆盖旧记录)

步骤 11:向用户确认

告知用户:

  • 笔记已成功创建/更新
  • 文件名
  • 使用的标签(以及是否为随机选择)
  • 内容的简要摘要

从 API 读取笔记

当用户请求读取、搜索或以现有笔记作为模板时,请使用读取接口(需在 token 中包含 diaries:read 权限)。

注意: 创建 token 时,diaries:read 权限可能尚未可用。若在调用读取接口时收到 403 INSUFFICIENT_SCOPE 错误,说明 token 缺少该权限。此时仍可正常写入笔记和获取标签信息。

列出笔记

curl -s "https://api.opennote.cc/api/v1/diaries?category=CATEGORY_ID&search=KEYWORD&limit=50&offset=0" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN"

查询参数(全部可选):

  • category — 按 categoryID(整数)筛选
  • search — 在内容和标题中进行关键词匹配(部分匹配)
  • limit — 每页结果数量,范围 1–200,默认 50
  • offset — 分页偏移量,默认 0

响应示例:

{
  "diaries": [ { "fileName": "...", "time": ..., "content": "...", "richContent": "...", ... } ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

获取单个笔记

curl -s "https://api.opennote.cc/api/v1/diaries/FILENAME.json" \
  -H "Authorization: Bearer $OPENNOTE_API_TOKEN"

查找历史笔记

当用户询问之前写过的笔记(例如:“上次写了什么?”、“找一下关于 X 的笔记”),请读取 .opennote/opennote-history.json 文件。可通过以下方式匹配:

  • title(部分匹配)
  • contentPreview(关键词搜索)
  • categoryName(标签名称)
  • timestamp(时间范围)
  • fileName(精确匹配)

报告所有匹配项的标题、内容预览、日期和标签信息。

验证规则

发送前请进行以下验证:

  1. richContent 必须是 JSON null,或能解析为数组的 JSON 字符串,且数组中每个元素必须包含 insert 键。
  2. 严禁在 richContent 中放入纯文本、Markdown、"NULL""null" 或空字符串 ""
  3. content 必须为纯文本(用于预览和搜索),不能是 JSON 格式。
  4. imageList 必须仅包含文件名,并且必须包含 richContent 中所有嵌入图片的文件名。
  5. timelastModified 使用 Unix 毫秒时间戳。
  6. stickerData 必须省略、为 null,或为可解析为有效贴纸对象数组的 JSON 字符串。
  7. 每个贴纸的 assetPath 必须与提供的贴纸列表中的路径完全一致。
  8. 贴纸的 scale 值应在 0.3 至 4.0 之间。
LD
@liam-duan

已收录 1 个 Skill

相关推荐