package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) const telegramAPIBase = "https://api.telegram.org" type TelegramClient struct { httpClient *http.Client apiBase string } func NewTelegramClient() *TelegramClient { return &TelegramClient{ httpClient: &http.Client{Timeout: 10 * time.Second}, apiBase: telegramAPIBase, } } type SendMessageParams struct { ChatID int64 `json:"chat_id"` Text string `json:"text"` } type apiResponse struct { OK bool `json:"ok"` Description string `json:"description,omitempty"` Result json.RawMessage `json:"result,omitempty"` ErrorCode int `json:"error_code,omitempty"` } func (c *TelegramClient) SendMessage(ctx context.Context, token string, params SendMessageParams) error { body, err := json.Marshal(params) if err != nil { return err } endpoint := fmt.Sprintf("%s/bot%s/sendMessage", c.apiBase, token) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var ar apiResponse if jerr := json.Unmarshal(respBody, &ar); jerr != nil { return fmt.Errorf("decode telegram response (status %d): %w", resp.StatusCode, jerr) } if !ar.OK { return fmt.Errorf("telegram api error (code=%d): %s", ar.ErrorCode, ar.Description) } return nil } type SetWebhookParams struct { URL string `json:"url"` SecretToken string `json:"secret_token,omitempty"` DropPendingUpdates bool `json:"drop_pending_updates,omitempty"` AllowedUpdates []string `json:"allowed_updates,omitempty"` MaxConnections int `json:"max_connections,omitempty"` } func (c *TelegramClient) SetWebhook(ctx context.Context, token string, params SetWebhookParams) error { body, err := json.Marshal(params) if err != nil { return err } endpoint := fmt.Sprintf("%s/bot%s/setWebhook", c.apiBase, token) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var ar apiResponse if jerr := json.Unmarshal(respBody, &ar); jerr != nil { return fmt.Errorf("decode telegram response (status %d): %w", resp.StatusCode, jerr) } if !ar.OK { return fmt.Errorf("telegram setWebhook error (code=%d): %s", ar.ErrorCode, ar.Description) } return nil } func (c *TelegramClient) DeleteWebhook(ctx context.Context, token string) error { endpoint := fmt.Sprintf("%s/bot%s/deleteWebhook", c.apiBase, token) req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil) if err != nil { return err } resp, err := c.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var ar apiResponse if jerr := json.Unmarshal(respBody, &ar); jerr != nil { return fmt.Errorf("decode telegram response (status %d): %w", resp.StatusCode, jerr) } if !ar.OK { return fmt.Errorf("telegram deleteWebhook error (code=%d): %s", ar.ErrorCode, ar.Description) } return nil } type WebhookInfo struct { URL string `json:"url"` HasCustomCert bool `json:"has_custom_certificate"` PendingUpdateCount int `json:"pending_update_count"` LastErrorDate int64 `json:"last_error_date,omitempty"` LastErrorMessage string `json:"last_error_message,omitempty"` } func (c *TelegramClient) GetWebhookInfo(ctx context.Context, token string) (*WebhookInfo, error) { endpoint := fmt.Sprintf("%s/bot%s/getWebhookInfo", c.apiBase, token) req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var ar apiResponse if jerr := json.Unmarshal(respBody, &ar); jerr != nil { return nil, fmt.Errorf("decode telegram response: %w", jerr) } if !ar.OK { return nil, fmt.Errorf("telegram getWebhookInfo error: %s", ar.Description) } var info WebhookInfo if jerr := json.Unmarshal(ar.Result, &info); jerr != nil { return nil, fmt.Errorf("decode webhook info: %w", jerr) } return &info, nil } // botBuildURL is exposed for tests; not used directly. var _ = url.Parse