✨ feat(api): 统一响应格式并完善参数体系与文档
- 正文参数支持语义值(plain/html/keep/remove)与别名(img_mode/review_mode),URL 参数优先于配置 - 修复纯文本被段评强制包回 HTML、HTML 模式段评注入失效的问题 - 修复 PKCS7 填充弱校验导致漫画/正文偶发解密乱码返回 200 的问题 - 搜索结果展平:去 search_tabs 层与 selected_tab_idx/server_ab_params,提升 has_more/next_offset - 全接口解包字符串形式的 JSON 字段(category_schema 等),缓存出口幂等处理 - 序列化关闭 HTML 转义,video/audio 播放链接可直接复制播放 - video 响应透传 CENC 加密状态(key_seed/kid/encryption_method) - 新增 gzip 中间件,JSON/文本响应压缩约 81%,二进制透传 - 漫画默认输出模式改为 proxy(url 模式链接 404 且内容加密不可用) - openapi.yaml:补全 23 个路径参数定义、tab_type 完整枚举、CDN 签名说明,示例更新
This commit is contained in:
1 parent
bfb4290c15
commit
1a4997e031
25 files changed
+489
-199
No files matched your search
+33
-95
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fanqie/internal/common"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -62,8 +64,11 @@ func postReviewAPI(url string, requestBody interface{}) (map[string]interface{},
|
||||
return nil, fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 提取 data 字段
|
||||
// 提取 data 字段(并解包 JSON 字符串字段,如 post_data.content)
|
||||
if data, ok := result["data"].(map[string]interface{}); ok {
|
||||
if unwrapped, ok := UnwrapJSONStrings(data).(map[string]interface{}); ok {
|
||||
return unwrapped, nil
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -169,8 +174,11 @@ func GetParaReview(itemID, bookID, commentID string, para, sort, offset, count i
|
||||
return nil, fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 提取 data 字段
|
||||
// 提取 data 字段(并解包 JSON 字符串字段,如 post_data.content)
|
||||
if data, ok := result["data"].(map[string]interface{}); ok {
|
||||
if unwrapped, ok := UnwrapJSONStrings(data).(map[string]interface{}); ok {
|
||||
return unwrapped, nil
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -235,103 +243,33 @@ func GenerateParagraphsWithComments(itemID, bookID, content, baseURL string) (st
|
||||
strings.Contains(content, "<div") ||
|
||||
strings.Contains(content, "<article")
|
||||
|
||||
// 关键:如果检测到有段评数据,强制使用HTML模式(包裹<p>标签)
|
||||
// 因为<comment>标签需要XML兼容的格式
|
||||
hasAnyComments := len(commentData) > 0
|
||||
if hasAnyComments && !isHTMLContent {
|
||||
isHTMLContent = true
|
||||
// 纯文本模式下 <comment> 标签无法承载,保持纯文本原样返回(不注入段评);
|
||||
// 需要段评标记时配置 remove_html_tags: false 或 text_mode=html
|
||||
if !isHTMLContent {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
var result strings.Builder
|
||||
paragraphIndex := 0
|
||||
|
||||
for i, line := range lines {
|
||||
isLastLine := i == len(lines)-1
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
|
||||
// 完全空行:直接输出换行符
|
||||
if trimmedLine == "" {
|
||||
if !isLastLine {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 跳过图片标签和div标签
|
||||
if strings.HasPrefix(trimmedLine, "<img") ||
|
||||
strings.HasPrefix(trimmedLine, "<div") ||
|
||||
strings.HasPrefix(trimmedLine, "</div>") ||
|
||||
strings.HasPrefix(trimmedLine, "<article") ||
|
||||
strings.HasPrefix(trimmedLine, "</article") {
|
||||
result.WriteString(line)
|
||||
if !isLastLine {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果原内容已经有<p>标签,在</p>之前插入段评标记
|
||||
if strings.HasPrefix(trimmedLine, "<p>") && strings.HasSuffix(trimmedLine, "</p>") {
|
||||
// 提取<p>和</p>之间的内容
|
||||
innerContent := trimmedLine[3 : len(trimmedLine)-4]
|
||||
result.WriteString("<p>")
|
||||
result.WriteString(innerContent)
|
||||
|
||||
// 添加评论标记
|
||||
key := fmt.Sprintf("%d", paragraphIndex)
|
||||
if commentInfo, exists := commentData[key]; exists && commentInfo.Count > 0 {
|
||||
commentTag := fmt.Sprintf(`<comment ident="%s/review_html?item_id=%s&book_id=%s¶=%d" count="%d" />`,
|
||||
baseURL, itemID, bookID, paragraphIndex, commentInfo.Count)
|
||||
result.WriteString(commentTag)
|
||||
}
|
||||
|
||||
result.WriteString("</p>")
|
||||
if !isLastLine {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
paragraphIndex++
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果是HTML模式,包裹<p>标签
|
||||
if isHTMLContent {
|
||||
result.WriteString("<p>")
|
||||
result.WriteString(line)
|
||||
|
||||
// 添加评论标记
|
||||
key := fmt.Sprintf("%d", paragraphIndex)
|
||||
if commentInfo, exists := commentData[key]; exists && commentInfo.Count > 0 {
|
||||
commentTag := fmt.Sprintf(`<comment ident="%s/review_html?item_id=%s&book_id=%s¶=%d" count="%d" />`,
|
||||
baseURL, itemID, bookID, paragraphIndex, commentInfo.Count)
|
||||
result.WriteString(commentTag)
|
||||
}
|
||||
|
||||
result.WriteString("</p>")
|
||||
if !isLastLine {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
} else {
|
||||
// 纯文本模式:只在行尾添加comment标记
|
||||
result.WriteString(line)
|
||||
|
||||
// 添加评论标记(纯文本格式)
|
||||
key := fmt.Sprintf("%d", paragraphIndex)
|
||||
if commentInfo, exists := commentData[key]; exists && commentInfo.Count > 0 {
|
||||
commentTag := fmt.Sprintf(`<comment ident="%s/review_html?item_id=%s&book_id=%s¶=%d" count="%d" />`,
|
||||
baseURL, itemID, bookID, paragraphIndex, commentInfo.Count)
|
||||
result.WriteString(commentTag)
|
||||
}
|
||||
|
||||
if !isLastLine {
|
||||
result.WriteString("\n")
|
||||
// HTML 内容:在每个 </p> 前插入段评标记。
|
||||
// 兼容 <p idx="n">(HTML 模式结构)与无属性的 <p>;段号取 idx,无 idx 时按出现顺序。
|
||||
// header/article/div/img 等非段落标签不受影响。
|
||||
pRe := regexp.MustCompile(`(?s)<p\b[^>]*>.*?</p>`)
|
||||
idxRe := regexp.MustCompile(`idx="(\d+)"`)
|
||||
order := 0
|
||||
return pRe.ReplaceAllStringFunc(content, func(m string) string {
|
||||
defer func() { order++ }()
|
||||
para := order
|
||||
if idx := idxRe.FindStringSubmatch(m); idx != nil {
|
||||
if n, err := strconv.Atoi(idx[1]); err == nil {
|
||||
para = n
|
||||
}
|
||||
}
|
||||
|
||||
paragraphIndex++
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
if commentInfo, exists := commentData[fmt.Sprintf("%d", para)]; exists && commentInfo.Count > 0 {
|
||||
commentTag := fmt.Sprintf(`<comment ident="%s/review_html?item_id=%s&book_id=%s¶=%d" count="%d" />`,
|
||||
baseURL, itemID, bookID, para, commentInfo.Count)
|
||||
return m[:len(m)-len("</p>")] + commentTag + "</p>"
|
||||
}
|
||||
return m
|
||||
}), nil
|
||||
}
|
||||
|
||||
// extractDeviceID 从 device header 中提取 device_id
|
||||
|
||||
Reference in new issue
Block a user