diff --git a/methods.go b/methods.go index 298b27e..40b82e2 100644 --- a/methods.go +++ b/methods.go @@ -1,18 +1,17 @@ package tgbotapi import ( - "bytes" "encoding/json" "errors" "fmt" - "io" "io/ioutil" "log" - "mime/multipart" "net/http" "net/url" "os" "strconv" + + "github.com/technoweenie/multipartstreamer" ) // Telegram constants @@ -193,46 +192,32 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, // // Requires the parameter to hold the file not be in the params. func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, filename string) (APIResponse, error) { - var b bytes.Buffer - w := multipart.NewWriter(&b) - f, err := os.Open(filename) if err != nil { return APIResponse{}, err } + defer f.Close() - fw, err := w.CreateFormFile(fieldname, filename) + fi, err := os.Stat(filename) if err != nil { return APIResponse{}, err } - if _, err = io.Copy(fw, f); err != nil { - return APIResponse{}, err - } + ms := multipartstreamer.New() + ms.WriteFields(params) + ms.WriteReader(fieldname, f.Name(), fi.Size(), f) - for key, val := range params { - if fw, err = w.CreateFormField(key); err != nil { - return APIResponse{}, err - } - - if _, err = fw.Write([]byte(val)); err != nil { - return APIResponse{}, err - } - } - - w.Close() - - req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), &b) + req, err := http.NewRequest("POST", fmt.Sprintf(APIEndpoint, bot.Token, endpoint), nil) + ms.SetupRequest(req) if err != nil { return APIResponse{}, err } - req.Header.Set("Content-Type", w.FormDataContentType()) - res, err := bot.Client.Do(req) if err != nil { return APIResponse{}, err } + defer res.Body.Close() bytes, err := ioutil.ReadAll(res.Body) if err != nil { @@ -408,6 +393,7 @@ func (bot *BotAPI) SendPhoto(config PhotoConfig) (Message, error) { // when the fields title and performer are both empty // and the mime-type of the file to be sent is not audio/mpeg, // the file must be in an .ogg file encoded with OPUS. +// You may use the tgutils.EncodeAudio func to assist you with this, if needed. // // Requires ChatID and FileID OR FilePath. // ReplyToMessageID and ReplyMarkup are optional. @@ -561,6 +547,7 @@ func (bot *BotAPI) SendDocument(config DocumentConfig) (Message, error) { // SendVoice sends or uploads a playable voice to a chat. // If using a file, the file must be encoded as an .ogg with OPUS. +// You may use the tgutils.EncodeAudio func to assist you with this, if needed. // // Requires ChatID and FileID OR FilePath. // ReplyToMessageID and ReplyMarkup are optional. diff --git a/tgutils/audio.go b/tgutils/audio.go new file mode 100644 index 0000000..f7a0034 --- /dev/null +++ b/tgutils/audio.go @@ -0,0 +1,92 @@ +// Package tgutils provides extra functions to make certain tasks easier. +package tgutils + +import ( + "github.com/syfaro/telegram-bot-api" + "os" + "os/exec" + "path/filepath" + "strconv" + "sync" + "time" +) + +var rand uint32 +var randmu sync.Mutex + +func reseed() uint32 { + return uint32(time.Now().UnixNano() + int64(os.Getpid())) +} + +func nextSuffix() string { + randmu.Lock() + r := rand + if r == 0 { + r = reseed() + } + r = r*1664525 + 1013904223 // constants from Numerical Recipes + rand = r + randmu.Unlock() + return strconv.Itoa(int(1e9 + r%1e9))[1:] +} + +// this function ripped from ioutils.TempFile, except with a suffix, instead of prefix. +func tempFileWithSuffix(dir, suffix string) (f *os.File, err error) { + if dir == "" { + dir = os.TempDir() + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, nextSuffix()+suffix) + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + break + } + return +} + +// EncodeAudio takes a file and attempts to convert it to a .ogg for Telegram. +// It then updates the path to the audio file in the AudioConfig. +// +// This function requires ffmpeg and opusenc to be installed on the system! +func EncodeAudio(audio *tgbotapi.AudioConfig) error { + f, err := tempFileWithSuffix(os.TempDir(), "_tgutils.ogg") + if err != nil { + return err + } + defer f.Close() + + ffmpegArgs := []string{ + "-i", + audio.FilePath, + "-f", + "wav", + "-", + } + + opusArgs := []string{ + "--bitrate", + "256", + "-", + f.Name(), + } + + c1 := exec.Command("ffmpeg", ffmpegArgs...) + c2 := exec.Command("opusenc", opusArgs...) + + c2.Stdin, _ = c1.StdoutPipe() + c2.Stdout = os.Stdout + c2.Start() + c1.Run() + c2.Wait() + + return nil +} diff --git a/types.go b/types.go index 2cbe72d..a2271e2 100644 --- a/types.go +++ b/types.go @@ -80,7 +80,7 @@ type Message struct { NewChatParticipant User `json:"new_chat_participant"` LeftChatParticipant User `json:"left_chat_participant"` NewChatTitle string `json:"new_chat_title"` - NewChatPhoto string `json:"new_chat_photo"` + NewChatPhoto []PhotoSize `json:"new_chat_photo"` DeleteChatPhoto bool `json:"delete_chat_photo"` GroupChatCreated bool `json:"group_chat_created"` } diff --git a/updates.go b/updates.go index a3c6c46..f790a88 100644 --- a/updates.go +++ b/updates.go @@ -13,13 +13,9 @@ func (bot *BotAPI) UpdatesChan(config UpdateConfig) error { for { updates, err := bot.GetUpdates(config) if err != nil { - if bot.Debug { - panic(err) - } else { - log.Println(err) - log.Println("Failed to get updates, retrying in 3 seconds...") - time.Sleep(time.Second * 3) - } + log.Println(err) + log.Println("Failed to get updates, retrying in 3 seconds...") + time.Sleep(time.Second * 3) continue }