]> git.giacomolli.uk - onechat/blobdiff - botapi.go
Capitalize type names so variables can use them
[onechat] / botapi.go
index 57bf9dba8111e163f99bda5a78470dafd594016e..2da945e9dc1b283d9b3c4dda8b7c54becb6f0419 100644 (file)
--- a/botapi.go
+++ b/botapi.go
@@ -21,11 +21,12 @@ const (
        // take before the bot gives it up.
        pollSlack = 20 * time.Second
 
        // take before the bot gives it up.
        pollSlack = 20 * time.Second
 
-       // sendTimeout is how long a sendMessage or sendChatAction call can
-       // take. A connection that stops without a close, such as after a
-       // network change, then fails the call, and the sender tries again,
-       // instead of a wait until TCP gives up. The client has no timeout of
-       // its own, because the long poll of getUpdates needs a longer limit.
+       // sendTimeout is how long a sendMessage, sendRichMessage, or
+       // sendChatAction call can take. A connection that stops without a
+       // close, such as after a network change, then fails the call, and the
+       // sender tries again, instead of a wait until TCP gives up. The client
+       // has no timeout of its own, because the long poll of getUpdates needs
+       // a longer limit.
        sendTimeout = 30 * time.Second
 
        // botAnswerBytes is the most bytes of one answer of the Bot API.
        sendTimeout = 30 * time.Second
 
        // botAnswerBytes is the most bytes of one answer of the Bot API.
@@ -38,7 +39,7 @@ var errBotNetwork = errors.New("no valid answer from the Bot API")
 
 // botAPI calls the Telegram Bot API. The bot token is a part of each URL, so no
 // error of botAPI holds a URL, a net/http error, or the token. SendTimeout
 
 // botAPI calls the Telegram Bot API. The bot token is a part of each URL, so no
 // error of botAPI holds a URL, a net/http error, or the token. SendTimeout
-// limits each sendMessage and sendChatAction call.
+// limits each sendMessage, sendRichMessage, and sendChatAction call.
 type botAPI struct {
        base        string
        token       string
 type botAPI struct {
        base        string
        token       string
@@ -47,12 +48,15 @@ type botAPI struct {
 }
 
 // botError is an answer of the Bot API that tells of a failure. RetryAfter is
 }
 
 // botError is an answer of the Bot API that tells of a failure. RetryAfter is
-// the wait that the Bot API asks for after HTTP 429.
+// 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 botError struct {
        Method      string
        Status      int
        Description string
        RetryAfter  time.Duration
 type botError struct {
        Method      string
        Status      int
        Description string
        RetryAfter  time.Duration
+       Refused     bool
 }
 
 // botAnswer is the envelope of each answer of the Bot API.
 }
 
 // botAnswer is the envelope of each answer of the Bot API.
@@ -130,15 +134,37 @@ func (b *botAPI) GetUpdates(ctx context.Context, offset int64) ([]botUpdate, err
        return updates, err
 }
 
        return updates, err
 }
 
-// SendMessage sends text to chat as plain text, with no parse mode. A call that
-// takes longer than sendTimeout fails with errBotNetwork.
-func (b *botAPI) SendMessage(ctx context.Context, chat int64, text string) error {
+// SendMessage sends text to chat with the format of entities, with no parse
+// mode, so the text needs no escaping. With no entities, the text is plain. A
+// call that takes longer than sendTimeout fails with errBotNetwork.
+func (b *botAPI) SendMessage(ctx context.Context, chat int64, text string, entities []botEntity) error {
        ctx, cancel := context.WithTimeout(ctx, b.sendTimeout)
        defer cancel()
 
        ctx, cancel := context.WithTimeout(ctx, b.sendTimeout)
        defer cancel()
 
-       return b.call(ctx, "sendMessage", map[string]any{
+       params := map[string]any{
                "chat_id": chat,
                "text":    text,
                "chat_id": chat,
                "text":    text,
+       }
+
+       if len(entities) > 0 {
+               params["entities"] = entities
+       }
+
+       return b.call(ctx, "sendMessage", params, nil)
+}
+
+// SendRichMessage sends markdown to chat as a rich message, which Telegram
+// renders itself, tables included. A call that takes longer than sendTimeout
+// fails with errBotNetwork.
+func (b *botAPI) SendRichMessage(ctx context.Context, chat int64, markdown string) error {
+       ctx, cancel := context.WithTimeout(ctx, b.sendTimeout)
+       defer cancel()
+
+       return b.call(ctx, "sendRichMessage", map[string]any{
+               "chat_id": chat,
+               "rich_message": map[string]any{
+                       "markdown": markdown,
+               },
        }, nil)
 }
 
        }, nil)
 }
 
@@ -199,6 +225,7 @@ func (b *botAPI) call(ctx context.Context, method string, params, result any) er
                        Status:      response.StatusCode,
                        Description: b.redact(errors.New(answer.Description)),
                        RetryAfter:  time.Duration(answer.Parameters.RetryAfter) * time.Second,
                        Status:      response.StatusCode,
                        Description: b.redact(errors.New(answer.Description)),
                        RetryAfter:  time.Duration(answer.Parameters.RetryAfter) * time.Second,
+                       Refused:     true,
                }
        }
 
                }
        }
 
@@ -245,3 +272,12 @@ func retryable(err error) (bool, time.Duration) {
 
        return false, 0
 }
 
        return false, 0
 }
+
+// rejected 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 rejected(err error) bool {
+       var answer *botError
+       return errors.As(err, &answer) && answer.Refused && (answer.Status == http.StatusBadRequest || answer.Status == http.StatusNotFound)
+}