fish2018/pansou · error
parse json failed
Error message
parse json failed: %w
What it means
Wrapped decode error in GetTopicDetail (plugin/discourse/discourse.go:495): the topic detail body downloaded successfully but is not valid JSON matching DetailResponse — typically because the site served an HTML challenge/error page with a 200 status. Indicates the response shape is wrong, not the network.
Solutions
- Dump the body head to check whether it is HTML instead of JSON
- Refresh/repair cloudscraper clearance so real JSON is served
- Verify the detail endpoint still returns the expected JSON structure for the forum's Discourse version
- Retry in case of a truncated response
Defensive patterns
Strategy: validation
Validate before calling
// pre-flight: confirm detail endpoint returns JSON
resp, _ := http.Get(baseURL + "/t/1.json")
head, _ := io.ReadAll(io.LimitReader(resp.Body, 1))
if len(head) == 1 && head[0] != '{' {
return errors.New("detail endpoint returns non-JSON (bot challenge or login page)")
} Try / catch
links, err := plugin.GetTopicDetail(id)
if err != nil && strings.Contains(err.Error(), "parse json failed") {
log.Printf("detail response not JSON; refreshing cloudscraper session")
return nil, ErrBotChallenge
} Prevention
- Keep scraper clearance current
- Verify API schema after Discourse upgrades
- Check for HTML error pages served with status 200
When it happens
Trigger: json.Unmarshal fails because the detail endpoint returned non-JSON content, e.g. an HTML Cloudflare challenge, login page, or error page with status 200.
Common situations: 站点改版导致接口结构变化;返回了被反爬注入脚本的 HTML。
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/b4af4bea86d7434a.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/discourse/discourse.go:495
return nil, fmt.Errorf("detail request failed: %w", err)
}
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
// 解析JSON响应
var detailResp DetailResponse
if err := json.Unmarshal(body, &detailResp); err != nil {
return nil, fmt.Errorf("parse json failed: %w", err)
}
// 提取第一个帖子的链接
if len(detailResp.PostStream.Posts) == 0 {
return nil, fmt.Errorf("no posts found")
}
mainPost := detailResp.PostStream.Posts[0]
// 从 link_counts 中提取网盘链接
var links []model.Link
for _, linkCount := range mainPost.LinkCounts {
// 跳过内部链接
if linkCount.Internal {
continue
}
// 判断是否为网盘链接并解析View on GitHub (pinned to beaa561337)