fish2018/pansou · error

[ ] 解析会话响应失败

Error message

[%s] 解析会话响应失败: %w

What it means

postSession unmarshals the raw bytes returned by postSessionRaw into nsgameSessionResponse to learn whether the session is active; this error means those bytes are not valid JSON for that struct. Since postSessionRaw already returns a 200 body, this points to a non-JSON body (HTML challenge/interstitial) or a changed session-response schema.

Solutions

  1. Log the raw body from postSessionRaw on unmarshal failure to see the actual content
  2. Update nsgameSessionResponse to match the current schema (especially the interface{} Data field's new shape)
  3. Ensure the challenge/issue steps succeeded and cookies (client.Jar) are retained before querying session status
  4. Re-run ensureSession from scratch if cookies were lost
  5. Check for a proxy stripping/rewriting the response

Example fix

// before
if err := json.Unmarshal(data, &response); err != nil {
    return false, fmt.Errorf("[%s] 解析会话响应失败: %w", p.Name(), err)
}
// after
if err := json.Unmarshal(data, &response); err != nil {
    return false, fmt.Errorf("[%s] 解析会话响应失败(响应开头: %.80q): %w", p.Name(), data, err)
}
Defensive patterns

Strategy: validation

Validate before calling

// before trusting session status, ensure the response is JSON
if len(data) > 0 && data[0] != '{' {
    return false, fmt.Errorf("session endpoint returned non-JSON body")
}

Try / catch

ok, err := plugin.CheckSession()
if err != nil {
    log.Warn("session status unknown, re-establishing", "err", err)
    if rerr := plugin.RefreshSession(); rerr != nil {
        return rerr
    }
}

Prevention

When it happens

Trigger: Session status endpoint returns an HTML login/WAF page instead of JSON, an empty body, or JSON whose data field no longer matches nsgameSessionResponse (e.g. data changed from bool to object).

Common situations: Anti-bot cookies were cleared or expired so the site serves a challenge page; site update altered the session response envelope; an intermediary proxy returns its own error page.

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/87316263a28d0f15. Report an issue: GitHub.

Appendix: source

Thrown at plugin/nsgame/nsgame.go:342

	nonce := solveChallenge(challenge.Data.Challenge, challenge.Data.DifficultyBits)
	if nonce == "" {
		return fmt.Errorf("[%s] 计算访问挑战失败", p.Name())
	}
	issueBody, _ := json.Marshal(map[string]string{"challenge": challenge.Data.Challenge, "nonce": nonce})
	if _, err := p.postSessionRaw(client, "/traffic/session/issue", issueBody); err != nil {
		return err
	}
	return nil
}

func (p *NSGameAsyncPlugin) postSession(client *http.Client, path string, body []byte) (bool, error) {
	data, err := p.postSessionRaw(client, path, body)
	if err != nil {
		return false, err
	}
	var response nsgameSessionResponse
	if err := json.Unmarshal(data, &response); err != nil {
		return false, fmt.Errorf("[%s] 解析会话响应失败: %w", p.Name(), err)
	}
	active, _ := response.Data.(bool)
	return active, nil
}

func (p *NSGameAsyncPlugin) postSessionRaw(client *http.Client, path string, body []byte) ([]byte, error) {
	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	defer cancel()
	var reader io.Reader
	if body != nil {
		reader = strings.NewReader(string(body))
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+path, reader)
	if err != nil {
		return nil, fmt.Errorf("[%s] 创建会话请求失败: %w", p.Name(), err)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")

View on GitHub (pinned to beaa561337)