fish2018/pansou · error

解析Payload JSON失败

Error message

解析Payload JSON失败: %w

What it means

decodeJWTURL parses a JWT token, base64-decodes its payload, and unmarshals the payload JSON into a struct expecting a data.url field. This error is wrapped when json.Unmarshal fails, meaning the payload segment is not valid JSON or does not match the expected shape. It preserves the underlying decode error via %w so the root cause (syntax error, type mismatch) is visible.

Solutions

  1. Log/print the decoded payload bytes before unmarshalling to see the actual JSON shape and update the struct to match.
  2. Verify the JWT has the standard three dot-separated segments and that the payload segment is valid base64 (handle URL-safe base64 and missing padding).
  3. Re-fetch the token from the source page rather than reusing a cached/copied token that may be truncated.
  4. If the upstream schema changed, adjust payloadData (e.g. url may have moved out of data) and redeploy.

Example fix

// before
var payloadData struct {
    Data struct {
        URL string `json:"url"`
    } `json:"data"`
}
if err := json.Unmarshal(payload, &payloadData); err != nil {
    return "", fmt.Errorf("解析Payload JSON失败: %w", err)
}
// after: tolerate both shapes
var payloadData struct {
    Data struct {
        URL string `json:"url"`
    } `json:"data"`
    URL string `json:"url"`
}
if err := json.Unmarshal(payload, &payloadData); err != nil {
    return "", fmt.Errorf("解析Payload JSON失败: %w", err)
}
if payloadData.Data.URL == "" {
    payloadData.Data.URL = payloadData.URL
}
Defensive patterns

Strategy: try-catch

Validate before calling

parts := strings.Split(jwtToken, ".")
if len(parts) < 2 {
    return fmt.Errorf("not a JWT: expected 3 segments, got %d", len(parts))
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
    return fmt.Errorf("payload is not valid base64: %w", err)
}
if !json.Valid(payload) {
    return fmt.Errorf("payload is not valid JSON: %s", payload)
}

Type guard

func isValidJWTPayload(tok string) bool {
    parts := strings.Split(tok, ".")
    if len(parts) != 3 {
        return false
    }
    b, err := base64.RawURLEncoding.DecodeString(parts[1])
    return err == nil && json.Valid(b)
}

Try / catch

url, err := decodeJWTURL(jwtToken)
if err != nil {
    var uerr *json.SyntaxError
    if errors.As(err, &uerr) {
        log.Printf("JWT payload not JSON (syntax at offset %d), refetching token", uerr.Offset)
    }
    return fallbackResolveLink(ctx, item)
}

Prevention

When it happens

Trigger: Calling getButtonDetail (or TestDecodeJWTURL) with a JWT whose payload segment decodes to bytes that are not valid JSON, or valid JSON but not an object with {"data":{"url":...}} (e.g. JSON arrays, strings, or differently-shaped claims).

Common situations: The site changed its JWT payload schema; the token was truncated or corrupted when copied from a button link; the payload is a different encoding (e.g. url-encoded or double-encoded) so base64 decoding yields garbage; an anti-bot page returned a placeholder token.

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/010dac32c02cd090. Report an issue: GitHub.

Appendix: source

Thrown at plugin/susu/susu.go:533

	parts := strings.Split(jwtToken, ".")
	if len(parts) != 3 {
		return "", fmt.Errorf("无效的JWT格式")
	}

	// 解码Payload
	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return "", fmt.Errorf("解码Payload失败: %w", err)
	}

	// 解析JSON
	var payloadData struct {
		Data struct {
			URL string `json:"url"`
		} `json:"data"`
	}
	if err := json.Unmarshal(payload, &payloadData); err != nil {
		return "", fmt.Errorf("解析Payload JSON失败: %w", err)
	}
	if strings.TrimSpace(payloadData.Data.URL) == "" {
		return "", fmt.Errorf("JWT Payload中没有链接")
	}

	// 缓存结果
	jwtDecodeCache.Store(jwtToken, payloadData.Data.URL)

	return payloadData.Data.URL, nil
}

func setBrowserHeaders(req *http.Request, referer string) {
	req.Header.Set("User-Agent", getRandomUA())
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
	req.Header.Set("Referer", referer)
}

View on GitHub (pinned to beaa561337)