178 lines
3.9 KiB
Go
178 lines
3.9 KiB
Go
package pf
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Telegram struct {
|
|
token string
|
|
client *http.Client
|
|
mu sync.Mutex
|
|
username string
|
|
}
|
|
|
|
type tgResponse[T any] struct {
|
|
OK bool `json:"ok"`
|
|
Description string `json:"description"`
|
|
Result T `json:"result"`
|
|
}
|
|
|
|
type TGUpdate struct {
|
|
UpdateID int64 `json:"update_id"`
|
|
Message *TGMessage `json:"message"`
|
|
}
|
|
|
|
type TGMessage struct {
|
|
MessageID int64 `json:"message_id"`
|
|
Text string `json:"text"`
|
|
Chat TGChat `json:"chat"`
|
|
From *TGUser `json:"from"`
|
|
}
|
|
|
|
type TGChat struct {
|
|
ID int64 `json:"id"`
|
|
}
|
|
|
|
type TGUser struct {
|
|
ID int64 `json:"id"`
|
|
Username string `json:"username"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
}
|
|
|
|
type TGBotUser struct {
|
|
ID int64 `json:"id"`
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
func NewTelegram(token string) *Telegram {
|
|
return &Telegram{token: token, client: &http.Client{Timeout: 35 * time.Second}}
|
|
}
|
|
|
|
func (t *Telegram) Enabled() bool {
|
|
return strings.TrimSpace(t.token) != ""
|
|
}
|
|
|
|
func (t *Telegram) BotUsername(ctx context.Context) (string, error) {
|
|
if !t.Enabled() {
|
|
return "", nil
|
|
}
|
|
t.mu.Lock()
|
|
if t.username != "" {
|
|
username := t.username
|
|
t.mu.Unlock()
|
|
return username, nil
|
|
}
|
|
t.mu.Unlock()
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, t.apiURL("getMe"), nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
var out tgResponse[TGBotUser]
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return "", err
|
|
}
|
|
if !out.OK {
|
|
return "", errors.New(out.Description)
|
|
}
|
|
username := strings.TrimPrefix(strings.TrimSpace(out.Result.Username), "@")
|
|
if username == "" {
|
|
return "", nil
|
|
}
|
|
t.mu.Lock()
|
|
t.username = username
|
|
t.mu.Unlock()
|
|
return username, nil
|
|
}
|
|
|
|
func (t *Telegram) SendMessage(ctx context.Context, chatID string, text string) error {
|
|
if !t.Enabled() {
|
|
return nil
|
|
}
|
|
payload := map[string]any{
|
|
"chat_id": chatID,
|
|
"text": text,
|
|
"parse_mode": "HTML",
|
|
"disable_web_page_preview": false,
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, t.apiURL("sendMessage"), bytes.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
var out tgResponse[json.RawMessage]
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return err
|
|
}
|
|
if !out.OK {
|
|
return errors.New(out.Description)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *Telegram) GetUpdates(ctx context.Context, offset int64) ([]TGUpdate, error) {
|
|
if !t.Enabled() {
|
|
return nil, fmt.Errorf("TG_BOT_TOKEN не задан в k8s/secrets.yaml")
|
|
}
|
|
values := url.Values{}
|
|
values.Set("timeout", "25")
|
|
if offset > 0 {
|
|
values.Set("offset", fmt.Sprintf("%d", offset))
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, t.apiURL("getUpdates")+"?"+values.Encode(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := t.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var out tgResponse[[]TGUpdate]
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return nil, err
|
|
}
|
|
if !out.OK {
|
|
return nil, errors.New(out.Description)
|
|
}
|
|
return out.Result, nil
|
|
}
|
|
|
|
func (t *Telegram) apiURL(method string) string {
|
|
return "https://api.telegram.org/bot" + t.token + "/" + method
|
|
}
|
|
|
|
func (u TGUser) FullName() string {
|
|
name := strings.TrimSpace(strings.TrimSpace(u.FirstName) + " " + strings.TrimSpace(u.LastName))
|
|
if name != "" {
|
|
return name
|
|
}
|
|
if u.Username != "" {
|
|
return u.Username
|
|
}
|
|
return fmt.Sprintf("user_%d", u.ID)
|
|
}
|