fish2018/pansou · error

解析 Anubis 验证题目失败

Error message

解析 Anubis 验证题目失败: %w

What it means

parseAnubisChallenge extracts the challenge JSON from the gate page via regex and unmarshals it into anubisChallengeDocument after HTML-unescaping. This error wraps a json.Unmarshal failure (miosou.go:262) — the embedded JSON does not match the expected schema.

Solutions

  1. Update the anubisChallengeDocument struct tags to match the current Anubis challenge JSON schema.
  2. Log the raw matched blob (html.UnescapeString(match[1])) to see the actual format and diff it against the struct.
  3. Verify the response was not truncated (1 MiB LimitReader) and no proxy rewrote the page.
  4. Run TestParseAndSolveAnubisChallenge with a fixture captured from the live server to confirm the parser.

Example fix

// before
type anubisChallengeDocument struct {
    Rules struct {
        Algorithm  string `json:"algorithm"`
        Difficulty int    `json:"difficulty"`
    } `json:"rules"`
    ...
}
// after — align tags with the server's current schema, e.g.
type anubisChallengeDocument struct {
    Rules struct {
        Algorithm  string `json:"algorithm"`
        Difficulty int    `json:"difficulty"`
    } `json:"rules"`
    Challenge struct {
        ID         string `json:"id"`
        RandomData string `json:"random_data"` // renamed upstream
    } `json:"challenge"`
}
Defensive patterns

Strategy: validation

Validate before calling

var probe map[string]any
blob := html.UnescapeString(string(match[1]))
if err := json.Unmarshal([]byte(blob), &probe); err != nil {
    return fmt.Errorf("challenge blob is not valid JSON: %w", err)
}
if _, ok := probe["challenge"]; !ok || _, ok := probe["rules"]; !ok {
    return fmt.Errorf("challenge JSON missing expected keys")
}

Type guard

func looksLikeAnubisChallenge(blob string) bool {
    var probe struct {
        Rules     map[string]any `json:"rules"`
        Challenge map[string]any `json:"challenge"`
    }
    return json.Unmarshal([]byte(blob), &probe) == nil &&
        probe.Rules != nil && probe.Challenge != nil
}

Try / catch

challenge, found, err := parseAnubisChallenge(body)
if err != nil {
    if strings.Contains(err.Error(), "解析 Anubis 验证题目失败") {
        // schema drift: log raw blob and update struct tags
    }
    return err
}

Prevention

When it happens

Trigger: The regex matched a challenge blob but json.Unmarshal fails: Anubis changed the JSON shape (renamed fields rules/challenge/id/randomData/algorithm/difficulty), HTML entity decoding left invalid JSON, or the body was truncated/corrupted.

Common situations: An Anubis server upgrade changing the challenge document format; a proxy/CDN mangling or truncating the page; a localized or customized Anubis deployment emitting different JSON.

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/97cdba7c1d7c680b. Report an issue: GitHub.

Appendix: source

Thrown at plugin/miosou/miosou.go:262

type anubisChallengeDocument struct {
	Rules struct {
		Algorithm  string `json:"algorithm"`
		Difficulty int    `json:"difficulty"`
	} `json:"rules"`
	Challenge struct {
		ID         string `json:"id"`
		RandomData string `json:"randomData"`
	} `json:"challenge"`
}

func parseAnubisChallenge(body []byte) (anubisChallengeDocument, bool, error) {
	match := anubisChallengePattern.FindSubmatch(body)
	if len(match) != 2 {
		return anubisChallengeDocument{}, false, nil
	}
	var challenge anubisChallengeDocument
	if err := json.Unmarshal([]byte(html.UnescapeString(string(match[1]))), &challenge); err != nil {
		return anubisChallengeDocument{}, true, fmt.Errorf("解析 Anubis 验证题目失败: %w", err)
	}
	if challenge.Challenge.ID == "" || challenge.Challenge.RandomData == "" || challenge.Rules.Difficulty < 1 || challenge.Rules.Difficulty > 7 {
		return anubisChallengeDocument{}, true, fmt.Errorf("Anubis 验证题目无效")
	}
	if challenge.Rules.Algorithm != "fast" && challenge.Rules.Algorithm != "slow" {
		return anubisChallengeDocument{}, true, fmt.Errorf("不支持的 Anubis 验证算法: %s", challenge.Rules.Algorithm)
	}
	return challenge, true, nil
}

func solveAnubisChallenge(ctx context.Context, randomData string, difficulty int) (string, uint64, error) {
	prefix := []byte(randomData)
	buffer := make([]byte, len(prefix), len(prefix)+20)
	copy(buffer, prefix)
	for nonce := uint64(0); ; nonce++ {
		if nonce&4095 == 0 {
			select {
			case <-ctx.Done():

View on GitHub (pinned to beaa561337)