fish2018/pansou · error
getKey id= code= msg=
Error message
getKey id=%d code=%d msg=%s
What it means
This error is returned by YingsoPlugin.resolveItem when the /getKey endpoint returns a non-200 code in its envelope OR returns an empty/whitespace data field, meaning the share key for a specific search result could not be obtained. It embeds the item ID plus the API's code and msg so the failing resource can be identified.
Solutions
- Log item.ID with the API code/msg to confirm whether it is per-resource (deleted/private) or global (auth/rate limit).
- Re-fetch bootstrap config (url_version, user_id) via fetchConfig and retry resolveItem if code indicates auth/parameter issues.
- Apply a small backoff/retry for transient codes; individual item failures are already tolerated by searchImpl unless ALL items fail.
- Treat empty data with code 200 as 'resource unavailable' and skip the item rather than surfacing it as a hard failure.
- If many items fail simultaneously, suspect the Yingso API changed its getKey contract and update the payload/encryption accordingly.
Example fix
// before
if response.Code != http.StatusOK || strings.TrimSpace(response.Data) == "" {
return model.SearchResult{}, fmt.Errorf("getKey id=%d code=%d msg=%s", item.ID, response.Code, response.Msg)
}
// after
if response.Code != http.StatusOK || strings.TrimSpace(response.Data) == "" {
if isTransientCode(response.Code) {
time.Sleep(200 * time.Millisecond)
return p.resolveItem(ctx, client, config, item)
}
return model.SearchResult{}, fmt.Errorf("getKey id=%d code=%d msg=%s", item.ID, response.Code, response.Msg)
} Defensive patterns
Strategy: try-catch
Try / catch
result, err := resolveItem(ctx, client, config, item)
if err != nil {
if strings.Contains(err.Error(), "getKey") {
// per-item failure: skip this item, continue with others
log.Printf("skipping item (%v)", err)
return nil
}
return err
} Prevention
- Expect per-item getKey failures (deleted/private shares) and skip them individually.
- Re-fetch bootstrap config periodically so user_id/url_version stay valid.
- Cap getKey concurrency and add per-call timeouts to avoid rate-limit rejections.
- Treat empty data with code 200 as 'no share available' rather than retrying forever.
When it happens
Trigger: POST {apiBaseURL}/{url_version}/getKey with {id, userId} returns apiEnvelope[string] where code != 200 or strings.TrimSpace(response.Data) == "" — typically the resource was removed, is private, has no share key, or the encrypted payload was rejected.
Common situations: Search results are stale and the underlying netdisk share has been deleted or taken down; the API withholds keys for sensitive resources; heavy concurrency (up to 8 parallel getKey calls) triggers per-item rate limiting; user_id from bootstrap config became invalid.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/495afd59ee5df6b6.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/yingso/yingso.go:248
return nil, fmt.Errorf("[%s] 搜索接口异常: code=%d msg=%s", p.Name(), response.Code, response.Msg)
}
return response.Data, nil
}
func (p *YingsoPlugin) resolveItem(ctx context.Context, client *http.Client, config apiConfig, item searchItem) (model.SearchResult, error) {
payload := getKeyPayload{ID: item.ID, UserID: config.UserID}
encrypted, err := encryptPayload(payload, config)
if err != nil {
return model.SearchResult{}, err
}
var response apiEnvelope[string]
endpoint := fmt.Sprintf("%s/%s/getKey", p.apiBaseURL, url.PathEscape(config.URLVersion))
if err := p.requestJSON(ctx, client, http.MethodPost, endpoint, encrypted, &response); err != nil {
return model.SearchResult{}, err
}
if response.Code != http.StatusOK || strings.TrimSpace(response.Data) == "" {
return model.SearchResult{}, fmt.Errorf("getKey id=%d code=%d msg=%s", item.ID, response.Code, response.Msg)
}
linkType, linkURL, password := buildLink(item.Root, response.Data)
if linkURL == "" {
return model.SearchResult{}, fmt.Errorf("不支持的网盘类型 root=%d id=%d", item.Root, item.ID)
}
title := cleanText(item.Title)
if title == "" {
title = fmt.Sprintf("影搜资源 %d", item.ID)
}
id := fmt.Sprintf("%s-%d", pluginName, item.ID)
now := time.Now()
return model.SearchResult{
MessageID: id,
UniqueID: id,
Channel: "",
Datetime: now,View on GitHub (pinned to beaa561337)