mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-16 15:09:57 +00:00
* gct: phase one context awareness pass * exchanges: context propagation pass * common/requester: force context requirement * gctcli/exchanges: linter fix * rpcserver: fix test using dummy rpc server * backtester: fix comments * grpc: add correct cancel and timeout for commands * rpcserver_test: add comment on dummy server * common: deprecated SendHTTPGetRequest * linter: fix * linter: turn on no context check * apichecker: fix context linter issue * binance: use param context * common: remove checks as this gets executed before main * common: change mutex to RW as clients can be used by multiple go routines. * common: remove init and JIT default client. Unexport global variables and add protection. * common: Add comments * bithumb: after dinner mints fix
261 lines
6.3 KiB
Go
261 lines
6.3 KiB
Go
// Package telegram is used to connect to a cloud-based mobile and desktop
|
|
// messaging app using the bot API defined in
|
|
// https://core.telegram.org/bots/api#recent-changes
|
|
package telegram
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/communications/base"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
const (
|
|
apiURL = "https://api.telegram.org/bot%s/%s"
|
|
|
|
methodGetMe = "getMe"
|
|
methodGetUpdates = "getUpdates"
|
|
methodSendMessage = "sendMessage"
|
|
|
|
cmdStart = "/start"
|
|
cmdStatus = "/status"
|
|
cmdHelp = "/help"
|
|
cmdSettings = "/settings"
|
|
|
|
cmdHelpReply = `GoCryptoTrader TelegramBot, thank you for using this service!
|
|
Current commands are:
|
|
/start - Will authenticate your ID
|
|
/status - Displays the status of the bot
|
|
/help - Displays current command list
|
|
/settings - Displays current bot settings`
|
|
|
|
talkRoot = "GoCryptoTrader bot"
|
|
)
|
|
|
|
var (
|
|
// ErrWaiter is the default timer to wait if an err occurs
|
|
// before retrying after successfully connecting
|
|
ErrWaiter = time.Second * 30
|
|
)
|
|
|
|
// Telegram is the overarching type across this package
|
|
type Telegram struct {
|
|
base.Base
|
|
initConnected bool
|
|
Token string
|
|
Offset int64
|
|
AuthorisedClients []int64
|
|
}
|
|
|
|
// IsConnected returns whether or not the connection is connected
|
|
func (t *Telegram) IsConnected() bool { return t.Connected }
|
|
|
|
// Setup takes in a Telegram configuration and sets verification token
|
|
func (t *Telegram) Setup(cfg *base.CommunicationsConfig) {
|
|
t.Name = cfg.TelegramConfig.Name
|
|
t.Enabled = cfg.TelegramConfig.Enabled
|
|
t.Token = cfg.TelegramConfig.VerificationToken
|
|
t.Verbose = cfg.TelegramConfig.Verbose
|
|
}
|
|
|
|
// Connect starts an initial connection
|
|
func (t *Telegram) Connect() error {
|
|
if err := t.TestConnection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugln(log.CommunicationMgr, "Telegram: Connected successfully!")
|
|
t.Connected = true
|
|
go t.PollerStart()
|
|
return nil
|
|
}
|
|
|
|
// PushEvent sends an event to a supplied recipient list via telegram
|
|
func (t *Telegram) PushEvent(event base.Event) error {
|
|
msg := fmt.Sprintf("Type: %s Message: %s",
|
|
event.Type, event.Message)
|
|
for i := range t.AuthorisedClients {
|
|
err := t.SendMessage(msg, t.AuthorisedClients[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PollerStart starts the long polling sequence
|
|
func (t *Telegram) PollerStart() {
|
|
errWait := func(err error) {
|
|
log.Errorln(log.CommunicationMgr, err)
|
|
time.Sleep(ErrWaiter)
|
|
}
|
|
|
|
for {
|
|
if !t.initConnected {
|
|
err := t.InitialConnect()
|
|
if err != nil {
|
|
errWait(err)
|
|
continue
|
|
}
|
|
t.initConnected = true
|
|
}
|
|
|
|
resp, err := t.GetUpdates()
|
|
if err != nil {
|
|
errWait(err)
|
|
continue
|
|
}
|
|
|
|
for i := range resp.Result {
|
|
if resp.Result[i].UpdateID > t.Offset {
|
|
if string(resp.Result[i].Message.Text[0]) == "/" {
|
|
err = t.HandleMessages(resp.Result[i].Message.Text, resp.Result[i].Message.From.ID)
|
|
if err != nil {
|
|
log.Errorf(log.CommunicationMgr, "Telegram: Unable to HandleMessages. Error: %s\n", err)
|
|
continue
|
|
}
|
|
}
|
|
t.Offset = resp.Result[i].UpdateID
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// InitialConnect sets offset, and sends a welcome greeting to any associated
|
|
// IDs
|
|
func (t *Telegram) InitialConnect() error {
|
|
resp, err := t.GetUpdates()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !resp.Ok {
|
|
return errors.New(resp.Description)
|
|
}
|
|
|
|
warmWelcomeList := make(map[string]int64)
|
|
for i := range resp.Result {
|
|
if resp.Result[i].Message.From.ID != 0 {
|
|
warmWelcomeList[resp.Result[i].Message.From.UserName] = resp.Result[i].Message.From.ID
|
|
}
|
|
}
|
|
|
|
for userName, ID := range warmWelcomeList {
|
|
err = t.SendMessage(fmt.Sprintf("GoCryptoTrader bot has connected: Hello, %s!", userName), ID)
|
|
if err != nil {
|
|
log.Errorf(log.CommunicationMgr, "Telegram: Unable to send welcome message. Error: %s\n", err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(resp.Result) == 0 {
|
|
return nil
|
|
}
|
|
|
|
t.Offset = resp.Result[len(resp.Result)-1].UpdateID
|
|
return nil
|
|
}
|
|
|
|
// HandleMessages handles incoming message from the long polling routine
|
|
func (t *Telegram) HandleMessages(text string, chatID int64) error {
|
|
if t.Verbose {
|
|
log.Debugf(log.CommunicationMgr, "Telegram: Received message: %s\n", text)
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(text, cmdHelp):
|
|
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, cmdHelpReply), chatID)
|
|
|
|
case strings.Contains(text, cmdStart):
|
|
return t.SendMessage(fmt.Sprintf("%s: START COMMANDS HERE", talkRoot), chatID)
|
|
|
|
case strings.Contains(text, cmdStatus):
|
|
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetStatus()), chatID)
|
|
|
|
default:
|
|
return t.SendMessage(fmt.Sprintf("Command %s not recognized", text), chatID)
|
|
}
|
|
}
|
|
|
|
// GetUpdates gets new updates via a long poll connection
|
|
func (t *Telegram) GetUpdates() (GetUpdateResponse, error) {
|
|
var newUpdates GetUpdateResponse
|
|
path := fmt.Sprintf(apiURL, t.Token, methodGetUpdates)
|
|
return newUpdates, t.SendHTTPRequest(path, nil, &newUpdates)
|
|
}
|
|
|
|
// TestConnection tests bot's supplied authentication token
|
|
func (t *Telegram) TestConnection() error {
|
|
var isConnected User
|
|
path := fmt.Sprintf(apiURL, t.Token, methodGetMe)
|
|
|
|
err := t.SendHTTPRequest(path, nil, &isConnected)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !isConnected.Ok {
|
|
return errors.New(isConnected.Description)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SendMessage sends a message to a user by their chatID
|
|
func (t *Telegram) SendMessage(text string, chatID int64) error {
|
|
path := fmt.Sprintf(apiURL, t.Token, methodSendMessage)
|
|
|
|
messageToSend := struct {
|
|
ChatID int64 `json:"chat_id"`
|
|
Text string `json:"text"`
|
|
}{
|
|
chatID,
|
|
text,
|
|
}
|
|
|
|
json, err := json.Marshal(&messageToSend)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp := Message{}
|
|
err = t.SendHTTPRequest(path, json, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !resp.Ok {
|
|
return errors.New(resp.Description)
|
|
}
|
|
|
|
if t.Verbose {
|
|
log.Debugf(log.CommunicationMgr, "Telegram: Sent '%s'\n", text)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SendHTTPRequest sends an authenticated HTTP request
|
|
func (t *Telegram) SendHTTPRequest(path string, data []byte, result interface{}) error {
|
|
headers := make(map[string]string)
|
|
headers["content-type"] = "application/json"
|
|
|
|
resp, err := common.SendHTTPRequest(context.TODO(),
|
|
http.MethodPost,
|
|
path,
|
|
headers,
|
|
bytes.NewBuffer(data),
|
|
t.Verbose)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return json.Unmarshal(resp, result)
|
|
}
|