diff --git a/README.md b/README.md index 6b3e1cf..d9f0f7c 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,7 @@ curl -X POST http://localhost:8888/api/auth/logout | plugins | string[] | 否 | 指定搜索的插件列表,不指定则搜索全部插件 | | cloud_types | string[] | 否 | 指定返回的网盘类型列表,支持:baidu、aliyun、quark、tianyi、uc、mobile、115、pikpak、xunlei、123、magnet、ed2k,不指定则返回所有类型 | | ext | object | 否 | 扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} | +| filter | object | 否 | 过滤配置,用于过滤返回结果。格式:{"include":["关键词1","关键词2"],"exclude":["排除词1","排除词2"]}。include为包含关键词列表(OR关系),exclude为排除关键词列表(AND关系) | **GET请求参数**: @@ -418,6 +419,7 @@ curl -X POST http://localhost:8888/api/auth/logout | plugins | string | 否 | 指定搜索的插件列表,使用英文逗号分隔多个插件名,不指定则搜索全部插件 | | cloud_types | string | 否 | 指定返回的网盘类型列表,使用英文逗号分隔多个类型,支持:baidu、aliyun、quark、tianyi、uc、mobile、115、pikpak、xunlei、123、magnet、ed2k,不指定则返回所有类型 | | ext | string | 否 | JSON格式的扩展参数,用于传递给插件的自定义参数,如{"title_en":"English Title", "is_all":true} | +| filter | string | 否 | JSON格式的过滤配置,用于过滤返回结果。格式:{"include":["关键词1","关键词2"],"exclude":["排除词1","排除词2"]} | **POST请求示例**: @@ -448,6 +450,17 @@ curl -X POST http://localhost:8888/api/search \ "kw": "速度与激情", "res": "merge" }' + +# 使用过滤器(只返回包含“合集”或“全集”,且不包含“预告”的结果) +curl -X POST http://localhost:8888/api/search \ + -H "Content-Type: application/json" \ + -d '{ + "kw": "唐朝诡事录", + "filter": { + "include": ["合集", "全集"], + "exclude": ["预告", "花絮"] + } + }' ``` **GET请求示例**: @@ -459,6 +472,9 @@ curl "http://localhost:8888/api/search?kw=速度与激情&res=merge&src=tg" # 启用认证时(需要添加Authorization头) curl "http://localhost:8888/api/search?kw=速度与激情&res=merge" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + +# 使用过滤器(GET方式需要URL编码JSON) +curl "http://localhost:8888/api/search?kw=唐朝诡事录&filter=%7B%22include%22%3A%5B%22合集%22%2C%22全集%22%5D%2C%22exclude%22%3A%5B%22预告%22%5D%7D" ``` **成功响应**: diff --git a/api/filter.go b/api/filter.go new file mode 100644 index 0000000..9328eaa --- /dev/null +++ b/api/filter.go @@ -0,0 +1,140 @@ +package api + +import ( + "pansou/model" + "strings" +) + +// applyResultFilter 应用过滤器到搜索响应 +func applyResultFilter(response model.SearchResponse, filter *model.FilterConfig, resultType string) model.SearchResponse { + if filter == nil || (len(filter.Include) == 0 && len(filter.Exclude) == 0) { + return response + } + + // 预处理关键词(转小写) + includeKeywords := make([]string, len(filter.Include)) + for i, kw := range filter.Include { + includeKeywords[i] = strings.ToLower(kw) + } + + excludeKeywords := make([]string, len(filter.Exclude)) + for i, kw := range filter.Exclude { + excludeKeywords[i] = strings.ToLower(kw) + } + + // 根据结果类型决定过滤策略 + if resultType == "merged_by_type" || resultType == "" { + // 过滤 merged_by_type 的 note 字段 + response.MergedByType = filterMergedByType(response.MergedByType, includeKeywords, excludeKeywords) + + // 重新计算 total + total := 0 + for _, links := range response.MergedByType { + total += len(links) + } + response.Total = total + } else if resultType == "all" || resultType == "results" { + // 过滤 results 的 title 和 links 的 work_title + response.Results = filterResults(response.Results, includeKeywords, excludeKeywords) + response.Total = len(response.Results) + + // 如果是 all 类型,也需要过滤 merged_by_type + if resultType == "all" { + response.MergedByType = filterMergedByType(response.MergedByType, includeKeywords, excludeKeywords) + } + } + + return response +} + +// filterMergedByType 过滤 merged_by_type 中的链接 +func filterMergedByType(mergedLinks model.MergedLinks, includeKeywords, excludeKeywords []string) model.MergedLinks { + if mergedLinks == nil { + return nil + } + + filtered := make(model.MergedLinks) + + for linkType, links := range mergedLinks { + filteredLinks := make([]model.MergedLink, 0) + + for _, link := range links { + if matchFilter(link.Note, includeKeywords, excludeKeywords) { + filteredLinks = append(filteredLinks, link) + } + } + + // 只添加非空的类型 + if len(filteredLinks) > 0 { + filtered[linkType] = filteredLinks + } + } + + return filtered +} + +// filterResults 过滤 results 数组 +func filterResults(results []model.SearchResult, includeKeywords, excludeKeywords []string) []model.SearchResult { + if results == nil { + return nil + } + + filtered := make([]model.SearchResult, 0) + + for _, result := range results { + // 先检查 title 是否匹配 + if !matchFilter(result.Title, includeKeywords, excludeKeywords) { + continue + } + + // title 匹配后,过滤 links 中的 work_title + filteredLinks := make([]model.Link, 0) + for _, link := range result.Links { + // 如果 link 有 work_title,检查它;否则使用 result.Title + checkText := link.WorkTitle + if checkText == "" { + checkText = result.Title + } + + if matchFilter(checkText, includeKeywords, excludeKeywords) { + filteredLinks = append(filteredLinks, link) + } + } + + // 只有有链接的结果才添加 + if len(filteredLinks) > 0 { + result.Links = filteredLinks + filtered = append(filtered, result) + } + } + + return filtered +} + +// matchFilter 检查文本是否匹配过滤条件 +func matchFilter(text string, includeKeywords, excludeKeywords []string) bool { + lowerText := strings.ToLower(text) + + // 检查 exclude(任一匹配则排除) + for _, kw := range excludeKeywords { + if strings.Contains(lowerText, kw) { + return false + } + } + + // 检查 include(如果有 include 列表,必须至少匹配一个) + if len(includeKeywords) > 0 { + matched := false + for _, kw := range includeKeywords { + if strings.Contains(lowerText, kw) { + matched = true + break + } + } + if !matched { + return false + } + } + + return true +} diff --git a/api/handler.go b/api/handler.go index b46e17f..63b31c3 100644 --- a/api/handler.go +++ b/api/handler.go @@ -130,6 +130,17 @@ func SearchHandler(c *gin.Context) { if ext == nil { ext = make(map[string]interface{}) } + + // 处理filter参数,JSON格式 + var filter *model.FilterConfig + filterStr := c.Query("filter") + if filterStr != "" && filterStr != " " { + filter = &model.FilterConfig{} + if err := jsonutil.Unmarshal([]byte(filterStr), filter); err != nil { + c.JSON(http.StatusBadRequest, model.NewErrorResponse(400, "无效的filter参数格式: "+err.Error())) + return + } + } req = model.SearchRequest{ Keyword: keyword, @@ -141,6 +152,7 @@ func SearchHandler(c *gin.Context) { Plugins: plugins, CloudTypes: cloudTypes, // 添加cloud_types到请求中 Ext: ext, + Filter: filter, } } else { // POST方式:从请求体获取 @@ -200,6 +212,11 @@ func SearchHandler(c *gin.Context) { return } + // 应用过滤器 + if req.Filter != nil { + result = applyResultFilter(result, req.Filter, req.ResultType) + } + // 包装SearchResponse到标准响应格式中 response := model.NewSuccessResponse(result) jsonData, _ := jsonutil.Marshal(response) diff --git a/model/request.go b/model/request.go index 964e973..9b643f7 100644 --- a/model/request.go +++ b/model/request.go @@ -1,5 +1,11 @@ package model +// FilterConfig 过滤配置 +type FilterConfig struct { + Include []string `json:"include,omitempty"` // 包含关键词列表(OR关系) + Exclude []string `json:"exclude,omitempty"` // 排除关键词列表(AND关系) +} + // SearchRequest 搜索请求参数 type SearchRequest struct { Keyword string `json:"kw" binding:"required"` // 搜索关键词 @@ -11,4 +17,5 @@ type SearchRequest struct { Plugins []string `json:"plugins"` // 指定搜索的插件列表,不指定则搜索全部插件 Ext map[string]interface{} `json:"ext"` // 扩展参数,用于传递给插件的自定义参数 CloudTypes []string `json:"cloud_types"` // 指定返回的网盘类型列表,不指定则返回所有类型 + Filter *FilterConfig `json:"filter,omitempty"` // 过滤配置,用于过滤返回结果 } \ No newline at end of file