{"record":{"id":"f5ab3df267158d40","repo":"fish2018/pansou","slug":"susu-w-f5ab3d","errorCode":null,"errorMessage":"[susu] 获取按钮列表失败: %w","messagePattern":"\\[susu\\] 获取按钮列表失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/susu/susu.go","lineNumber":355,"sourceCode":"\t\treturn cachedLinks.([]model.Link), nil\n\t}\n\n\tform := url.Values{\n\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 {","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/susu/susu.go#L337-L373","documentation":"getLinks wraps errors from doRequestWithRetry when fetching the button-list API endpoint. Like [700], the retried POST to ButtonListURL never produced a response: connection failures, timeouts, or retries exhausted over MaxRetries attempts. The 20-second context timeout also surfaces as this error when exceeded.","triggerScenarios":"Calling getLinks (via the resolver chain from an anonymous goroutine/caller) when the POST to ButtonListURL fails at transport level: ctx deadline exceeded after 20s, connection refused, TLS failure, or MaxRetries exhausted.","commonSituations":"The API endpoint being slower than the 20s context timeout under load; the API host blocked or DNS-failing; the site rate-limiting repeated getLinks calls; container without outbound access to the API host.","solutions":["Unwrap the error chain to see the root cause (deadline exceeded vs connection refused).","Raise the 20s context timeout if the API is legitimately slow.","Confirm the API host is reachable (curl -X POST ButtonListURL with the same form) from the host.","Check MaxRetries/backoff settings; increase them for flaky networks.","If the endpoint moved, update ButtonListURL."],"exampleFix":"null","handlingStrategy":"retry","validationCode":"conn, err := net.DialTimeout(\"tcp\", apiHost+\":443\", 5*time.Second)\nif err != nil { /* API host unreachable, skip link resolution */ }\nconn.Close()","typeGuard":null,"tryCatchPattern":"links, err := p.getLinks(postID)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        // increase timeout or retry with backoff\n    }\n    log.Printf(\"button-list fetch failed: %v\", err)\n    return nil, err\n}","preventionTips":["Size the context timeout to worst-case API latency","Enable retries with exponential backoff","Monitor API endpoint reachability","Cache successful link resolutions to reduce API calls"],"tags":["network","timeout","api-request","retry-exhausted"],"backgroundTag":"api-request-failed","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"}