diff --git a/request.go b/request.go deleted file mode 100644 index b783383..0000000 --- a/request.go +++ /dev/null @@ -1,44 +0,0 @@ -package telegram - -import ( - "errors" - "fmt" - "net/url" - - json "github.com/pquerna/ffjson/ffjson" - http "github.com/valyala/fasthttp" -) - -func (bot *Bot) request(dst []byte, method string, args *http.Args) (*Response, error) { - requestURI := &url.URL{ - Scheme: "https", - Host: "api.telegram.org", - Path: fmt.Sprint("/bot", bot.AccessToken, "/", method), - } - if args != nil { - requestURI.RawQuery = args.String() - } - - var req http.Request - var resp http.Response - - req.Header.SetMethod("POST") - req.Header.SetContentType("application/json") - req.SetRequestURI(requestURI.String()) - req.SetBody(dst) - - if err := http.Do(&req, &resp); err != nil { - return nil, err - } - - var data Response - if err := json.Unmarshal(resp.Body(), &data); err != nil { - return nil, err - } - - if !data.Ok { - return nil, errors.New(data.Description) - } - - return &data, nil -} diff --git a/set_webhook.go b/set_webhook.go index 59da3fc..55045e1 100644 --- a/set_webhook.go +++ b/set_webhook.go @@ -1,7 +1,6 @@ package telegram import ( - "fmt" "strconv" "strings" @@ -16,7 +15,7 @@ type SetWebhookParameters struct { // Upload your public key certificate so that the root certificate in use can // be checked. See our self-signed guide for details. - Certificate *InputFile `json:"certificate,omitempty"` + Certificate InputFile `json:"certificate,omitempty"` // Maximum allowed number of simultaneous HTTPS connections to the webhook // for update delivery, 1-100. Defaults to 40. Use lower values to limit the @@ -38,14 +37,14 @@ type SetWebhookParameters struct { } func NewWebhook(url string, file interface{}) *SetWebhookParameters { - params := &SetWebhookParameters{URL: url} + var params SetWebhookParameters + params.URL = url if file != nil { - var input InputFile = file - params.Certificate = &input + params.Certificate = &file } - return params + return ¶ms } // SetWebhook specify a url and receive incoming updates via an outgoing webhook. @@ -62,13 +61,25 @@ func (bot *Bot) SetWebhook(params *SetWebhookParameters) (bool, error) { args.Add("url", params.URL) if len(params.AllowedUpdates) > 0 { - args.Add("allowed_updates", fmt.Sprint(`["`, strings.Join(params.AllowedUpdates, `","`), `"]`)) + args.Add("allowed_updates", strings.Join(params.AllowedUpdates, `","`)) } if params.MaxConnections > 0 { args.Add("max_connections", strconv.Itoa(params.MaxConnections)) } - resp, err := bot.upload(*params.Certificate, "certificate", "cert.pem", "setWebhook", &args) + var err error + resp := &Response{} + if params.Certificate != nil { + resp, err = bot.upload( + params.Certificate, + "certificate", + "cert.pem", + "setWebhook", + &args, + ) + } else { + resp, err = bot.request(nil, "setWebhook", &args) + } if err != nil { return false, err } diff --git a/telegram.go b/telegram.go index df9a6ab..b82322b 100644 --- a/telegram.go +++ b/telegram.go @@ -1,7 +1,152 @@ // Version of the bot API: 3.5 (November 17, 2017) package telegram +import ( + "bytes" + "errors" + "fmt" + "io" + "mime/multipart" + "net/url" + "os" + + log "github.com/kirillDanshin/dlog" + json "github.com/pquerna/ffjson/ffjson" + http "github.com/valyala/fasthttp" +) + const ( APIEndpoint = "https://api.telegram.org/bot%s/%s" FileEndpoind = "https://api.telegram.org/file/bot%s/%s" + + methodPOST = "post" + + mimeJSON = "application/json" + mimeMultipart = "multipart/form-data" + + urlHost = "api.telegram.org" + urlPathPrefix = "/bot" + urlScheme = "https" + + userAgent = "go-telegram/3.5" ) + +var ( + ErrBadFileType = errors.New("bad file type") +) + +func (bot *Bot) request(dst []byte, method string, args *http.Args) (*Response, error) { + requestURI := &url.URL{ + Scheme: urlScheme, + Host: urlHost, + Path: fmt.Sprint(urlPathPrefix, bot.AccessToken, "/", method), + } + if args != nil { + requestURI.RawQuery = args.String() + } + + var req http.Request + var resp http.Response + + req.Header.SetMethod(methodPOST) + req.Header.SetContentType(mimeJSON) + req.Header.SetUserAgent(userAgent) + req.SetRequestURI(requestURI.String()) + req.SetBody(dst) + + if err := http.Do(&req, &resp); err != nil { + return nil, err + } + + var data Response + if err := json.Unmarshal(resp.Body(), &data); err != nil { + return nil, err + } + + log.Ln("Raw response:") + log.D(data) + + if !data.Ok { + return nil, errors.New(data.Description) + } + + return &data, nil +} + +func (bot *Bot) upload(file InputFile, fieldName, fileName, method string, args *http.Args) (*Response, error) { + var buffer bytes.Buffer + multi := multipart.NewWriter(&buffer) + defer multi.Close() + + switch source := file.(type) { + case string: + f, err := os.Open(source) + if err != nil { + return nil, err + } + defer f.Close() + + formFile, err := multi.CreateFormFile(fieldName, f.Name()) + if err != nil { + return nil, err + } + if _, err = io.Copy(formFile, f); err != nil { + return nil, err + } + case []byte: + formFile, err := multi.CreateFormFile(fieldName, fileName) + if err != nil { + return nil, err + } + if _, err = io.Copy(formFile, bytes.NewReader(source)); err != nil { + return nil, err + } + case *url.URL: + if err := multi.WriteField(fieldName, source.String()); err != nil { + return nil, err + } + case io.Reader: + multi.CreateFormFile(fieldName, fileName) + default: + return nil, ErrBadFileType + } + + requestURI := &url.URL{ + Scheme: urlScheme, + Host: urlHost, + Path: fmt.Sprint(urlPathPrefix, bot.AccessToken, "/", method), + } + if args != nil { + requestURI.RawQuery = args.String() + } + + var req http.Request + var resp http.Response + + req.Header.SetMethod(methodPOST) + req.Header.SetContentType(mimeMultipart) + req.Header.SetMultipartFormBoundary(multi.Boundary()) + req.Header.SetUserAgent(userAgent) + req.SetRequestURI(requestURI.String()) + req.SetBody(buffer.Bytes()) + + args.WriteTo(req.BodyWriter()) + + if err := http.Do(&req, &resp); err != nil { + return nil, err + } + + var data Response + if err := json.Unmarshal(resp.Body(), &data); err != nil { + return nil, err + } + + log.Ln("Raw response:") + log.D(data) + + if !data.Ok { + return nil, errors.New(data.Description) + } + + return &data, nil +} diff --git a/upload.go b/upload.go deleted file mode 100644 index bb79b87..0000000 --- a/upload.go +++ /dev/null @@ -1,88 +0,0 @@ -package telegram - -import ( - "bytes" - "errors" - "fmt" - "io" - "mime/multipart" - "net/url" - "os" - - json "github.com/pquerna/ffjson/ffjson" - http "github.com/valyala/fasthttp" -) - -// There are three ways to send files (photos, stickers, audio, media, etc.): -// -// 1. If the file is already stored somewhere on the Telegram servers, you don't need to reupload it: each file object has a file_id field, simply pass this file_id as a parameter instead of uploading. There are no limits for files sent this way. -// 2. Provide Telegram with an HTTP URL for the file to be sent. Telegram will download and send the file. 5 MB max size for photos and 20 MB max for other types of content. -// 3. Post the file using multipart/form-data in the usual way that files are uploaded via the browser. 10 MB max size for photos, 50 MB for other files. -func (bot *Bot) upload(file InputFile, fieldName, fileName, method string, args *http.Args) (*Response, error) { - var buffer bytes.Buffer - multi := multipart.NewWriter(&buffer) - defer multi.Close() - - switch source := file.(type) { - case string: - f, err := os.Open(source) - if err != nil { - return nil, err - } - defer f.Close() - - formFile, err := multi.CreateFormFile(fieldName, f.Name()) - if err != nil { - return nil, err - } - if _, err = io.Copy(formFile, f); err != nil { - return nil, err - } - case []byte: - formFile, err := multi.CreateFormFile(fieldName, fileName) - if err != nil { - return nil, err - } - if _, err = io.Copy(formFile, bytes.NewReader(source)); err != nil { - return nil, err - } - case *url.URL: - if err := multi.WriteField(fieldName, source.String()); err != nil { - return nil, err - } - case io.Reader: - multi.CreateFormFile(fieldName, fileName) - default: - return nil, errors.New("bad file type") - } - - requestURI := fmt.Sprintf(APIEndpoint, bot.AccessToken, method) - if args != nil { - requestURI += fmt.Sprint("?", args.String()) - } - - var req http.Request - var resp http.Response - - req.Header.SetMethod("POST") - req.Header.SetContentType("multipart/form-data") - req.Header.SetMultipartFormBoundary(multi.Boundary()) - args.WriteTo(req.BodyWriter()) - req.SetRequestURI(requestURI) - req.SetBody(buffer.Bytes()) - - if err := http.Do(&req, &resp); err != nil { - return nil, err - } - - var data Response - if err := json.Unmarshal(resp.Body(), &data); err != nil { - return nil, err - } - - if !data.Ok { - return nil, errors.New(data.Description) - } - - return &data, nil -} diff --git a/upload_file.go b/upload_file.go new file mode 100644 index 0000000..9ea53bf --- /dev/null +++ b/upload_file.go @@ -0,0 +1,37 @@ +package telegram + +// UploadFile is a helper method which provide are three ways to send files +// (photos, stickers, audio, media, etc.): +// +// 1. If the file is already stored somewhere on the Telegram servers, you don't +// need to reupload it: each file object has a file_id field, simply pass this +// file_id as a parameter instead of uploading. There are no limits for files +// sent this way. +// 2. Provide Telegram with an *url.URL for the file to be sent. Telegram will +// download and send the file. 5 MB max size for photos and 20 MB max for other +// types of content. +// 3. Post the file using multipart/form-data in the usual way that files are +// uploaded via the browser. Use path string, []byte or io.Reader for this. 10 MB +// max size for photos, 50 MB for other files. +// +// Sending by file_id +// +// - It is not possible to change the file type when resending by file_id. I.e. +// a video can't be sent as a photo, a photo can't be sent as a document, etc. +// - It is not possible to resend thumbnails. +// - Resending a photo by file_id will send all of its sizes. +// - file_id is unique for each individual bot and can't be transferred from one +// bot to another. +// +// Sending by URL +// +// - When sending by *url.URL the target file must have the correct MIME type +// (e.g., audio/mpeg for sendAudio, etc.). +// - In sendDocument, sending by URL will currently only work for gif, pdf and +// zip files. +// - To use SendVoice, the file must have the type audio/ogg and be no more than +// 1MB in size. 1–20MB voice notes will be sent as files. +// - Other configurations may work but we can't guarantee that they will. +func (bot *Bot) UploadFile(name string, file InputFile) (*Response, error) { + return bot.upload(file, "file", name, "", nil) +}