{"record":{"id":"453ea0621a57ad26","repo":"fish2018/pansou","slug":"susu-d-453ea0","errorCode":null,"errorMessage":"[susu] 按钮列表请求返回状态码: %d","messagePattern":"\\[susu\\] 按钮列表请求返回状态码: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/susu/susu.go","lineNumber":359,"sourceCode":"\t\t\"post_id\": {postID},\n\t\t\"guest\":   {\"\"},\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, ButtonListURL, strings.NewReader(form.Encode()))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 创建按钮列表请求失败: %w\", err)\n\t}\n\tsetAPIHeaders(req, fmt.Sprintf(\"%s/%s.html\", BaseURL, postID))\n\n\tresp, err := p.doRequestWithRetry(client, req, MaxRetries)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[susu] 获取按钮列表失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\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)","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/susu/susu.go#L341-L377","documentation":"getLinks throws this when the button-list API responds with a non-200 status code after a successful transport round-trip. The body is not read or parsed; the status code is reported verbatim so callers can diagnose whether it's auth (403), rate limiting (429), server error (5xx), or a wrong-endpoint 404.","triggerScenarios":"The POST to ButtonListURL returns resp.StatusCode != http.StatusOK — e.g. WAF challenge (403), rate limit (429), server maintenance (503), or 404 after an API endpoint change.","commonSituations":"Anti-bot defenses flagging the API request (headers/cookies stale); hammering the API and getting 429; the site changing its API and returning 404/405; Cloudflare 5xx during incidents.","solutions":["Read the status code from the message; 403/503 usually means bot protection — refresh setAPIHeaders/cookies.","If 429, add delays between getLinks calls or implement backoff.","If 404/405, verify ButtonListURL against the current site API.","If 5xx, retry later or extend doRequestWithRetry to retry on 5xx.","Capture a sample response body (before discarding) to confirm WAF/challenge pages."],"exampleFix":"null","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"links, err := p.getLinks(postID)\nif err != nil {\n    if strings.Contains(err.Error(), \"按钮列表请求返回状态码: 429\") {\n        time.Sleep(backoff)\n        // retry once\n    }\n}","preventionTips":["Keep setAPIHeaders/referer current to avoid 403 challenges","Throttle getLinks calls per postID","Retry on 5xx/429 only; surface 4xx to the user","Alert on persistent non-200s indicating API drift"],"tags":["http-status","non-200","api-request","anti-bot"],"backgroundTag":"http-non-200-response","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"}