Phase 1 MVP — echo bot factory
All checks were successful
Docker Build / build-and-push-image (push) Successful in 1m8s

This commit is contained in:
2026-05-09 12:23:59 +02:00
commit ee832de089
28 changed files with 1376 additions and 0 deletions

166
telegram.go Normal file
View 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