我正在尝试通过 Facebook 附件上传 API 将附件添加到我的消息中。
我正在从一个页面(我是其管理员)发送消息给已向我的页面发送消息的用户。 发送简单的短信,并且附件作为类型“文件”也可以正常工作。但是当我将 'image' 或 'audio' 作为附件放入时,Facebook 会发送响应:
错误:{ message: '(#100) 上传附件失败。', 类型:'OAuthException', 代码:100, 错误子代码:2018047, fbtrace_id: 'AzfHWxf3AnikXiCCC-hYJOu' }
这是我的代码:
let messageData = {
message: {
attachment: {
**type: "image"**,
payload: {
is_reusable: true,
url: <url of the image>
}
}
}
}
let data = await axios.post(`https://graph.facebook.com/v8.0/me/message_attachments?access_token=${accessToken}`, messageData)
上传附件失败。触发此错误的常见方法是提供的媒体类型与 URL 中提供的文件类型不匹配
来自官方文档
对于 Golang,这是我的复制粘贴示例,请参阅评论。
func UploadAttachment(dataBytes []byte, fileName string, contentType string) (string, error) {
// Create a io.Reader to hold dataBytes and pass to http
reader := bytes.NewReader(dataBytes)
// Create a buffer and multipart writer
var b bytes.Buffer
multipartWriter := multipart.NewWriter(&b)
// Define the structs
type Payload struct {
Reusable bool `json:"is_reusable"`
}
type Attachment struct {
Type string `json:"type"`
Payload Payload `json:"payload"`
}
type Message struct {
Attachment Attachment `json:"attachment"`
}
msg := Message{
Attachment: Attachment{
Type: getTypeFromContentType(contentType),
Payload: Payload{
Reusable: true,
},
},
}
// Marshal the struct to JSON
jsonData, err := json.Marshal(msg)
if err != nil {
fmt.Println("Error marshalling to JSON:", err)
return "", err
}
// Convert JSON bytes to string
jsonString := string(jsonData)
// Debug
fmt.Println("Sending :" + jsonString)
multipartWriter.WriteField("message", jsonString)
multipartWriter.WriteField("access_token", os.Getenv("FACEBOOK_FAN_PERMANENT_TOKEN"))
// Note ! formFileWriter is different from formFieldWriter, don't be lazy and reuse variable like (fw) will cause confusion from many "sample code"
// Note ! w.CreateFormFile by default DO h.Set("Content-Type", "application/octet-stream"), but facebook requires the content-type matching or you get an error_subcode: 2018047
// If you're using w.CreateFormFile and specify type="file" in the json payload, it's fine with facebook (no extra check). But when you specify type="image" in the json payload, here's why error_subcode: 2018047 kicks in.
// Therefore, i copied from w.CreateFormFile (which is just a wrapper of CreatePart) and set the Content-Type to the correct one.
// Manually create the form file part, setting the Content-Type explicitly
// This concept also apply to whatsapp upload media
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fileName))
h.Set("Content-Type", contentType) // << here
formFileWriter, err := multipartWriter.CreatePart(h)
if err != nil {
return "", err
}
// Write the binary data from reader to formFileWriter
if _, err := io.Copy(formFileWriter, reader); err != nil {
return "", err
}
// Close the writer
multipartWriter.Close()
// Create and send the request
// I don't like to put access_token in the url (safer)
url := os.Getenv("FACEBOOK_FAN_ENDPOINT") + "/message_attachments"
// Debug
fmt.Println("Posting to url " + url)
req, err := http.NewRequest("POST", url, &b)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", multipartWriter.FormDataContentType()) // << multipart/form-data
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("error reading response body: %w", err)
}
// Debug Print the response body
fmt.Println("Response Body:", string(body))
// Check the response
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("http not OK")
}
// Parse the JSON response
var attachmentResp struct {
ID string `json:"attachment_id"`
}
if err := json.Unmarshal(body, &attachmentResp); err != nil {
return "", err
}
// Assuming the response contains attachment_id
if attachmentResp.ID != "" {
return attachmentResp.ID, nil
}
return "", fmt.Errorf("no attachment ID found in response")
}
// Depends on contenType select typeField for the message payload
func getTypeFromContentType(contentType string) string {
var typeField = "file" // default as file
switch contentType {
case "image/jpeg":
fallthrough
case "image/png":
typeField = "image"
case "video/mp4":
typeField = "video"
case "text/plain":
fallthrough
case "application/pdf":
fallthrough
case "application/vnd.ms-powerpoint":
fallthrough
case "application/msword":
fallthrough
case "application/vnd.ms-excel":
fallthrough
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
fallthrough
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
fallthrough
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
typeField = "file"
}
return typeField
}