11

I'm new to golang and I'm trying to write a function that uploads a file with a post request to telegram for a bot I'm writing.

I've tried with this code but the error I'm getting from telegram is Bad Request: there is no photo in the request.

I've searched on the net for how to do that, but none of what I found helped me through the problem.

func SendPostRequest (url string, filename string) []byte {
    file, err := os.Open(filename)

    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    response, err := http.Post(url, "binary/octet-stream", file)

    if err != nil {
        log.Fatal(err)
    }
    defer response.Body.Close()

    content, err := ioutil.ReadAll(response.Body)

    if err != nil {
        log.Fatal(err)
    }

    return content
}

The function I'm calling the SendPostRequest from is

func (e Engine) SendPhoto (filename string, chatId int64) APIResponse {
    var url = fmt.Sprintf("%ssendPhoto?chat_id=%d", e.baseUrl, chatId)

    var content []byte = SendPostRequest(url, filename)
    var response APIResponse

    json.Unmarshal(content, &response)
    return response
}

EDIT: The link to the Telegram bot api I'm using in the code is https://core.telegram.org/bots/api

And the api method is https://core.telegram.org/bots/api#sendphoto

NicoNex
  • 469
  • 2
  • 5
  • 15
  • Hope this help: [GIST](https://gist.github.com/ebraminio/576fdfdff425bf3335b51a191a65dbdb) – Danilo Jul 08 '18 at 21:26
  • 1
    The docs say to upload the file with multipart/form-data. – Michael Hampton Jul 09 '18 at 01:21
  • @MichaelHampton I've tried putting "multipart/form-data" instead of "binary/octet-stream", but it won't work anyway with no error message from telegram also. – NicoNex Jul 09 '18 at 14:40
  • The problem is that you are not setting photo parameter name in request. Also instead of creating integration I would recomend to use this package https://github.com/go-telegram-bot-api/telegram-bot-api . There is `UploadFile` method which probably easly will solve your problem – ttomalak Jul 09 '18 at 20:33
  • 1
    @ttomalak could you please explain me how to set the photo parameter name in the request? – NicoNex Jul 11 '18 at 15:36
  • at that line https://github.com/go-telegram-bot-api/telegram-bot-api/blob/4c16a90966d12d963e19e4df99e7744c191d0e79/bot.go#L139 there are paramas array where you could set other custom params but you need to set `fieldname`. So your func call will look something like that `tgbotapi.UploadFile(endpoint, customparams, 'photo', yourFile)` – ttomalak Jul 11 '18 at 16:41

1 Answers1

19

After some digging I figured it out with this

import (
    "bytes"
    "io"
    "mime/multipart"
    "net/http"
    "path/filepath"
)

// content is a struct which contains a file's name, its type and its data.
type content struct {
    fname string
    ftype string
    fdata []byte
}

func sendPostRequest(url string, files ...content) ([]byte, error) {
    var (
        buf = new(bytes.Buffer)
        w   = multipart.NewWriter(buf)
    )

    for _, f := range files {
        part, err := w.CreateFormFile(f.ftype, filepath.Base(f.fname))
        if err != nil {
            return []byte{}, err
        }

        _, err = part.Write(f.fdata)
        if err != nil {
            return []byte{}, err
        }
    }

    err := w.Close()
    if err != nil {
        return []byte{}, err
    }

    req, err := http.NewRequest("POST", url, buf)
    if err != nil {
        return []byte{}, err
    }
    req.Header.Add("Content-Type", w.FormDataContentType())

    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
        return []byte{}, err
    }
    defer res.Body.Close()

    cnt, err := io.ReadAll(res.Body)
    if err != nil {
        return []byte{}, err
    }
    return cnt, nil
}
John M.
  • 2,234
  • 4
  • 24
  • 36
NicoNex
  • 469
  • 2
  • 5
  • 15