182 lines
4.2 KiB
Go
182 lines
4.2 KiB
Go
|
|
package telegram
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"strconv"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"laodingbot/internal/logger"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Bot struct {
|
||
|
|
token string
|
||
|
|
baseURL string
|
||
|
|
http *http.Client
|
||
|
|
pollTimeout int
|
||
|
|
log *logger.Logger
|
||
|
|
}
|
||
|
|
|
||
|
|
type IncomingMessage struct {
|
||
|
|
ChatID string
|
||
|
|
UserID string
|
||
|
|
Text string
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewBot(token string, pollTimeout int, log *logger.Logger) (*Bot, error) {
|
||
|
|
if token == "" {
|
||
|
|
return nil, fmt.Errorf("empty telegram token")
|
||
|
|
}
|
||
|
|
if pollTimeout <= 0 {
|
||
|
|
pollTimeout = 30
|
||
|
|
}
|
||
|
|
return &Bot{
|
||
|
|
token: token,
|
||
|
|
baseURL: "https://api.telegram.org/bot" + token,
|
||
|
|
http: &http.Client{Timeout: 70 * time.Second},
|
||
|
|
pollTimeout: pollTimeout,
|
||
|
|
log: log,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *Bot) Run(ctx context.Context, handler func(context.Context, IncomingMessage) (string, error)) error {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Infof("telegram polling started timeout=%ds", b.pollTimeout)
|
||
|
|
}
|
||
|
|
offset := 0
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Infof("telegram polling stopped: %v", ctx.Err())
|
||
|
|
}
|
||
|
|
return ctx.Err()
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
|
||
|
|
updates, err := b.getUpdates(ctx, offset)
|
||
|
|
if err != nil {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Errorf("telegram getUpdates failed err=%v", err)
|
||
|
|
}
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if b.log != nil && len(updates) > 0 {
|
||
|
|
b.log.Debugf("telegram updates received count=%d offset=%d", len(updates), offset)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, u := range updates {
|
||
|
|
offset = u.UpdateID + 1
|
||
|
|
if u.Message.Text == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
in := IncomingMessage{
|
||
|
|
ChatID: strconv.FormatInt(u.Message.Chat.ID, 10),
|
||
|
|
UserID: strconv.FormatInt(u.Message.From.ID, 10),
|
||
|
|
Text: u.Message.Text,
|
||
|
|
}
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Infof("telegram message received chat_id=%s user_id=%s text_len=%d", in.ChatID, in.UserID, len(in.Text))
|
||
|
|
}
|
||
|
|
resp, err := handler(ctx, in)
|
||
|
|
if err != nil {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Errorf("telegram handler failed chat_id=%s err=%v", in.ChatID, err)
|
||
|
|
}
|
||
|
|
resp = "处理失败: " + err.Error()
|
||
|
|
}
|
||
|
|
if err := b.sendMessage(ctx, in.ChatID, resp); err != nil {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Errorf("telegram sendMessage failed chat_id=%s err=%v", in.ChatID, err)
|
||
|
|
}
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Debugf("telegram message sent chat_id=%s text_len=%d", in.ChatID, len(resp))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type updatesResponse struct {
|
||
|
|
OK bool `json:"ok"`
|
||
|
|
Result []update `json:"result"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type update struct {
|
||
|
|
UpdateID int `json:"update_id"`
|
||
|
|
Message struct {
|
||
|
|
Text string `json:"text"`
|
||
|
|
Chat struct {
|
||
|
|
ID int64 `json:"id"`
|
||
|
|
} `json:"chat"`
|
||
|
|
From struct {
|
||
|
|
ID int64 `json:"id"`
|
||
|
|
} `json:"from"`
|
||
|
|
} `json:"message"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *Bot) getUpdates(ctx context.Context, offset int) ([]update, error) {
|
||
|
|
url := fmt.Sprintf("%s/getUpdates?timeout=%d&offset=%d", b.baseURL, b.pollTimeout, offset)
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
resp, err := b.http.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
raw, err := io.ReadAll(resp.Body)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
var out updatesResponse
|
||
|
|
if err := json.Unmarshal(raw, &out); err != nil {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Errorf("telegram parse getUpdates response failed err=%v", err)
|
||
|
|
}
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if !out.OK {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Warnf("telegram getUpdates not ok")
|
||
|
|
}
|
||
|
|
return nil, fmt.Errorf("telegram getUpdates failed")
|
||
|
|
}
|
||
|
|
return out.Result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *Bot) sendMessage(ctx context.Context, chatID, text string) error {
|
||
|
|
payload := map[string]string{
|
||
|
|
"chat_id": chatID,
|
||
|
|
"text": text,
|
||
|
|
}
|
||
|
|
bts, err := json.Marshal(payload)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.baseURL+"/sendMessage", bytes.NewReader(bts))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
resp, err := b.http.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||
|
|
if b.log != nil {
|
||
|
|
b.log.Warnf("telegram sendMessage bad status=%d chat_id=%s", resp.StatusCode, chatID)
|
||
|
|
}
|
||
|
|
return fmt.Errorf("telegram sendMessage status: %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|