{"record":{"id":"4d8c6f3579a0288b","repo":"fish2018/pansou","slug":"susu-s","errorCode":null,"errorMessage":"[susu] 帖子 %s 没有可用下载按钮","messagePattern":"\\[susu\\] 帖子 (.+?) 没有可用下载按钮","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugin/susu/susu.go","lineNumber":377,"sourceCode":"\t\treturn nil, fmt.Errorf(\"[susu] 按钮列表请求返回状态码: %d\", resp.StatusCode)\n\t}\n\n\trespBody, err := io.ReadAll(io.LimitReader(resp.Body, 2<<20))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 读取按钮列表失败: %w\", err)\n\t}\n\n\tvar groups []downloadGroup\n\tif err := json.Unmarshal(respBody, &groups); err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 解析按钮列表失败: %w\", err)\n\t}\n\n\ttotalButtons := 0\n\tfor _, group := range groups {\n\t\ttotalButtons += len(group.Button)\n\t}\n\tif totalButtons == 0 {\n\t\treturn nil, fmt.Errorf(\"[susu] 帖子 %s 没有可用下载按钮\", postID)\n\t}\n\n\tlinkChan := make(chan model.Link, totalButtons)\n\tvar wgLinks sync.WaitGroup\n\tsemaphore := make(chan struct{}, MaxConcurrency)\n\n\tfor groupIndex, group := range groups {\n\t\tfor buttonIndex := range group.Button {\n\t\t\twgLinks.Add(1)\n\t\t\tgo func(index, i int) {\n\t\t\t\tdefer wgLinks.Done()\n\t\t\t\tsemaphore <- struct{}{}\n\t\t\t\tdefer func() { <-semaphore }()\n\n\t\t\t\tlink, err := p.getButtonDetail(client, postID, index, i)\n\t\t\t\tif err == nil && link.URL != \"\" {\n\t\t\t\t\tlinkChan <- link\n\t\t\t\t}","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/susu/susu.go#L359-L395","documentation":"getLinks throws this when the button-list JSON decoded successfully but the total count of buttons across all groups is zero — the post exists (or the API pretends it does) yet exposes no download buttons. This is a domain-level empty-result guard, typically meaning the postID is wrong, the post was deleted, or it never had download content.","triggerScenarios":"After json.Unmarshal into []downloadGroup, summing len(group.Button) over all groups yields 0 — empty array, groups with empty Button slices, or a structurally valid but contentless response (e.g. API returning [] for unknown postIDs).","commonSituations":"Passing a stale or mistyped postID; the post's downloads were removed or the post deleted; scraping a post type that uses a different download mechanism; API returning empty results for region/language variants.","solutions":["Verify the postID by opening BaseURL/<postID>.html in a browser and confirming download buttons render.","Check that the JSON groups actually map to the current site structure (a field rename can silently zero out Button).","Handle gracefully upstream: treat as 'no downloadable content' rather than a hard failure when appropriate.","Refresh cached search results — the post may have been deleted since the search index cached it."],"exampleFix":"null","handlingStrategy":"fallback","validationCode":"// resolve links only for postIDs known to have content\nif postID == \"\" || !postIDPattern.MatchString(postID) {\n    // skip getLinks for malformed IDs\n}","typeGuard":null,"tryCatchPattern":"links, err := p.getLinks(postID)\nif err != nil && strings.Contains(err.Error(), \"没有可用下载按钮\") {\n    // treat as 'no downloadable content', not a hard error\n    return model.LinkResults{}, nil\n}","preventionTips":["Validate postIDs come from a fresh search, not stale caches","Confirm posts still exist before resolving links","Expect legitimately empty posts and handle them gracefully","Check the downloadGroup struct maps the API fields (a rename can zero out Button)"],"tags":["empty-result","domain-logic","post-id","scraping"],"backgroundTag":"empty-result-set","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}