X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/d73a88b9e0835b1d7f0711e02db56555101a7830..c781f79f3c8cb9a7577ee1574d196988da2719b3:/telegram_api.go?ds=sidebyside diff --git a/telegram_api.go b/telegram_api.go index f53d8d5..3a44a0d 100644 --- a/telegram_api.go +++ b/telegram_api.go @@ -38,7 +38,8 @@ const ( // telegramFileLookupTimeout is the longest wait for media file details. telegramFileLookupTimeout = 30 * time.Second - // telegramFileDownloadTimeout is the longest wait for image bytes. + // telegramFileDownloadTimeout is the longest wait for downloaded media + // bytes. telegramFileDownloadTimeout = 30 * time.Second // telegramTypingTimeout is the longest wait to show the typing action. @@ -48,50 +49,113 @@ const ( telegramAnswerBytes = 16 << 20 ) -// newTelegramAPI returns the client of the Bot API at base with token. -func newTelegramAPI(base, token string) *TelegramAPI { - return &TelegramAPI{ - base: base, - token: token, - http: &http.Client{}, - } +// TelegramAPI calls the Telegram Bot API. The bot token is a part of each URL, +// so no error of TelegramAPI holds a URL, a net/http error, or the token. +type TelegramAPI struct { + base string + token string + http *http.Client } -// telegramRetryable tells if a later try of a failed Bot API call can work: -// after a network error, HTTP 429, or a server error. It returns the wait that -// the Bot API asks for, or 0. -func telegramRetryable(err error) (bool, time.Duration) { - var network *TelegramNetworkError +// TelegramNetworkError tells that a Bot API call got no valid answer, such as +// after a network error. A later call can work. Cause tells why in plain words, +// with no URL and no token. +type TelegramNetworkError struct { + Cause string +} - if errors.As(err, &network) { - return true, 0 - } +// TelegramError is an answer of the Bot API that tells of a failure. Its text +// gives the HTTP status and the description, such as "502 Bad Gateway". +// RetryAfter is the wait that the Bot API asks for after HTTP 429. Refused +// tells that the answer is a Bot API answer with ok false, not a page of a +// proxy or of a server error. +type TelegramError struct { + Status int + Description string + RetryAfter time.Duration + Refused bool +} - var answer *TelegramError +// TelegramUpdate is one update of getUpdates. Message is nil for the kinds of +// updates that are not a new message. +type TelegramUpdate struct { + UpdateID int64 `json:"update_id"` + Message *TelegramMessage `json:"message"` +} - if errors.As(err, &answer) { - return answer.Status == http.StatusTooManyRequests || answer.Status >= 500, answer.RetryAfter - } +// TelegramEntity is a message entity of sendMessage: a part of the text with a +// format, such as bold or a link. Offset and Length count UTF-16 code units. +// URL is the target of a text_link, and Language the language of a pre. +type TelegramEntity struct { + Type string `json:"type"` + Offset int `json:"offset"` + Length int `json:"length"` + URL string `json:"url,omitempty"` + Language string `json:"language,omitempty"` +} - return false, 0 +// TelegramAnswer is the envelope of each answer of the Bot API. +type TelegramAnswer struct { + OK bool `json:"ok"` + Result json.RawMessage `json:"result"` + ErrorCode int `json:"error_code"` + Description string `json:"description"` + Parameters struct { + RetryAfter int `json:"retry_after"` + } `json:"parameters"` } -// telegramRejected tells if the Bot API refused a call with ok false and HTTP -// 400 or 404. Then the call did nothing: 400 refuses the request, and 404 tells -// that the method is not there. An error page with no Bot API answer does not -// tell this, so it is not a refusal. -func telegramRejected(err error) bool { - var answer *TelegramError +// TelegramMessage holds text and supported media metadata of a bot message. +type TelegramMessage struct { + Chat TelegramChat `json:"chat"` + From *TelegramUser `json:"from"` + Text string `json:"text"` + Caption string `json:"caption"` + Photo []TelegramPhoto `json:"photo"` + Document *TelegramDocument `json:"document"` + Voice *TelegramVoice `json:"voice"` +} - return errors.As(err, &answer) && answer.Refused && (answer.Status == http.StatusBadRequest || answer.Status == http.StatusNotFound) +// TelegramVoice identifies a voice note in the authenticated file API. +type TelegramVoice struct { + FileID string `json:"file_id"` + FileSize int64 `json:"file_size"` } -// TelegramAPI calls the Telegram Bot API. The bot token is a part of each URL, -// so no error of TelegramAPI holds a URL, a net/http error, or the token. -type TelegramAPI struct { - base string - token string - http *http.Client +// TelegramPhoto is one supplied size of a photo. +type TelegramPhoto struct { + FileID string `json:"file_id"` + Width int `json:"width"` + Height int `json:"height"` + FileSize int64 `json:"file_size"` +} + +// TelegramDocument is a file sent without photo compression. +type TelegramDocument struct { + FileID string `json:"file_id"` + MimeType string `json:"mime_type"` + FileSize int64 `json:"file_size"` +} + +// TelegramChat is the chat of a message. Type is private for a chat with one +// user. +type TelegramChat struct { + ID int64 `json:"id"` + Type string `json:"type"` +} + +// TelegramUser is the sender of a message. +type TelegramUser struct { + ID int64 `json:"id"` +} + +// newTelegramAPI returns the client of the Bot API at base with token. +func newTelegramAPI(base, token string) *TelegramAPI { + return &TelegramAPI{ + base: base, + token: token, + http: &http.Client{}, + } } // GetUpdates waits for the updates from offset on, up to @@ -367,102 +431,39 @@ func (b *TelegramAPI) redact(err error) string { return strings.ReplaceAll(err.Error(), b.token, "TOKEN") } -// TelegramNetworkError tells that a Bot API call got no valid answer, such as -// after a network error. A later call can work. Cause tells why in plain words, -// with no URL and no token. -type TelegramNetworkError struct { - Cause string -} - func (e *TelegramNetworkError) Error() string { return e.Cause } -// TelegramError is an answer of the Bot API that tells of a failure. Its text -// gives the HTTP status and the description, such as "502 Bad Gateway". -// RetryAfter is the wait that the Bot API asks for after HTTP 429. Refused -// tells that the answer is a Bot API answer with ok false, not a page of a -// proxy or of a server error. -type TelegramError struct { - Status int - Description string - RetryAfter time.Duration - Refused bool -} - func (e *TelegramError) Error() string { return fmt.Sprintf("%d %s", e.Status, e.Description) } -// TelegramUpdate is one update of getUpdates. Message is nil for the kinds of -// updates that are not a new message. -type TelegramUpdate struct { - UpdateID int64 `json:"update_id"` - Message *TelegramMessage `json:"message"` -} - -// TelegramEntity is a message entity of sendMessage: a part of the text with a -// format, such as bold or a link. Offset and Length count UTF-16 code units. -// URL is the target of a text_link, and Language the language of a pre. -type TelegramEntity struct { - Type string `json:"type"` - Offset int `json:"offset"` - Length int `json:"length"` - URL string `json:"url,omitempty"` - Language string `json:"language,omitempty"` -} - -// TelegramAnswer is the envelope of each answer of the Bot API. -type TelegramAnswer struct { - OK bool `json:"ok"` - Result json.RawMessage `json:"result"` - ErrorCode int `json:"error_code"` - Description string `json:"description"` - Parameters struct { - RetryAfter int `json:"retry_after"` - } `json:"parameters"` -} +// telegramRetryable tells if a later try of a failed Bot API call can work: +// after a network error, HTTP 429, or a server error. It returns the wait that +// the Bot API asks for, or 0. +func telegramRetryable(err error) (bool, time.Duration) { + var network *TelegramNetworkError -// TelegramMessage holds the text, caption, and image sizes of a bot message. -type TelegramMessage struct { - Chat TelegramChat `json:"chat"` - From *TelegramUser `json:"from"` - Text string `json:"text"` - Caption string `json:"caption"` - Photo []TelegramPhoto `json:"photo"` - Document *TelegramDocument `json:"document"` - Voice *TelegramVoice `json:"voice"` -} + if errors.As(err, &network) { + return true, 0 + } -// TelegramVoice identifies a voice note in the authenticated file API. -type TelegramVoice struct { - FileID string `json:"file_id"` - FileSize int64 `json:"file_size"` -} + var answer *TelegramError -// TelegramPhoto is one supplied size of a photo. -type TelegramPhoto struct { - FileID string `json:"file_id"` - Width int `json:"width"` - Height int `json:"height"` - FileSize int64 `json:"file_size"` -} + if errors.As(err, &answer) { + return answer.Status == http.StatusTooManyRequests || answer.Status >= 500, answer.RetryAfter + } -// TelegramDocument is a file sent without photo compression. -type TelegramDocument struct { - FileID string `json:"file_id"` - MimeType string `json:"mime_type"` - FileSize int64 `json:"file_size"` + return false, 0 } -// TelegramChat is the chat of a message. Type is private for a chat with one -// user. -type TelegramChat struct { - ID int64 `json:"id"` - Type string `json:"type"` -} +// telegramRejected tells if the Bot API refused a call with ok false and HTTP +// 400 or 404. Then the call did nothing: 400 refuses the request, and 404 tells +// that the method is not there. An error page with no Bot API answer does not +// tell this, so it is not a refusal. +func telegramRejected(err error) bool { + var answer *TelegramError -// TelegramUser is the sender of a message. -type TelegramUser struct { - ID int64 `json:"id"` + return errors.As(err, &answer) && answer.Refused && (answer.Status == http.StatusBadRequest || answer.Status == http.StatusNotFound) }