get_book_notes_and_highlights
Retrieve and organize book highlights and notes by chapter using a specified book ID. Customize results with chapter details, organization, and highlight style filters for structured reading insights.
Instructions
Get all highlights and notes for a specific book, organized by chapter
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| book_id | Yes | Book ID | |
| highlight_style | No | Highlight style filter, null means all | |
| include_chapters | No | Whether to include chapter information | |
| organize_by_chapter | No | Whether to organize by chapter |
Implementation Reference
- src/index.ts:480-762 (handler)Main execution logic for the tool: fetches book info, read progress, chapter structure, highlights (via getBookmarkList), notes (via getReviewList), processes and organizes them by chapter if requested, applies filters, formats timestamps, and returns structured JSON response.case "get_book_notes_and_highlights": { const bookId = String(request.params.arguments?.book_id || ""); const includeChapters = Boolean(request.params.arguments?.include_chapters !== false); const organizeByChapter = Boolean(request.params.arguments?.organize_by_chapter !== false); // 解析highlight_style参数 let highlightStyle = null; if (request.params.arguments?.highlight_style !== undefined && request.params.arguments?.highlight_style !== null) { highlightStyle = Number(request.params.arguments.highlight_style); } if (!bookId) { throw new Error("书籍ID不能为空"); } // 1. 获取书籍信息 const bookInfo = await wereadApi.getBookinfo(bookId); const bookTitle = bookInfo.title || ""; // 2. 获取书籍阅读进度信息 const readInfo = await wereadApi.getReadInfo(bookId); // 3. 获取章节信息 const chapterInfo = await wereadApi.getChapterInfo(bookId); // 4. 获取划线数据 const bookmarkResponse = await wereadApi.getBookmarkList(bookId); // 确认从响应中获取正确的划线数组 const highlights = Array.isArray(bookmarkResponse) ? bookmarkResponse : ((bookmarkResponse as any)?.updated || []); // 5. 获取笔记列表 const reviews = await wereadApi.getReviewList(bookId); // 获取开始阅读时间 const startReadingTime = readInfo.book?.startReadingTime || 0; const startReadingTimeISO = startReadingTime > 0 ? new Date(startReadingTime * 1000).toISOString() : ""; // 组织数据结构 const result: any = { book_id: bookId, book_title: bookTitle, book_info: { author: bookInfo.author || "", translator: bookInfo.translator || "", publisher: bookInfo.publisher || "", publish_time: bookInfo.publishTime || "", word_count: bookInfo.totalWords || 0, rating: bookInfo.newRating ? (bookInfo.newRating / 100) : 0, category: bookInfo.category || "" }, reading_status: { progress: readInfo.book?.progress || 0, reading_time: readInfo.book?.readingTime || 0, reading_time_formatted: formatReadingTime(readInfo.book?.readingTime || 0), start_reading_time: startReadingTimeISO, has_started_reading: startReadingTime > 0, last_read_time: readInfo.book?.updateTime ? new Date(readInfo.book.updateTime * 1000).toISOString() : "", finish_reading: bookInfo.finishReading === 1 }, total_highlights: highlights.length, total_notes: reviews.length, last_updated: new Date().toISOString() }; // 处理未分类的内容 if (organizeByChapter) { result.uncategorized = { highlights: [], notes: [] }; } else { result.highlights = []; result.notes = []; } // 如果需要按章节组织 if (includeChapters && organizeByChapter) { // 第一步:创建所有章节映射 - 从原始数据 const chapterMap: Record<string, any> = {}; // 将API返回的章节信息转换为我们需要的格式 const originalChapters = Object.values(chapterInfo); // 创建基本章节对象 - 简化结构,不保留index和level字段 originalChapters.forEach((chapter: any) => { // 确保chapterUid被转换为字符串 const chapterUidStr = String(chapter.chapterUid); chapterMap[chapterUidStr] = { uid: chapter.chapterUid, title: chapter.title, // 只在构建过程中使用level和index _level: chapter.level, _index: chapter.chapterIdx, children: [], highlights: [], notes: [] }; }); // 第二步:构建章节层级关系 const rootChapters: any[] = []; const chapterLevels: Record<number, any[]> = {}; // 按level分组 Object.values(chapterMap).forEach(chapter => { if (!chapterLevels[chapter._level]) { chapterLevels[chapter._level] = []; } chapterLevels[chapter._level].push(chapter); }); // 获取可用的level并排序 const levels = Object.keys(chapterLevels).map(Number).sort(); // 第一级作为根节点 if (levels.length > 0) { const topLevel = levels[0]; rootChapters.push(...chapterLevels[topLevel].sort((a, b) => a._index - b._index)); // 从第二级开始,找父章节 for (let i = 1; i < levels.length; i++) { const currentLevel = levels[i]; // 对当前级别的每个章节 chapterLevels[currentLevel].sort((a, b) => a._index - b._index).forEach(chapter => { // 找到前一级别中最近的章节作为父章节 const prevLevel = levels[i-1]; const prevLevelChapters = chapterLevels[prevLevel].sort((a, b) => a._index - b._index); let parent = null; for (let j = prevLevelChapters.length - 1; j >= 0; j--) { if (prevLevelChapters[j]._index < chapter._index) { parent = prevLevelChapters[j]; break; } } // 如果找到父章节,添加到其children中 if (parent) { parent.children.push(chapter); } else { // 如果找不到父章节,直接添加到根 rootChapters.push(chapter); } }); } } // 设置结果 result.chapters = rootChapters; // 第三步:处理划线数据 - 根据chapterUid分配到对应章节 let highlightsAddedCount = 0; let uncategorizedCount = 0; highlights.forEach((highlight: any) => { // 确保所有必要的字段都存在 if (!highlight.markText) { return; } const chapterUid = highlight.chapterUid; if (!chapterUid) { return; } if (highlightStyle !== null && highlight.colorStyle !== highlightStyle) { return; // 跳过不匹配的划线样式 } const highlightData = { text: highlight.markText, style: highlight.colorStyle || highlight.style || 0, create_time: new Date(highlight.createTime * 1000).toISOString() }; // 查找对应章节 - 直接以字符串形式查找 const chapterUidStr = String(chapterUid); const chapter = chapterMap[chapterUidStr]; if (chapter) { chapter.highlights.push(highlightData); highlightsAddedCount++; } else { result.uncategorized.highlights.push(highlightData); uncategorizedCount++; } }); // 第四步:处理笔记数据 - 根据chapterUid分配到对应章节 reviews.forEach((review: any) => { // 确保所有必要的字段都存在 if (!review.content) { return; } const chapterUid = review.chapterUid; if (!chapterUid) { return; } const noteData = { content: review.content, highlight_text: review.abstract || "", create_time: new Date(review.createTime * 1000).toISOString() }; // 查找对应章节 - 直接以字符串形式查找 const chapterUidStr = String(chapterUid); const chapter = chapterMap[chapterUidStr]; if (chapter) { chapter.notes.push(noteData); } else { result.uncategorized.notes.push(noteData); } }); // 第五步:清理不必要的字段并递归移除空章节 const cleanAndRemoveEmpty = (chapters: any[]): any[] => { return chapters.filter(chapter => { // 先清理章节对象中用于构建的临时字段 delete chapter._level; delete chapter._index; // 递归处理子章节 if (chapter.children && chapter.children.length > 0) { chapter.children = cleanAndRemoveEmpty(chapter.children); } // 章节不为空的条件:有划线、有笔记或有非空子章节 return ( (chapter.highlights && chapter.highlights.length > 0) || (chapter.notes && chapter.notes.length > 0) || (chapter.children && chapter.children.length > 0) ); }); }; result.chapters = cleanAndRemoveEmpty(result.chapters); } else if (!organizeByChapter) { // 非按章节组织模式 highlights.forEach((highlight: any) => { if (!highlight.markText || !highlight.chapterUid) return; if (highlightStyle !== null && highlight.colorStyle !== highlightStyle) return; result.highlights.push({ text: highlight.markText, style: highlight.colorStyle || highlight.style || 0, create_time: new Date(highlight.createTime * 1000).toISOString(), chapter_uid: highlight.chapterUid, chapter_title: chapterInfo[highlight.chapterUid]?.title || "未知章节" }); }); reviews.forEach((review: any) => { if (!review.content || !review.chapterUid) return; result.notes.push({ content: review.content, highlight_text: review.abstract || "", create_time: new Date(review.createTime * 1000).toISOString(), chapter_uid: review.chapterUid, chapter_title: chapterInfo[review.chapterUid]?.title || "未知章节" }); }); } return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }
- src/index.ts:93-121 (schema)Input schema definition including required book_id and optional parameters for chapter organization and highlight filtering.{ name: "get_book_notes_and_highlights", description: "Get all highlights and notes for a specific book, organized by chapter", inputSchema: { type: "object", properties: { book_id: { type: "string", description: "Book ID" }, include_chapters: { type: "boolean", description: "Whether to include chapter information", default: true }, organize_by_chapter: { type: "boolean", description: "Whether to organize by chapter", default: true }, highlight_style: { type: ["integer", "null"], description: "Highlight style filter, null means all", default: null } }, required: ["book_id"] } },
- src/index.ts:52-153 (registration)Registers the tool by including it in the list returned by ListToolsRequestHandler.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_bookshelf", description: "Get all books in the user's bookshelf with comprehensive statistics and categorization information", inputSchema: { type: "object", properties: {}, required: [] } }, { name: "search_books", description: "Search for books in the user's bookshelf by keywords and return matching books with details and reading progress", inputSchema: { type: "object", properties: { keyword: { type: "string", description: "Search keyword to match book title, author, translator or category" }, exact_match: { type: "boolean", description: "Whether to use exact matching, default is fuzzy matching", default: false }, include_details: { type: "boolean", description: "Whether to include detailed information", default: true }, max_results: { type: "integer", description: "Maximum number of results to return", default: 5 } }, required: ["keyword"] } }, { name: "get_book_notes_and_highlights", description: "Get all highlights and notes for a specific book, organized by chapter", inputSchema: { type: "object", properties: { book_id: { type: "string", description: "Book ID" }, include_chapters: { type: "boolean", description: "Whether to include chapter information", default: true }, organize_by_chapter: { type: "boolean", description: "Whether to organize by chapter", default: true }, highlight_style: { type: ["integer", "null"], description: "Highlight style filter, null means all", default: null } }, required: ["book_id"] } }, { name: "get_book_best_reviews", description: "Get popular reviews for a specific book", inputSchema: { type: "object", properties: { book_id: { type: "string", description: "Book ID" }, count: { type: "integer", description: "Number of reviews to return", default: 10 }, max_idx: { type: "integer", description: "Pagination index", default: 0 }, synckey: { type: "integer", description: "Sync key for pagination", default: 0 } }, required: ["book_id"] } }, ] }; });
- src/WeReadApi.ts:364-373 (helper)Helper method to fetch highlights/bookmarks from WeRead API endpoint.public async getBookmarkList(bookId: string): Promise<any[]> { await this.ensureInitialized(); return this.retry(async () => { const data = await this.makeApiRequest<any>(WEREAD_BOOKMARKLIST_URL, "get", { bookId }); let bookmarks = data.updated || []; // 确保每个划线对象格式一致 bookmarks = bookmarks.filter((mark: any) => mark.markText && mark.chapterUid); return bookmarks; }); }
- src/WeReadApi.ts:384-410 (helper)Helper method to fetch notes/reviews from WeRead API endpoint, processes response format.public async getReviewList(bookId: string): Promise<any[]> { await this.ensureInitialized(); return this.retry(async () => { const data = await this.makeApiRequest<any>(WEREAD_REVIEW_LIST_URL, "get", { bookId, listType: 4, maxIdx: 0, count: 0, listMode: 2, syncKey: 0 }); let reviews = data.reviews || []; // 转换成正确的格式 reviews = reviews.map((x: any) => x.review); // 为书评添加chapterUid reviews = reviews.map((x: any) => { if (x.type === 4) { return { chapterUid: 1000000, ...x }; } return x; }); return reviews; }); }