Hermes 操作 Trilium 传图指南

Hermes 操作 Trilium 传图指南

本文档记录 Hermes Agent 通过 ETAPI 向 Trilium 笔记添加图片的完整流程,包括搜图、上传和引用。


一、ETAPI 附件接口

Trilium v0.63+ 的 ETAPI 附件接口接受 JSON body(不是 multipart 表单)。

创建附件

POST /etapi/attachments
Content-Type: application/json
Authorization: <ETAPI_TOKEN>

{
  "ownerId": "<目标 NoteId>",
  "role": "image",
  "mime": "image/jpeg",
  "title": "描述性标题",
  "content": "<base64 编码的图片数据>",
  "position": 100
}
参数说明
ownerId目标笔记的 noteId
role"image" 或 "file"
mimeMIME 类型(image/jpeg, image/png 等)
title显示标题
content图片的 base64 编码字符串(可选,也可后续上传)
position排序位置(整数)

返回 201 及附件对象,包含 attachmentId

读取/写入附件内容

GET  /etapi/attachments/{attachmentId}/content  # 读取原始内容
PUT  /etapi/attachments/{attachmentId}/content  # 写入原始内容(binary body)

引用附件

在笔记 HTML 中使用以下路径引用:

api/attachments/{attachmentId}/image/{filename}

例如:api/attachments/YGMH54cTFaj4/image/beatles.jpg


二、Python 上传脚本

import urllib.request, json, base64

def upload_image_to_trilium(token, note_id, image_path, title, position=0):
    """Upload an image to a Trilium note via ETAPI."""
    with open(image_path, 'rb') as f:
        b64 = base64.b64encode(f.read()).decode('ascii')

    # Determine MIME from extension
    ext = image_path.rsplit('.', 1)[-1].lower()
    mime_map = {'jpg': 'image/jpeg', 'jpeg': 'image/jpeg',
                'png': 'image/png', 'gif': 'image/gif',
                'webp': 'image/webp'}
    mime = mime_map.get(ext, 'image/jpeg')

    payload = json.dumps({
        "ownerId": note_id,
        "role": "image",
        "mime": mime,
        "title": title,
        "content": b64,
        "position": position
    }).encode()

    req = urllib.request.Request(
        'https://trilium.atibm.com/etapi/attachments',
        data=payload,
        headers={
            'Authorization': token,
            'Content-Type': 'application/json'
        }
    )

    resp = urllib.request.urlopen(req)
    data = json.loads(resp.read())
    return data['attachmentId']  # 返回 attachmentId 用于引用

三、Hermes 搜图途径

Hermes 没有 web_search 工具,但有以下搜图方式:

方法 A:PinchTab + DuckDuckGo 图片搜索

  1. 用 PinchTab 打开 DuckDuckGo 图片搜索
  2. 导航:https://duckduckgo.com/?q={关键词}&iax=images&ia=images
  3. pinchtab_get_textpinchtab_snapshot 提取结果
  4. pinchtab_click 打开图片页
  5. 右键/检查获取图片 URL,用 curl 下载

方法 B:Wikimedia Commons API

  1. 搜索图片:https://commons.wikimedia.org/w/api.php?action=query&list=search&srsearch={关键词}&srlimit=5&format=json&srnamespace=6
  2. 获取下载 URL:https://commons.wikimedia.org/w/api.php?action=query&titles={文件名}&prop=imageinfo&iiprop=url&format=json
  3. 用 curl 下载:curl -sL -o output.jpg '{image_url}'

注意:Google 图片搜索有反爬验证(CAPTCHA),不可用。直接 curl 请求搜索引擎也被拦截。


四、完整工作流示例

  1. 搜图 — 用 PinchTab 开 DuckDuckGo 搜图,或 Wikimedia API 查找 CC 协议图片
  2. 下载 — 用 curl 下载到 /tmp/
  3. 上传 — 用 Python 脚本 base64 编码后 POST 到 ETAPI
  4. 引用 — 在笔记 HTML 中插入 <img src="api/attachments/{attachmentId}/image/{filename}">
  5. 更新 — 用 PUT /etapi/notes/{noteId}/content 写入更新后的 HTML

注意:PUT 更新内容时用 Content-Type: text/plain,不是 application/json


五、常见问题

Q: 上传返回 500 "role must be given"

确认用的是 JSON body(非 multipart/form-data),且 ownerId 是已有笔记的 ID。

Q: 图片上传后笔记不显示

确认 attachmentId 拼写正确,且 role="image"。

Q: 笔记内容更新后返回 "Router not found"

确认用的是 PUT /etapi/notes/{noteId}/content,且 Content-Type 为 text/plain