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

  1. Dump the body head to check whether it is HTML instead of JSON
  2. Refresh/repair cloudscraper clearance so real JSON is served
  3. Verify the detail endpoint still returns the expected JSON structure for the forum's Discourse version
  4. 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

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)