fish2018/pansou · error

api returned error

Error message

api returned error: %s

What it means

After successful decoding, searchPage checks the application-level code in MelostResponse; if apiResp.Code != 200 it returns "api returned error: %s" with the API's Msg field. This is a business-logic error from the melost.cn API itself: the HTTP layer succeeded but the API refused the search (invalid params, quota, auth ticket, or captcha required).

Solutions

  1. Read apiResp.Msg in the error text — the API states the exact reason (quota, ticket, captcha)
  2. If Msg indicates captcha/ticket, populate adv_params.search_code or obtain a fresh search_ticket before searching
  3. Reduce request frequency/concurrency if the code indicates rate limiting
  4. Re-check the request parameter contract against the current melost.cn frontend to find rejected fields
  5. Since doSearch only surfaces this when all pages fail, verify whether the code applies to specific pages only

Example fix

// before: message only
if apiResp.Code != 200 {
	return nil, fmt.Errorf("api returned error: %s", apiResp.Msg)
}
// after: include code for programmatic handling
if apiResp.Code != 200 {
	return nil, fmt.Errorf("api returned error: code=%d msg=%s", apiResp.Code, apiResp.Msg)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate inputs the API is known to reject before calling
if strings.TrimSpace(keyword) == "" {
	return nil, fmt.Errorf("keyword required")
}

Try / catch

if apiResp.Code != 200 {
	switch {
	case strings.Contains(apiResp.Msg, "ticket") || strings.Contains(apiResp.Msg, "验证"):
		// refresh search_ticket / handle captcha, then retry
	case strings.Contains(apiResp.Msg, "频繁") || strings.Contains(apiResp.Msg, "limit"):
		// back off; rate limited
	default:
		return nil, fmt.Errorf("api returned error: %s", apiResp.Msg)
	}
}

Prevention

When it happens

Trigger: melost.cn responds 200 with an envelope whose code is not 200 (e.g. rate-limit code, missing/invalid search_ticket, required captcha/search_code, or query rejected), and Msg carries the API's reason.

Common situations: Search quota exhausted for the client/IP, the API now requires a valid search_ticket or search_code (anti-bot escalation), invalid parameter combination rejected by the backend, or empty/over-filtered queries flagged server-side.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/29e1abdc9f3e92ee. Report an issue: GitHub.

Appendix: source

Thrown at plugin/melost/melost.go:191

	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
	}

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response failed: %w", err)
	}

	var apiResp MelostResponse
	if err := json.Unmarshal(respBody, &apiResp); err != nil {
		return nil, fmt.Errorf("decode response failed: %w", err)
	}

	if apiResp.Code != 200 {
		return nil, fmt.Errorf("api returned error: %s", apiResp.Msg)
	}

	return apiResp.Data.List, nil
}

func (p *MelostAsyncPlugin) deduplicateItems(items []MelostItem) []MelostItem {
	uniqueMap := make(map[string]MelostItem)

	for _, item := range items {
		key := item.DiskID
		if key == "" {
			key = item.Link
		}
		if key == "" {
			key = item.DiskName + "|" + item.DiskType
		}

		existing, exists := uniqueMap[key]

View on GitHub (pinned to beaa561337)