45 KiB
45 KiB
微信读书 EPUB 阅读器 — 数据结构与 API 协议定义
基于 WeRead v10.0.3 (Build 79) 逆向工程 数据结构来源: __objc_methtype + class_ro_t ivar 段 API 来源: WRBookNetwork 44 个 class method 签名
一、关键数据结构定义 (Header Files)
1.1 EPUB 解析数据结构
// ============================================================
#pragma mark - WREpubParser (EPUB 文件解析器)
// ============================================================
@interface WREpubParser : NSObject
@property (nonatomic, copy) NSString *epubFilePath; // EPUB 文件路径
@property (nonatomic, copy) NSString *opfFilePath; // content.opf 路径
@property (nonatomic, strong) NSError *lastError; // 最近错误
@property (nonatomic, weak) UIViewController *hostVC; // 宿主控制器
@property (nonatomic, strong) WRBook *book; // 书籍模型
@property (nonatomic, strong) WHAlbumInfo *albumInfo; // 专辑信息
// 解析 EPUB 结构
- (BOOL)parseEpubAtPath:(NSString *)path error:(NSError **)error;
// 获取章节目录 (NCX)
- (NSArray<NSDictionary *> *)tableOfContents;
// 获取资源文件映射 (manifest)
- (NSDictionary<NSString *, NSString *> *)resourceManifest;
// 获取 XHTML 章节文件路径
- (NSString *)filePathForChapterWithId:(NSString *)chapterId;
@end
// ============================================================
#pragma mark - WRBook (书籍模型)
// ============================================================
@interface WRBook : NSObject
@property (nonatomic, copy) NSString *bookId; // 书籍 ID
@property (nonatomic, copy) NSString *title; // 书名
@property (nonatomic, copy) NSString *author; // 作者
@property (nonatomic, copy) NSString *cover; // 封面 URL
@property (nonatomic, assign) WRBookFormat format; // epub/pdf/mp
@property (nonatomic, strong) NSArray<WRChapter *> *chapters; // 章节列表
@property (nonatomic, copy) NSString *encryptKey; // 加密密钥
@property (nonatomic, assign) BOOL isFinished; // 是否读完
@property (nonatomic, assign) BOOL isVIP; // 是否 VIP 书籍
typedef NS_ENUM(NSInteger, WRBookFormat) {
WRBookFormatEpub = 0,
WRBookFormatPDF = 1,
WRBookFormatMP = 2, // 公众号文章
};
@end
// ============================================================
#pragma mark - WRChapter (章节模型)
// ============================================================
@interface WRChapter : NSObject
@property (nonatomic, copy) NSString *chapterUid; // 章节唯一 ID
@property (nonatomic, assign) NSInteger chapterIdx; // 章节索引
@property (nonatomic, copy) NSString *title; // 章节标题
@property (nonatomic, assign) WRChapterFormat format; // 章节格式
@property (nonatomic, copy) NSString *fileId; // 文件 ID
@property (nonatomic, assign) NSUInteger filePosition; // 文件位置
@property (nonatomic, assign) NSUInteger fileSize; // 文件大小
@property (nonatomic, assign) BOOL isPaid; // 是否付费章节
@property (nonatomic, assign) BOOL isAvailable; // 是否可用
typedef NS_ENUM(NSInteger, WRChapterFormat) {
WRChapterFormatXHTML = 0,
WRChapterFormatHTML = 1,
WRChapterFormatTXT = 2,
};
@end
1.2 排版数据结构
// ============================================================
#pragma mark - DTHTMLElement (HTML 元素模型, 38 ivars)
// ============================================================
@interface DTHTMLElement : NSObject
// 树结构
@property (nonatomic, weak) DTHTMLElement *parent; // 父元素
@property (nonatomic, strong) NSMutableArray<DTHTMLElement *> *children; // 子元素
@property (nonatomic, copy) NSString *tagName; // 标签名 (p, h1, img...)
@property (nonatomic, copy) NSString *elementId; // id 属性
@property (nonatomic, copy) NSString *className; // class 属性
// 样式
@property (nonatomic, strong) DTCoreTextFontDescriptor *fontDescriptor; // 字体
@property (nonatomic, strong) DTCoreTextParagraphStyle *paragraphStyle; // 段落
@property (nonatomic, strong) UIColor *textColor; // 文字颜色
@property (nonatomic, strong) UIColor *backgroundColor; // 背景色
@property (nonatomic, assign) CGFloat textScale; // 文字缩放
@property (nonatomic, assign) CGFloat letterSpacing; // 字间距
// 附件
@property (nonatomic, strong) DTTextAttachment *textAttachment; // 附件 (图片/视频)
// 链接
@property (nonatomic, strong) NSURL *linkURL; // 超链接地址
// CSS 样式字典
@property (nonatomic, strong) NSDictionary *cssStyles; // 应用的 CSS 样式
// 边框与背景
@property (nonatomic, assign) DTBorderStyle borderTop; // 上边框
@property (nonatomic, assign) DTBorderStyle borderBottom; // 下边框
@property (nonatomic, strong) UIColor *borderColor; // 边框颜色
@property (nonatomic, strong) DTBackgroundImageStyle *backgroundImage; // 背景图
// 表格
@property (nonatomic, strong) DTTableStyle *tableStyle; // 表格样式
// 微信读书自定义属性
@property (nonatomic, assign) NSInteger verticalCenterStyle; // wr-vertical-center-style
@property (nonatomic, assign) BOOL pageRelate; // weread-page-relate
@property (nonatomic, assign) BOOL avoidPageBreakInside; // 断页保护
@property (nonatomic, assign) BOOL pageBreakAfter; // 元素后分页
@property (nonatomic, assign) BOOL pageBreakBefore; // 元素前分页
@property (nonatomic, strong) UIColor *pageBackgroundColor; // 页面背景色
@property (nonatomic, strong) NSURL *pageBackgroundImage; // 页面背景图
// 翻译
@property (nonatomic, assign) BOOL isTranslateTag; // 翻译标签
@property (nonatomic, assign) BOOL isTranslateNoStyle; // 无翻译样式
// 核心方法
- (void)applyStyleDictionary:(NSDictionary *)styles
isLatinLanguageBook:(BOOL)isLatin;
- (NSAttributedString *)attributedString;
- (void)interpretAttributes;
@end
// ============================================================
#pragma mark - DTCoreTextFontDescriptor (字体描述)
// ============================================================
@interface DTCoreTextFontDescriptor : NSObject
@property (nonatomic, copy) NSString *fontFamily; // 字体族 (如 "Source Han Serif CN")
@property (nonatomic, copy) NSString *fontName; // 字体名 (如 "SourceHanSerifCN-Medium")
@property (nonatomic, assign) CGFloat pointSize; // 字号 (pt)
@property (nonatomic, assign) BOOL bold; // 粗体
@property (nonatomic, assign) BOOL italic; // 斜体
@property (nonatomic, assign) uint32_t symbolicTraits; // 符号特征
// 匹配系统字体
- (CTFontRef)matchedFontDescriptor;
@end
// ============================================================
#pragma mark - DTCoreTextParagraphStyle (段落样式)
// ============================================================
@interface DTCoreTextParagraphStyle : NSObject
@property (nonatomic, assign) CTTextAlignment alignment; // 对齐方式
@property (nonatomic, assign) CGFloat lineSpacing; // 行间距
@property (nonatomic, assign) CGFloat paragraphSpacing; // 段间距
@property (nonatomic, assign) CGFloat paragraphSpacingBefore; // 段前间距
@property (nonatomic, assign) CGFloat firstLineHeadIndent; // 首行缩进
@property (nonatomic, assign) CGFloat headIndent; // 左缩进
@property (nonatomic, assign) CGFloat tailIndent; // 右缩进
@property (nonatomic, assign) CGFloat minimumLineHeight; // 最小行高
@property (nonatomic, assign) CGFloat maximumLineHeight; // 最大行高
@property (nonatomic, assign) CGFloat lineHeightMultiple; // 行高倍数
// 微信读书扩展
@property (nonatomic, assign) CGFloat defaultTabInterval; // 默认制表位
@end
// ============================================================
#pragma mark - DTTextAttachment (文本附件)
// ============================================================
@interface DTTextAttachment : NSObject
@property (nonatomic, assign) DTTextAttachmentType contentType; // 附件类型
@property (nonatomic, strong) NSData *contents; // 内容数据
@property (nonatomic, strong) NSURL *contentURL; // 内容 URL
@property (nonatomic, assign) CGSize displaySize; // 显示尺寸
@property (nonatomic, assign) CGSize originalSize; // 原始尺寸
@property (nonatomic, assign) CGFloat verticalAlignment; // 垂直对齐
typedef NS_ENUM(NSInteger, DTTextAttachmentType) {
DTTextAttachmentTypeImage = 0,
DTTextAttachmentTypeVideo = 1,
DTTextAttachmentTypeIframe = 2,
DTTextAttachmentTypeObject = 3,
};
@end
// ============================================================
#pragma mark - DTCSSStylesheet (CSS 样式表)
// ============================================================
@interface DTCSSStylesheet : NSObject
@property (nonatomic, strong) NSDictionary<NSString *, NSDictionary *> *rules;
// rules 格式: { "selector": { "property": "value", ... }, ... }
// 从 CSS 文本解析
+ (DTCSSStylesheet *)styleSheetWithCSSString:(NSString *)cssString;
// 合并另一个样式表 (后者覆盖前者)
- (void)mergeStylesheet:(DTCSSStylesheet *)other;
// 获取匹配选择器的样式
- (NSDictionary *)stylesForElement:(DTHTMLElement *)element;
@end
1.3 渲染数据结构
// ============================================================
#pragma mark - WRChapterData (章节数据模型, 12 ivars)
// ============================================================
@interface WRChapterData : NSObject
// 排版结果
@property (nonatomic, strong) NSMutableAttributedString *attributedString; // 排版后富文本
@property (nonatomic, strong) WRCoreTextLayouter *layouter; // 排版器
// 分页
@property (nonatomic, strong) NSArray<NSValue *> *pageRanges; // 每页 NSRange
// 标注
@property (nonatomic, strong) NSArray<WRPageHighlight *> *highlights; // 高亮列表
@property (nonatomic, strong) NSArray<WRPageUnderline *> *underlines; // 下划线列表
@property (nonatomic, strong) NSArray<WRPageMark *> *marks; // 书签标注
@property (nonatomic, strong) NSArray<NSDictionary *> *reviews; // 书评列表
@property (nonatomic, strong) NSArray *tempHighlights; // 临时高亮
@property (nonatomic, strong) NSArray *tempUnderlines; // 临时下划线
// 目录与搜索
@property (nonatomic, strong) NSArray<NSDictionary *> *outlineContents; // 目录
@property (nonatomic, strong) NSArray<NSDictionary *> *searchResults; // 搜索结果
// 书签
@property (nonatomic, strong) NSSet<NSString *> *bookmarkSet; // 书签集合
// 原始数据
@property (nonatomic, strong) NSAttributedString *originalString; // 原始富文本
// 核心方法
- (NSRange)rangeOfPage:(NSInteger)pageIndex;
- (void)addHighlightInRange:(NSRange)range key:(NSString *)key
itemId:(NSString *)itemId color:(UIColor *)color;
- (void)addReviewUnderlineInRange:(NSRange)range itemId:(NSString *)itemId
type:(WRReviewType)type;
- (void)deleteReviewUnderlineInRange:(NSRange)range type:(WRReviewType)type;
- (NSArray<NSDictionary *> *)generateOutlineContents;
- (NSInteger)freeTrialChapterCutOffRealStringLocation;
+ (void)addUnderLineToAttributedString:(NSMutableAttributedString *)attrStr
range:(NSRange)range
itemId:(NSString *)itemId
style:(WRUnderlineStyle)style
color:(UIColor *)color;
+ (NSInteger)freeTrialChapterCutOffStringLocaionWithAttributedString:
(NSAttributedString *)attrStr book:(WRBook *)book;
@end
// ============================================================
#pragma mark - WRChapterPageCount (分页计算, 4 ivars)
// ============================================================
@interface WRChapterPageCount : NSObject
@property (nonatomic, copy) NSString *bookId; // 书籍 ID
@property (nonatomic, copy) NSString *chapterUid; // 章节 ID
@property (nonatomic, copy) NSString *cacheKey; // 缓存键
@property (nonatomic, strong) NSArray<NSValue *> *pageRanges; // 页范围
+ (NSString *)currentCacheKeyWithBookId:(NSString *)bookId;
+ (NSValue *)rangeValueWithPageInfo:(NSDictionary *)pageInfo;
@end
1.4 标注数据结构
// ============================================================
#pragma mark - WRPageHighlight (页面高亮)
// ============================================================
@interface WRPageHighlight : NSObject
@property (nonatomic, assign) NSRange range; // 高亮范围
@property (nonatomic, copy) NSString *highlightKey; // 高亮唯一键
@property (nonatomic, copy) NSString *itemId; // 标注 ID
@property (nonatomic, strong) UIColor *color; // 高亮颜色
@property (nonatomic, assign) BOOL isTemporary; // 是否临时
@end
// ============================================================
#pragma mark - WRPageUnderline (页面下划线)
// ============================================================
@interface WRPageUnderline : NSObject
@property (nonatomic, assign) NSRange range; // 下划线范围
@property (nonatomic, copy) NSString *itemId; // 标注 ID
@property (nonatomic, assign) WRUnderlineStyle style; // 下划线样式
@property (nonatomic, strong) UIColor *color; // 颜色
typedef NS_ENUM(NSInteger, WRUnderlineStyle) {
WRUnderlineStyleSolid = 0, // 实线
WRUnderlineStyleDashed = 1, // 虚线
WRUnderlineStyleWavy = 2, // 波浪线
WRUnderlineStyleDotted = 3, // 点线
};
@end
// ============================================================
#pragma mark - WRPageMark (页面标注, 25 methods)
// ============================================================
@interface WRPageMark : NSObject
@property (nonatomic, assign) NSRange range; // 标注范围
@property (nonatomic, assign) WRMarkType type; // 标注类型
@property (nonatomic, copy) NSString *content; // 标注内容
@property (nonatomic, copy) NSString *itemId; // 标注 ID
@property (nonatomic, strong) NSDate *createTime; // 创建时间
@property (nonatomic, strong) UIColor *color; // 颜色
typedef NS_ENUM(NSInteger, WRMarkType) {
WRMarkTypeHighlight = 0, // 高亮
WRMarkTypeUnderline = 1, // 下划线
WRMarkTypeBookmark = 2, // 书签
WRMarkTypeNote = 3, // 笔记
WRMarkTypeReview = 4, // 书评
};
@end
// ============================================================
#pragma mark - WRBookmark (书签模型, 25 ivars)
// ============================================================
@interface WRBookmark : NSObject
@property (nonatomic, copy) NSString *bookId; // 书籍 ID
@property (nonatomic, copy) NSString *chapterUid; // 章节 ID
@property (nonatomic, copy) NSString *itemId; // 书签 ID
@property (nonatomic, assign) NSUInteger rangeLocation; // 范围起始
@property (nonatomic, assign) NSUInteger rangeLength; // 范围长度
@property (nonatomic, copy) NSString *abstractText; // 摘要文本
@property (nonatomic, assign) WRBookmarkType type; // 书签类型
@property (nonatomic, strong) UIColor *color; // 颜色
@property (nonatomic, assign) WRUnderlineStyle style; // 下划线样式
@property (nonatomic, copy) NSString *reviewId; // 关联书评 ID
@property (nonatomic, strong) NSDate *createTime; // 创建时间
@property (nonatomic, strong) NSDate *updateTime; // 更新时间
typedef NS_ENUM(NSInteger, WRBookmarkType) {
WRBookmarkTypeHighlight = 0,
WRBookmarkTypeUnderline = 1,
WRBookmarkTypeBookmark = 2,
WRBookmarkTypeNote = 3,
WRBookmarkTypeReview = 4,
};
@end
1.5 附件数据结构
// ============================================================
#pragma mark - WRPageImageAttachment (图片附件)
// ============================================================
@interface WRPageImageAttachment : NSObject
@property (nonatomic, copy) NSString *imageURL; // 图片 URL
@property (nonatomic, strong) UIImage *image; // 图片对象
@property (nonatomic, assign) CGSize displaySize; // 显示尺寸
@property (nonatomic, assign) CGSize originalSize; // 原始尺寸
@property (nonatomic, assign) NSInteger position; // 在文本中的位置
@property (nonatomic, assign) NSInteger verticalCenterStyle; // 居中方式
@property (nonatomic, assign) BOOL hasWhiteBackground; // 是否白底
@end
// ============================================================
#pragma mark - WRPageHyperlinksAttachment (超链接附件)
// ============================================================
@interface WRPageHyperlinksAttachment : NSObject
@property (nonatomic, strong) NSURL *url; // 链接 URL
@property (nonatomic, assign) NSRange range; // 文本范围
@property (nonatomic, copy) NSString *displayText; // 显示文本
@end
// ============================================================
#pragma mark - WRPageChapterToolAttachment (章节工具附件)
// ============================================================
@interface WRPageChapterToolAttachment : NSObject
@property (nonatomic, assign) WRChapterToolType type; // 工具类型
@property (nonatomic, strong) NSDictionary *data; // 工具数据
typedef NS_ENUM(NSInteger, WRChapterToolType) {
WRChapterToolTypeShare = 0, // 分享
WRChapterToolTypeReview = 1, // 书评
WRChapterToolTypeBookmark = 2, // 书签
WRChapterToolTypeNext = 3, // 下一章
};
@end
// ============================================================
#pragma mark - WRPageFlyleafAttachment (扉页附件)
// ============================================================
@interface WRPageFlyleafAttachment : NSObject
@property (nonatomic, assign) WRFlyleafType type; // 扉页类型
@property (nonatomic, strong) NSDictionary *data; // 扉页数据
typedef NS_ENUM(NSInteger, WRFlyleafType) {
WRFlyleafTypeCover = 0, // 封面
WRFlyleafTypeTitle = 1, // 标题页
WRFlyleafTypeAuthor = 2, // 作者页
WRFlyleafTypeIntro = 3, // 简介页
WRFlyleafTypeCatalog = 4, // 目录页
};
@end
// ============================================================
#pragma mark - WRPageCodeAttachment (代码块附件)
// ============================================================
@interface WRPageCodeAttachment : NSObject
@property (nonatomic, copy) NSString *codeString; // 代码内容
@property (nonatomic, copy) NSString *language; // 编程语言
@property (nonatomic, assign) NSInteger lineNumber; // 起始行号
@end
// ============================================================
#pragma mark - WRPageTableAttachment (表格附件)
// ============================================================
@interface WRPageTableAttachment : NSObject
@property (nonatomic, assign) NSInteger rows; // 行数
@property (nonatomic, assign) NSInteger columns; // 列数
@property (nonatomic, strong) NSArray<NSArray<NSString *> *> *cells; // 单元格数据
@end
二、API 与网络协议控制矩阵
2.1 书籍信息 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork loadBookInfoWithBookId: /api/book/info GET │
│ source:callback:] ?bookId=&source= │
│ │
│ +[WRBookNetwork loadBookReadInfoWithBookId: /api/book/readInfo GET │
│ callback:] ?bookId= │
│ │
│ +[WRBookNetwork loadBookReadInfoWithBookIdAndVid: /api/book/readInfo GET │
│ vid:callback:] ?bookId=&vid= │
│ │
│ +[WRBookNetwork loadBookReadDetailInfoWithBookId: /api/book/readDetail GET │
│ callback:] ?bookId= │
│ │
│ +[WRBookNetwork loadBookLectureAuthors: /api/book/authors GET │
│ callback:] ?bookId= │
│ │
│ +[WRBookNetwork loadArticleBookDetailWithBookId: /api/article/detail GET │
│ callback:] ?bookId= │
│ │
│ +[WRBookNetwork fetchLockInfoWithBookId: /api/book/lockInfo GET │
│ callback:] ?bookId= │
└────────────────────────────────────────────────────────────────────────────────┘
响应格式 (推断):
{
"bookId": "string",
"title": "string",
"author": "string",
"cover": "string (URL)",
"format": 0,
"chapters": [
{
"chapterUid": "string",
"chapterIdx": 0,
"title": "string",
"fileId": "string",
"filePosition": 0,
"fileSize": 0,
"isPaid": false
}
],
"encryptKey": "string",
"isFinished": false,
"isVIP": false
}
2.2 章节内容 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork loadTarForEpubBookId:chapter: /api/book/tar GET │
│ isPreload:] ?bookId=&chapterUid= │
│ &isPreload= │
│ 响应: 加密 ZIP 文件 (application/octet-stream) │
│ 文件名: {bookId}_DECRYPT.zip │
│ │
│ +[WRBookNetwork loadTarForEpubBookId:chapters: /api/book/tar GET │
│ isPreload:] ?bookId=&chapterUids= │
│ &isPreload= │
│ 批量下载: chapters 逗号分隔 │
│ │
│ +[WRBookNetwork loadChapterContentWithParam: /api/book/chapter GET │
│ callback:] ?bookId=&chapterUid= │
│ 参数: NSDictionary (bookId, chapterUid, format) │
│ │
│ +[WRBookNetwork fileContentWithChapter:book: 本地文件读取 - │
│ shouldRemoveHtmlTags:filterTranslateContent:] 从 plainBookDirectory 读取 │
│ │
│ +[WRBookNetwork chaptersInfoFromFile: 本地文件解析 - │
│ checkTranslate:] 解析 OPF/NCX │
└────────────────────────────────────────────────────────────────────────────────┘
2.3 预加载管理 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork savePreloadInfoWithDownloadParam: 本地存储 - │
│ chaptersStr:timeFlag:tmpFilePath:encryptKey:] NSUserDefaults │
│ │
│ +[WRBookNetwork clearPreloadKVWithBookId: 本地清理 - │
│ chapterUid:zipPath:] NSUserDefaults │
│ │
│ +[WRBookNetwork storeTarEpubImageToDiskWith 本地存储 - │
│ BookId:chapterUid:untarDirectory:] epubImage/ 目录 │
│ │
│ +[WRBookNetwork processEncryptedBookFileAtPath: 本地处理 - │
│ encryptKey:book:chapterUid:isFromReview:] 解密 + 存储 │
│ │
│ +[WRBookNetwork processChapterInfosFrom 本地处理 - │
│ ChapterDownload:bookId:] 解析章节信息 │
└────────────────────────────────────────────────────────────────────────────────┘
2.4 书签/标注 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork loadBookmarkListWithBookId: /api/book/bookmarks GET │
│ syncKey:callback:] ?bookId=&syncKey= │
│ │
│ +[WRBookNetwork addReview:shareToWechat: /api/review/add POST │
│ audioArticleId:outlineContent: body: {bookId, chapterUid, │
│ audioColumnId:callback:] content, range, type, ...} │
│ │
│ +[WRBookNetwork likeReviewById:isUnlike: /api/review/like POST │
│ withParams:callback:] body: {reviewId, isUnlike} │
│ │
│ +[WRBookNetwork dislikeReviewById:isDislike: /api/review/dislike POST │
│ withParams:callback:] body: {reviewId, isDislike} │
│ │
│ +[WRBookNetwork repostReview:reposted: /api/review/repost POST │
│ callback:] body: {reviewId, reposted} │
│ │
│ +[WRBookNetwork rewardReviewForId:price: /api/review/reward POST │
│ timestamp:callback:] body: {reviewId, price, ts} │
│ │
│ +[WRBookNetwork postReviewHideWithBookId: /api/review/hide POST │
│ hide:callback:] body: {bookId, hide} │
│ │
│ +[WRBookNetwork loadTopicReviewlist: /api/review/topic GET │
│ callback:] ?topicId= │
│ │
│ +[WRBookNetwork loadRelatedBooksForReviewDetail: /api/review/related GET │
│ callback:] ?reviewId= │
└────────────────────────────────────────────────────────────────────────────────┘
2.5 阅读进度 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork uploadBookProgressAndReadingTime: /api/book/progress POST │
│ callback:offlineCallback:] body: {bookId, chapterUid, │
│ progress, readingTime, ...} │
│ │
│ +[WRBookNetwork setFinishReading:withBookId: /api/book/finish POST │
│ callback:] body: {bookId, isFinished} │
│ │
│ +[WRBookNetwork setIsStartReading:withBookId: /api/book/start POST │
│ callback:] body: {bookId} │
│ │
│ +[WRBookNetwork markReadingStatus:bookIds: /api/book/status POST │
│ isCancel:callback:] body: {bookIds[], isCancel} │
│ │
│ +[WRBookNetwork markReadingStatus:withBookId: /api/book/status POST │
│ isCancel:withFinishInfo:callback:] body: {bookId, isCancel, │
│ finishInfo} │
│ │
│ +[WRBookNetwork automaticallyMarkFinishReading /api/book/autoFinish POST │
│ WithBookId:callback:] body: {bookId} │
│ │
│ +[WRBookNetwork addMileStone:callback:] /api/book/milestone POST │
│ body: {bookId, milestone} │
└────────────────────────────────────────────────────────────────────────────────┘
2.6 搜索 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork searchResultsForBook: /api/search GET │
│ chapterUid:searchString:posBeg: ?bookId=&chapterUid= │
│ posEnd:mode:callback:] &keyword=&posBeg= │
│ &posEnd=&mode= │
│ mode: 0=精确 1=模糊 2=正则 │
│ │
│ +[WRBookNetwork searchResultsForLocalBook: 本地搜索 - │
│ chapterUid:searchString:posBeg: 遍历 NSAttributedString │
│ posEnd:mode:callback:] │
└────────────────────────────────────────────────────────────────────────────────┘
2.7 翻译 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork pollingChapterTranslateStatus /api/book/translate GET │
│ WithBookId:isFreeTrialActive: ?bookId=&chapterUid= │
│ referenceLocationDict:chapterTranslations: &isFreeTrial= │
│ from:] │
│ │
│ +[WRBookNetwork _removeTranslateHtml:] 本地处理 - │
│ 移除翻译 HTML 标签 │
└────────────────────────────────────────────────────────────────────────────────┘
2.8 付费/会员 API
┌────────────────────────────────────────────────────────────────────────────────┐
│ 方法签名 推断端点 HTTP 方法 │
├────────────────────────────────────────────────────────────────────────────────┤
│ +[WRBookNetwork fetchLockInfoWithBookId: /api/book/lock GET │
│ callback:] ?bookId= │
│ │
│ +[WRBookNetwork resetChapterPaidIfNeeded /api/book/resetPaid POST │
│ WithBookId:chapterUid:] body: {bookId, chapterUid} │
│ │
│ +[WRBookNetwork loadReadTimeWelfareAction /api/welfare/readTime GET │
│ WithBookId:opt:secretKey:firstEnter:] ?bookId=&opt=&secretKey= │
│ │
│ +[WRBookNetwork checkFMCards:callback:] /api/fm/cards GET │
│ │
│ +[WRBookNetwork loadFMCardsWithBookId: /api/fm/cards GET │
│ withSynckey:withListType:withFilterType: ?bookId=&synckey= │
│ withMaxIdx:withCount:] &listType=&filterType= │
└────────────────────────────────────────────────────────────────────────────────┘
三、本地存储 Schema
3.1 文件系统布局
┌─────────────────────────────────────────────────────────────────────┐
│ App 沙箱 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Documents/ │
│ ├── {bookId}/ │
│ │ ├── plainBookDirectory/ ← 解密后的 EPUB XHTML 文件 │
│ │ │ ├── META-INF/container.xml │
│ │ │ ├── OEBPS/content.opf │
│ │ │ ├── OEBPS/toc.ncx │
│ │ │ ├── OEBPS/Text/chapter1.xhtml │
│ │ │ ├── OEBPS/Text/chapter2.xhtml │
│ │ │ └── ... │
│ │ ├── epubImage/ ← 书籍图片缓存 │
│ │ │ ├── image1.jpg │
│ │ │ ├── image2.png │
│ │ │ └── ... │
│ │ └── {bookId}_DECRYPT.zip ← 下载的加密 ZIP (可能已删除) │
│ │ │
│ └── ... (其他书籍) │
│ │
│ Library/ │
│ ├── {cachePath}/ │
│ │ └── epubImage/ ← SDWebImage 缓存 │
│ │ └── com.hackemist.SDWebImageCache.epubImage/ │
│ ├── Caches/ │
│ │ └── chapterCache/ ← 章节排版缓存 │
│ └── Preferences/ │
│ └── com.tencent.weread.plist ← NSUserDefaults │
│ │
│ tmp/ │
│ └── {临时解压目录} ← 下载解压临时目录 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 NSUserDefaults Keys
// 阅读进度
NSString *const kWRReadingProgressKey = @"WRReadingProgress_{bookId}";
// 格式: {chapterUid, chapterIdx, filePosition, stringLocation, pageOfChapter, lastReadDate}
// 字体设置
NSString *const kWRFontFamilyKey = @"WRFontFamily"; // 字体族名
NSString *const kWRFontSizeKey = @"WRFontSize"; // 字号 (默认 18)
NSString *const kWRLineHeightKey = @"WRLineHeight"; // 行高 (默认 1.8)
NSString *const kWRParagraphSpacingKey = @"WRParagraphSpacing"; // 段间距
NSString *const kWRFirstIndentKey = @"WRFirstIndent"; // 首行缩进
// 主题设置
NSString *const kWRThemeKey = @"WRTheme"; // 主题 (light/dark/sepia)
NSString *const kWRBrightnessKey = @"WRBrightness"; // 亮度
// 翻页设置
NSString *const kWRPageTurningStyleKey = @"WRPageTurningStyle"; // 翻页样式 (curl/scroll)
NSString *const kWRAutoReadKey = @"WRAutoRead"; // 自动阅读
// 繁简转换
NSString *const kWRCht2sKey = @"WRCht2s_{bookId}"; // 繁简转换状态
// 预加载
NSString *const kWRPreloadSettingKey = @"WRPreloadSetting"; // 预加载设置
NSString *const kWRPreloadEncryptKeyKey = @"WRPreloadEncryptKey_{bookId}_{chapterUid}";
NSString *const kWRPreloadFileNameKey = @"WRPreloadFileName_{bookId}_{key}";
3.3 Keychain Keys
// 每本书的加密密钥
NSString *const kWRBookEncryptKeyKey = @"com.weread.encrypt.{bookId}";
// 存储在 Keychain 中, 标记为 kSecAttrAccessibleAfterFirstUnlock
// 用户凭证
NSString *const kWRUserTokenKey = @"com.weread.user.token";
NSString *const kWRUserVidKey = @"com.weread.user.vid";
3.4 NSAttributedString 自定义属性键
// 微信读书在 NSAttributedString 中注入的自定义属性
// 用于在排版结果中传递页面布局元数据
NSString *const DTPageBackgroundColorAttribute = @"DTPageBackgroundColor";
NSString *const DTPageBackgroundImageAttribute = @"DTPageBackgroundImage";
NSString *const DTPageBackgroundImagePathAttribute = @"DTPageBackgroundImagePath";
NSString *const DTPageBreakAfterAttribute = @"DTPageBreakAfter";
NSString *const DTPageBreakBeforeAttribute = @"DTPageBreakBefore";
NSString *const DTPageBreakInsideAvoidAttribute = @"DTPageBreakInsideAvoid";
NSString *const DTPageRelateAttribute = @"DTPageRelate";
NSString *const DTPageSize = @"DTPageSize";
NSString *const DTPageFlippingStyle = @"DTPageFlippingStyle";
NSString *const DTHTMLVerticalCenterAttribute = @"DTHTMLVerticalCenter";
NSString *const DTHTMLTranslateTagAttribute = @"DTHTMLTranslateTag";
NSString *const DTHTMLTranslateNoStyleAttribute = @"DTHTMLTranslateNoStyle";
// 标注相关属性
NSString *const WRHighlightAttributeKey = @"com.weread.highlight";
NSString *const WRUnderlineAttributeKey = @"com.weread.underline";
NSString *const WRBookmarkAttributeKey = @"com.weread.bookmark";
NSString *const WRReviewAttributeKey = @"com.weread.review";
// 附件属性
NSString *const DTTextAttachmentAttribute = @"DTTextAttachment";
NSString *const WRChapterToolAttachmentAttribute = @"WRChapterToolAttachment";
NSString *const WRFlyleafAttachmentAttribute = @"WRFlyleafAttachment";