360 Secure SkillHub
360 安全技能中心

koreader-highlights

通过 Bash 与 Python 在本地 Dropbox/Apps 目录查找 KOReader 的 .sdr.json 文件,列出书籍、读取单本书的高亮与笔记、跨书搜索关键词,并按时间汇总最新高亮;全程只读,不修改文件、不联网,失败时以自然语言提示。

myugan
myugan
热度 55.22
最近更新 2026-05-17 13:38:51
分类:知识依赖:代码执行 / 读写文件应用场景:笔记整理 / 摘要总结 / 研究

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add koreader-highlights
Metadata
name
koreader-highlights
description
Use this skill when the user asks about reading highlights, book notes, annotations, or reading history from KOReader. Triggers: "what books", "show highlights", "latest highlights", "how many books", "find highlights about", "summarize highlights", KOReader, HighlightSync, .sdr.json. Read-only — only retrieves data, never modifies files, never does unrelated tasks.

KOReader Highlights Skill

Step 1: Find the highlights directory

Run this via your tool (NOT in your reply):

find ~/Dropbox/Apps -name "*.sdr.json" -maxdepth 2 2>/dev/null | head -5
  • If results come back, the directory is the parent folder of those files.
  • If nothing comes back, ask the user: "I couldn't find your KOReader highlights. What's the name of your HighlightSync folder under Dropbox/Apps?"
  • Save the discovered path in MEMORY.md for future sessions.

Step 2: List books

Run via tool:

ls ~/Dropbox/Apps/<APP_NAME>/*.sdr.json 2>/dev/null

Each .sdr.json file = one book. The book title is the filename without .sdr.json. Example: Designing Data-Intensive Applications.sdr.json → book title is "Designing Data-Intensive Applications"

Reply to user like: "You have 5 books in your library: ..." (list the titles)

Step 3: Read highlights from a book

Run via tool:

python3 -c "
import json
with open('<FULL_PATH_TO_FILE>') as f: data=json.load(f)
for h in data: print(h.get('datetime',''), '|', h.get('chapter',''), '|', h.get('pageno',''), '|', h.get('text','')[:200])
"

Reply with the highlights formatted naturally:

  • "From chapter X, page Y (March 6, 2026): [highlight text]"

Step 4: Search across all books

Run via tool:

python3 -c "
import json, glob, os
for f in glob.glob(os.path.expanduser('~/Dropbox/Apps/<APP_NAME>/*.sdr.json')):
    title = os.path.basename(f).replace('.sdr.json','')
    data = json.load(open(f))
    for h in data:
        if '<SEARCH_TERM>'.lower() in h.get('text','').lower() or '<SEARCH_TERM>'.lower() in h.get('notes','').lower():
            print(title, '|', h.get('pageno',''), '|', h.get('text','')[:200])
"

Step 5: Latest highlights

Run via tool:

python3 -c "
import json, glob, os
all_h = []
for f in glob.glob(os.path.expanduser('~/Dropbox/Apps/<APP_NAME>/*.sdr.json')):
    title = os.path.basename(f).replace('.sdr.json','')
    data = json.load(open(f))
    for h in data:
        if h.get('datetime'): all_h.append((h['datetime'], title, h.get('chapter',''), h.get('pageno',''), h.get('text','')))
all_h.sort(reverse=True)
for dt,t,ch,pg,tx in all_h[:10]: print(dt, '|', t, '|', ch, '|', pg, '|', tx[:200])
"

.sdr.json format

Each file is a flat JSON array. Here is a real example:

[{"datetime":"2026-03-06 18:42:42","chapter":"Why You Might Need a Sharded Cache","pageno":192,"page":"/body/DocFragment[25]/body/p[41]/text().0","text":"in Why You Might Need a Sharded Cache"},{"datetime":"2026-03-07 09:07:13","pageno":192,"page":"/body/DocFragment[25]/body/p[48]/text().10","drawer":"lighten","chapter":"Why You Might Need a Sharded Cache","pos0":"/body/DocFragment[25]/body/p[48]/text().10","pos1":"/body/DocFragment[25]/body/p[49]/text().33","text":"the primary reason for sharding any service is to increase the size of the\ndata being stored in the service.","color":"gray"}]

Fields you SHOW to the user: text, datetime, chapter, pageno, notes (if present). Fields you IGNORE (internal, never show): page, drawer, color, pos0, pos1.

Notes:

  • Field order is NOT fixed — fields can appear in any order within an entry.
  • drawer, color, pos0, pos1 are optional — not every entry has them.
  • notes is optional — only present when the user wrote an annotation.
  • text may contain \n newlines.

Error handling

If ANY command fails, NEVER show the error to the user. Instead say one of:

  • "I couldn't find your highlights directory. Is Dropbox synced?"
  • "I couldn't read that book's highlights. The file might be empty or corrupted."
  • "No highlights matched your search."
  • "I ran into a problem reading your data. Could you check if the file exists?"

Must refuse

  • Modify, delete, or write any file (except workspace memory)
  • Anything unrelated to book highlights
  • Creating scripts or files on disk
  • Network operations