@@ -135,6 +135,7 @@ func (b *Bot) doJSON(method string, body any, dest any) error {
135135}
136136
137137// doUpload sends a multipart/form-data POST request with a file and optional parameters.
138+ // Retries on transient errors with the same backoff strategy as doJSON.
138139func (b * Bot ) doUpload (method string , field string , path string , params map [string ]any , dest any ) error {
139140 file , err := os .Open (path )
140141 if err != nil {
@@ -175,51 +176,77 @@ func (b *Bot) doUpload(method string, field string, path string, params map[stri
175176 return fmt .Errorf ("telegram: close multipart writer: %w" , err )
176177 }
177178
179+ bodyBytes := buf .Bytes ()
180+ contentType := writer .FormDataContentType ()
178181 url := b .url (method )
179- req , err := http .NewRequest (http .MethodPost , url , & buf )
180- if err != nil {
181- b .log .Error ("create request failed" , "method" , method , "error" , err )
182- return fmt .Errorf ("telegram: create request: %w" , err )
183- }
184- req .Header .Set ("Content-Type" , writer .FormDataContentType ())
182+ var lastErr error
185183
186- resp , err := b . Client . Do ( req )
187- if err != nil {
188- b . log . Error ( "http post failed" , "method" , method , "error" , err )
189- return fmt . Errorf ( "telegram: post %s: %w" , method , err )
190- }
191- defer resp . Body . Close ()
184+ for attempt := 0 ; attempt < 5 ; attempt ++ {
185+ if attempt > 0 {
186+ backoff := time . Duration ( 1 << ( attempt - 1 )) * time . Second
187+ b . log . Warn ( "retrying upload" , "method" , method , "attempt" , attempt , "backoff" , backoff )
188+ time . Sleep ( backoff )
189+ }
192190
193- respBody , err := io .ReadAll (resp .Body )
194- if err != nil {
195- b .log .Error ("read response body failed" , "method" , method , "error" , err )
196- return fmt .Errorf ("telegram: read response: %w" , err )
197- }
191+ req , err := http .NewRequest (http .MethodPost , url , bytes .NewReader (bodyBytes ))
192+ if err != nil {
193+ b .log .Error ("create request failed" , "method" , method , "error" , err )
194+ return fmt .Errorf ("telegram: create request: %w" , err )
195+ }
196+ req .Header .Set ("Content-Type" , contentType )
198197
199- var apiResp struct {
200- OK bool `json:"ok"`
201- Result json.RawMessage `json:"result"`
202- Description string `json:"description"`
203- ErrorCode int `json:"error_code"`
204- }
205- if err := json .Unmarshal (respBody , & apiResp ); err != nil {
206- b .log .Error ("unmarshal response failed" , "method" , method , "error" , err )
207- return fmt .Errorf ("telegram: unmarshal response: %w" , err )
208- }
198+ resp , err := b .Client .Do (req )
199+ if err != nil {
200+ b .log .Error ("http post failed" , "method" , method , "error" , err )
201+ lastErr = fmt .Errorf ("telegram: post %s: %w" , method , err )
202+ continue
203+ }
209204
210- if ! apiResp .OK {
211- b .log .Error ("api error" , "method" , method , "description" , apiResp .Description , "error_code" , apiResp .ErrorCode )
212- return fmt .Errorf ("telegram: %s failed: %s (code %d)" , method , apiResp .Description , apiResp .ErrorCode )
213- }
205+ respBody , err := io .ReadAll (resp .Body )
206+ resp .Body .Close ()
207+ if err != nil {
208+ b .log .Error ("read response body failed" , "method" , method , "error" , err )
209+ lastErr = fmt .Errorf ("telegram: read response: %w" , err )
210+ continue
211+ }
214212
215- if dest != nil && len (apiResp .Result ) > 0 {
216- if err := json .Unmarshal (apiResp .Result , dest ); err != nil {
217- b .log .Error ("unmarshal result failed" , "method" , method , "error" , err )
218- return fmt .Errorf ("telegram: unmarshal result: %w" , err )
213+ var apiResp struct {
214+ OK bool `json:"ok"`
215+ Result json.RawMessage `json:"result"`
216+ Description string `json:"description"`
217+ ErrorCode int `json:"error_code"`
218+ }
219+ if err := json .Unmarshal (respBody , & apiResp ); err != nil {
220+ b .log .Error ("unmarshal response failed" , "method" , method , "error" , err )
221+ return fmt .Errorf ("telegram: unmarshal response: %w" , err )
222+ }
223+
224+ if ! apiResp .OK {
225+ if apiResp .ErrorCode == 429 {
226+ b .log .Warn ("rate limited" , "method" , method , "description" , apiResp .Description )
227+ lastErr = fmt .Errorf ("telegram: %s failed: %s (code %d)" , method , apiResp .Description , apiResp .ErrorCode )
228+ continue
229+ }
230+ if apiResp .ErrorCode >= 500 && apiResp .ErrorCode < 600 {
231+ b .log .Warn ("server error" , "method" , method , "error_code" , apiResp .ErrorCode , "description" , apiResp .Description )
232+ lastErr = fmt .Errorf ("telegram: %s failed: %s (code %d)" , method , apiResp .Description , apiResp .ErrorCode )
233+ continue
234+ }
235+ b .log .Error ("api error" , "method" , method , "description" , apiResp .Description , "error_code" , apiResp .ErrorCode )
236+ return fmt .Errorf ("telegram: %s failed: %s (code %d)" , method , apiResp .Description , apiResp .ErrorCode )
219237 }
238+
239+ if dest != nil && len (apiResp .Result ) > 0 {
240+ if err := json .Unmarshal (apiResp .Result , dest ); err != nil {
241+ b .log .Error ("unmarshal result failed" , "method" , method , "error" , err )
242+ return fmt .Errorf ("telegram: unmarshal result: %w" , err )
243+ }
244+ }
245+
246+ return nil
220247 }
221248
222- return nil
249+ return lastErr
223250}
224251
225252// SendMessage sends a text message to the specified chat.
0 commit comments