{"record":{"id":"4165249c29eb2acc","repo":"chenhg5/cc-connect","slug":"qqbot-upload-rich-media-empty-file-info","errorCode":null,"errorMessage":"qqbot: upload rich media: empty file_info","messagePattern":"qqbot: upload rich media: empty file_info","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/qqbot/qqbot.go","lineNumber":298,"sourceCode":"\n\tb64 := base64.StdEncoding.EncodeToString(data)\n\treqBody := map[string]any{\n\t\t\"file_type\":    fileType,\n\t\t\"file_data\":    b64,\n\t\t\"srv_send_msg\": false,\n\t}\n\tif fileType == 4 && fileName != \"\" {\n\t\treqBody[\"file_name\"] = fileName\n\t}\n\n\tvar result struct {\n\t\tFileInfo string `json:\"file_info\"`\n\t}\n\tif err := p.apiRequestJSON(\"POST\", url, reqBody, &result); err != nil {\n\t\treturn \"\", err\n\t}\n\tif result.FileInfo == \"\" {\n\t\treturn \"\", fmt.Errorf(\"qqbot: upload rich media: empty file_info\")\n\t}\n\treturn result.FileInfo, nil\n}\n\n// apiRequestJSON is like apiRequest but also decodes the response body into result.\nfunc (p *Platform) apiRequestJSON(method, url string, body any, result any) error {\n\tvar bodyReader io.Reader\n\tif body != nil {\n\t\tdata, err := json.Marshal(body)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"qqbot: marshal body: %w\", err)\n\t\t}\n\t\tbodyReader = bytes.NewReader(data)\n\t}\n\n\ttoken, err := p.getAccessToken()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"qqbot: get token: %w\", err)","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/qqbot/qqbot.go#L280-L316","documentation":"After a successful apiRequestJSON call, uploadRichMedia validates that the QQ rich-media API actually returned a non-empty file_info token, which is required to attach the media to a message. An empty file_info means the API responded 2xx but without the expected payload — the upload did not really complete, so the adapter treats it as an error.","triggerScenarios":"The rich-media endpoint returns HTTP 200 with a body lacking file_info (e.g. an error object in disguise, an async-processing response, or an API behavior change).","commonSituations":"QQ API version drift changing the response shape; uploading a file type the bot lacks permission for, with the API failing softly; intermittent media-service degradation returning empty bodies.","solutions":["Retry the upload — the condition is often transient on QQ's media service.","Log the full raw response body (add temporary logging in apiRequestJSON) to see what was actually returned.","Verify the file_type (1=image, 4=file) and payload size match QQ Bot rich-media API requirements.","Check for a QQ Open Platform changelog entry about the rich-media response format.","Confirm the bot's permissions for the target group/c2c media upload."],"exampleFix":"// before\nfileInfo, err := p.uploadRichMedia(rctx, 1, img.Data, \"\")\nif err != nil { return err }\n// after\nvar fileInfo string\nfor i := 0; i < 2; i++ {\n    fileInfo, err = p.uploadRichMedia(rctx, 1, img.Data, \"\")\n    if err == nil { break }\n    if !strings.Contains(err.Error(), \"empty file_info\") { break }\n    time.Sleep(time.Second)\n}\nif err != nil { return err }","handlingStrategy":"retry","validationCode":"// Validate payload before upload so the API cannot fail softly\nif len(data) == 0 { return errors.New(\"qqbot: empty upload payload\") }","typeGuard":null,"tryCatchPattern":"fileInfo, err := p.uploadRichMedia(rctx, fileType, data, name)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty file_info\") {\n        // transient/soft API failure: retry once, then log raw body\n    }\n}","preventionTips":["Log the raw API response body on empty file_info to spot format drift early.","Retry uploads once with backoff — QQ media service degrades intermittently.","Subscribe to QQ Open Platform changelog for response-shape changes.","Test the uploader against a stub server returning 200 with an empty body."],"tags":["qqbot","api","empty-response","file-upload"],"backgroundTag":"empty-api-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}