mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
9072e9eafb
A fully-generated, strongly-typed Go client for the Telegram Bot API. * 176 methods + 301 types generated from Bot API v10.0 * 1408 auto-generated tests (8 scenarios per method) * Typed unions throughout — no 'any' in the public surface * Pluggable HTTP transport and JSON codec (default goccy/go-json) * Built-in retry middleware honouring Telegram's retry_after * Generic dispatcher with filters and conversation handlers * Self-verifying codegen pipeline (regen → audit → emit → run tests) * 14 example bots covering common patterns
108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
package client
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// APIError represents a non-OK Telegram Bot API response.
|
|
// It satisfies error and unwraps to a sentinel (ErrUnauthorized, etc.)
|
|
// where the description matches a known prefix, enabling errors.Is checks.
|
|
type APIError struct {
|
|
Code int
|
|
Description string
|
|
Parameters *ResponseParameters
|
|
|
|
// sentinel, if non-nil, is the wrapped sentinel error returned by
|
|
// Unwrap. It is set by mapAPIError based on Code+Description.
|
|
sentinel error
|
|
}
|
|
|
|
// Error implements error.
|
|
func (e *APIError) Error() string {
|
|
return fmt.Sprintf("telegram: %d %s", e.Code, e.Description)
|
|
}
|
|
|
|
// Unwrap returns the matched sentinel error, if any.
|
|
func (e *APIError) Unwrap() error { return e.sentinel }
|
|
|
|
// IsRetryable returns true for transient HTTP statuses (429, 5xx).
|
|
func (e *APIError) IsRetryable() bool {
|
|
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
|
|
}
|
|
|
|
// RetryAfter returns the recommended back-off duration. It honours the
|
|
// Telegram-supplied retry_after parameter; if absent, returns 0.
|
|
func (e *APIError) RetryAfter() time.Duration {
|
|
if e.Parameters == nil {
|
|
return 0
|
|
}
|
|
return time.Duration(e.Parameters.RetryAfter) * time.Second
|
|
}
|
|
|
|
// NetworkError wraps a transport-level failure (DNS, TCP, TLS, timeout
|
|
// short of an HTTP response).
|
|
type NetworkError struct{ Err error }
|
|
|
|
func (e *NetworkError) Error() string { return "telegram: network: " + redactToken(e.Err.Error()) }
|
|
|
|
func (e *NetworkError) Unwrap() error { return e.Err }
|
|
|
|
// ParseError wraps a JSON decode failure on a response body. Body is
|
|
// retained (truncated to 4 KiB); Error() displays up to 256 bytes for diagnostics.
|
|
type ParseError struct {
|
|
Err error
|
|
Body []byte
|
|
}
|
|
|
|
func (e *ParseError) Error() string {
|
|
body := e.Body
|
|
if len(body) > 256 {
|
|
body = body[:256]
|
|
}
|
|
return fmt.Sprintf("telegram: parse: %s (body=%q)", redactToken(e.Err.Error()), body)
|
|
}
|
|
|
|
func (e *ParseError) Unwrap() error { return e.Err }
|
|
|
|
// Sentinel errors returned via APIError.Unwrap when the description matches.
|
|
// Compare with errors.Is.
|
|
var (
|
|
ErrUnauthorized = errors.New("telegram: unauthorized")
|
|
ErrChatNotFound = errors.New("telegram: chat not found")
|
|
ErrMessageNotModified = errors.New("telegram: message is not modified")
|
|
ErrTooManyRequests = errors.New("telegram: too many requests")
|
|
ErrBadRequest = errors.New("telegram: bad request")
|
|
ErrForbidden = errors.New("telegram: forbidden")
|
|
ErrUserNotFound = errors.New("telegram: user not found")
|
|
ErrMessageNotFound = errors.New("telegram: message not found")
|
|
)
|
|
|
|
// mapAPIError builds an *APIError and attaches the appropriate sentinel
|
|
// based on Code+Description. It is the single point where wire-level
|
|
// failures are translated into the Go error taxonomy.
|
|
func mapAPIError(code int, description string, params *ResponseParameters) *APIError {
|
|
e := &APIError{Code: code, Description: description, Parameters: params}
|
|
switch {
|
|
case code == 401:
|
|
e.sentinel = ErrUnauthorized
|
|
case code == 403:
|
|
e.sentinel = ErrForbidden
|
|
case code == 429:
|
|
e.sentinel = ErrTooManyRequests
|
|
case code == 400 && strings.Contains(description, "user not found"):
|
|
e.sentinel = ErrUserNotFound
|
|
case code == 400 && strings.Contains(description, "message to") && strings.Contains(description, "not found"):
|
|
e.sentinel = ErrMessageNotFound
|
|
case code == 400 && strings.Contains(description, "chat not found"):
|
|
e.sentinel = ErrChatNotFound
|
|
case code == 400 && strings.Contains(description, "message is not modified"):
|
|
e.sentinel = ErrMessageNotModified
|
|
case code == 400:
|
|
e.sentinel = ErrBadRequest
|
|
}
|
|
return e
|
|
}
|