Phase 1 MVP — echo bot factory
All checks were successful
Docker Build / build-and-push-image (push) Successful in 1m8s
All checks were successful
Docker Build / build-and-push-image (push) Successful in 1m8s
This commit is contained in:
166
telegram.go
Normal file
166
telegram.go
Normal file
@@ -0,0 +1,166 @@
|
||||
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
|
||||
Reference in New Issue
Block a user