{"record":{"id":"47e92a03358b6982","repo":"Tencent/WeKnora","slug":"no-file-url-in-message","errorCode":null,"errorMessage":"no file URL in message","messagePattern":"no file URL in message","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/wecom/ws_adapter.go","lineNumber":85,"sourceCode":"func (a *WSAdapter) FinalizeStream(ctx context.Context, incoming *im.IncomingMessage, streamID string, finalContent string) error {\n\treturn a.client.FinalizeStream(ctx, incoming, streamID, finalContent)\n}\n\nfunc (a *WSAdapter) SendStreamChunk(ctx context.Context, incoming *im.IncomingMessage, streamID string, content string) error {\n\treturn a.client.UpdateStreamContent(ctx, incoming, streamID, content)\n}\n\nfunc (a *WSAdapter) EndStream(ctx context.Context, incoming *im.IncomingMessage, streamID string) error {\n\treturn a.client.EndStream(ctx, incoming, streamID)\n}\n\n// ── FileDownloader implementation ──\n// WeCom aibot provides AES-256-CBC encrypted URLs for image/file/video messages.\n// Each message carries its own aeskey for decryption.\n\nfunc (a *WSAdapter) DownloadFile(ctx context.Context, msg *im.IncomingMessage) (io.ReadCloser, string, error) {\n\tif msg.FileKey == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"no file URL in message\")\n\t}\n\tfileName := msg.FileName\n\tif fileName == \"\" {\n\t\tfileName = msg.FileKey\n\t}\n\n\t// Download the (encrypted) file content\n\treader, fileName, err := downloadFromURL(ctx, msg.FileKey, fileName, a.client.extraAllowedHost)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\t// If an AES key is provided, the downloaded content is AES-256-CBC encrypted\n\t// and must be decrypted before use. This is the case for WeCom aibot long\n\t// connection mode where each file/image message carries a per-message aeskey.\n\taesKeyB64 := msg.Extra[\"aes_key\"]\n\tif aesKeyB64 == \"\" {\n\t\t// No encryption — return raw content (e.g. webhook mode uses media API)","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/wecom/ws_adapter.go#L67-L103","documentation":"DownloadFile downloads and decrypts the media attached to a WeCom aibot message. Each incoming message carries an encrypted URL in FileKey; when that field is empty there is nothing to download, so the adapter returns this error. It guards against messages that have no file payload (e.g. plain text) or were parsed incompletely.","triggerScenarios":"Calling DownloadFile with an *im.IncomingMessage whose FileKey is empty — typically a text message, or a media message whose parse step failed to populate FileKey.","commonSituations":"Downloading attachments for every incoming message without checking message type first; upstream parser version mismatch leaving FileKey unset; voice/emoji messages that carry no downloadable URL.","solutions":["Check msg.FileKey (or the message type) is non-empty before calling DownloadFile.","Skip download for non-media message types (text, event, etc.).","Log the raw message payload to confirm the parser populated FileKey; if media messages consistently lack it, upgrade/fix the callback parsing code."],"exampleFix":"// before\nrc, name, err := adapter.DownloadFile(ctx, msg)\n// after\nif msg.FileKey == \"\" {\n    return nil, nil // not a media message\n}\nrc, name, err := adapter.DownloadFile(ctx, msg)","handlingStrategy":"validation","validationCode":"if msg.FileKey == \"\" {\n    // not a media message; skip download\n    return nil\n}\nrc, name, err := adapter.DownloadFile(ctx, msg)","typeGuard":"func hasDownloadableFile(msg *im.IncomingMessage) bool {\n    return msg != nil && msg.FileKey != \"\"\n}","tryCatchPattern":"rc, name, err := adapter.DownloadFile(ctx, msg)\nif err != nil && strings.Contains(err.Error(), \"no file URL in message\") {\n    return nil, nil // expected for text messages\n}","preventionTips":["Check message type before attempting file download.","Log raw incoming payloads when FileKey is unexpectedly empty on media messages.","Keep the callback/message parser up to date so FileKey is always populated for media."],"tags":["wecom","file-download","missing-field","validation"],"backgroundTag":"missing-file-url","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}