mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
telegram: Fix pushing events to an authorised client list (#1208)
* fix_communications_authorised_clients * Telegram: Link config to authorised clients list * Telegram: Prevent multiple spam messages from unauthed user * Telegram: Improve command handling for authenticated users Telegram doesn't allow you to easily fetch the user ID of a user unless they have previously sent you a message and is currently waiting to be processed, or if they message you on the fly once the bot is connected. This ensures that the user ID is stored for future usage upon a single successful auth command. It also fixes the offset as the previous code wouldn't be able to process incoming messages once connected and instead only relay them. * Bump docs * default to UTC time in case bot is run on a server with diff time zones * Enhance config for already upgraded configs --------- Co-authored-by: shanhuhai5739 <shanhu5739@gmail.com>
This commit is contained in:
@@ -17,22 +17,28 @@ developed by Telegram Messenger LLP
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-communications-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
+ See the individual package example below. NOTE: For privacy considerations, it's not possible to directly request a user's ID through the
|
||||
Telegram Bot API unless the user interacts first. The user must message the bot directly. This allows the bot to identify and save the user's ID.
|
||||
If this wasn't set initially, the user's ID will be stored by this package following a successful authentication when any supported command is issued.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/telegram"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/telegram"
|
||||
)
|
||||
|
||||
t := new(telegram.Telegram)
|
||||
|
||||
// Define Telegram configuration
|
||||
commsConfig := config.CommunicationsConfig{TelegramConfig: config.TelegramConfig{
|
||||
Name: "Telegram",
|
||||
Enabled: true,
|
||||
Verbose: false,
|
||||
VerificationToken: "token",
|
||||
}}
|
||||
commsConfig := &base.CommunicationsConfig{
|
||||
TelegramConfig: base.TelegramConfig{
|
||||
Name: "Telegram",
|
||||
Enabled: true,
|
||||
Verbose: false,
|
||||
VerificationToken: "token",
|
||||
AuthorisedClients: map[string]int64{"pepe": 0}, // 0 represents a placeholder for the user's ID, see note above for more info.
|
||||
},
|
||||
}
|
||||
|
||||
t.Setup(commsConfig)
|
||||
err := t.Connect
|
||||
@@ -46,7 +52,6 @@ via Telegram:
|
||||
/start - Will authenticate your ID
|
||||
/status - Displays the status of the bot
|
||||
/help - Displays current command list
|
||||
/settings - Displays current bot settings
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
@@ -45,7 +45,7 @@ func (b *Base) GetName() string {
|
||||
func (b *Base) GetStatus() string {
|
||||
return `
|
||||
GoCryptoTrader Service: Online
|
||||
Service Started: ` + b.ServiceStarted.String()
|
||||
Service Started: ` + b.ServiceStarted.UTC().String()
|
||||
}
|
||||
|
||||
// SetServiceStarted sets the time the service started
|
||||
@@ -117,8 +117,9 @@ type SMTPConfig struct {
|
||||
|
||||
// TelegramConfig holds all variables to start and run the Telegram package
|
||||
type TelegramConfig struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Verbose bool `json:"verbose"`
|
||||
VerificationToken string `json:"verificationToken"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Verbose bool `json:"verbose"`
|
||||
VerificationToken string `json:"verificationToken"`
|
||||
AuthorisedClients map[string]int64 `json:"authorisedClients"`
|
||||
}
|
||||
|
||||
@@ -35,22 +35,28 @@ developed by Telegram Messenger LLP
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-communications-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
+ See the individual package example below. NOTE: For privacy considerations, it's not possible to directly request a user's ID through the
|
||||
Telegram Bot API unless the user interacts first. The user must message the bot directly. This allows the bot to identify and save the user's ID.
|
||||
If this wasn't set initially, the user's ID will be stored by this package following a successful authentication when any supported command is issued.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/telegram"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/telegram"
|
||||
)
|
||||
|
||||
t := new(telegram.Telegram)
|
||||
|
||||
// Define Telegram configuration
|
||||
commsConfig := config.CommunicationsConfig{TelegramConfig: config.TelegramConfig{
|
||||
Name: "Telegram",
|
||||
Enabled: true,
|
||||
Verbose: false,
|
||||
VerificationToken: "token",
|
||||
}}
|
||||
commsConfig := &base.CommunicationsConfig{
|
||||
TelegramConfig: base.TelegramConfig{
|
||||
Name: "Telegram",
|
||||
Enabled: true,
|
||||
Verbose: false,
|
||||
VerificationToken: "token",
|
||||
AuthorisedClients: map[string]int64{"pepe": 0}, // 0 represents a placeholder for the user's ID, see note above for more info.
|
||||
},
|
||||
}
|
||||
|
||||
t.Setup(commsConfig)
|
||||
err := t.Connect
|
||||
@@ -64,7 +70,6 @@ via Telegram:
|
||||
/start - Will authenticate your ID
|
||||
/status - Displays the status of the bot
|
||||
/help - Displays current command list
|
||||
/settings - Displays current bot settings
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,17 +27,15 @@ const (
|
||||
methodGetUpdates = "getUpdates"
|
||||
methodSendMessage = "sendMessage"
|
||||
|
||||
cmdStart = "/start"
|
||||
cmdStatus = "/status"
|
||||
cmdHelp = "/help"
|
||||
cmdSettings = "/settings"
|
||||
cmdStart = "/start"
|
||||
cmdStatus = "/status"
|
||||
cmdHelp = "/help"
|
||||
|
||||
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`
|
||||
/help - Displays current command list`
|
||||
|
||||
talkRoot = "GoCryptoTrader bot"
|
||||
)
|
||||
@@ -44,6 +44,9 @@ var (
|
||||
// ErrWaiter is the default timer to wait if an err occurs
|
||||
// before retrying after successfully connecting
|
||||
ErrWaiter = time.Second * 30
|
||||
|
||||
// ErrNotConnected is the error message returned if Telegram is not connected
|
||||
ErrNotConnected = errors.New("Telegram not connected")
|
||||
)
|
||||
|
||||
// Telegram is the overarching type across this package
|
||||
@@ -52,7 +55,7 @@ type Telegram struct {
|
||||
initConnected bool
|
||||
Token string
|
||||
Offset int64
|
||||
AuthorisedClients []int64
|
||||
AuthorisedClients map[string]int64
|
||||
}
|
||||
|
||||
// IsConnected returns whether or not the connection is connected
|
||||
@@ -64,6 +67,7 @@ func (t *Telegram) Setup(cfg *base.CommunicationsConfig) {
|
||||
t.Enabled = cfg.TelegramConfig.Enabled
|
||||
t.Token = cfg.TelegramConfig.VerificationToken
|
||||
t.Verbose = cfg.TelegramConfig.Verbose
|
||||
t.AuthorisedClients = cfg.TelegramConfig.AuthorisedClients
|
||||
}
|
||||
|
||||
// Connect starts an initial connection
|
||||
@@ -80,15 +84,24 @@ func (t *Telegram) Connect() error {
|
||||
|
||||
// PushEvent sends an event to a supplied recipient list via telegram
|
||||
func (t *Telegram) PushEvent(event base.Event) error {
|
||||
if !t.Connected {
|
||||
return ErrNotConnected
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var errors error
|
||||
for user, ID := range t.AuthorisedClients {
|
||||
if ID == 0 {
|
||||
log.Warnf(log.CommunicationMgr, "Telegram: Unable to send message to %s as their ID isn't set. A user must issue any supported command to begin a session.\n", user)
|
||||
continue
|
||||
}
|
||||
if err := t.SendMessage(msg, ID); err != nil {
|
||||
errors = common.AppendError(errors, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors
|
||||
}
|
||||
|
||||
// PollerStart starts the long polling sequence
|
||||
@@ -116,7 +129,11 @@ func (t *Telegram) PollerStart() {
|
||||
|
||||
for i := range resp.Result {
|
||||
if resp.Result[i].UpdateID > t.Offset {
|
||||
if string(resp.Result[i].Message.Text[0]) == "/" {
|
||||
username := resp.Result[i].Message.From.UserName
|
||||
if id, ok := t.AuthorisedClients[username]; ok && resp.Result[i].Message.Text[0] == '/' {
|
||||
if id == 0 {
|
||||
t.AuthorisedClients[username] = resp.Result[i].Message.From.ID
|
||||
}
|
||||
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)
|
||||
@@ -141,14 +158,25 @@ func (t *Telegram) InitialConnect() error {
|
||||
return errors.New(resp.Description)
|
||||
}
|
||||
|
||||
warmWelcomeList := make(map[string]int64)
|
||||
knownBadUsers := make(map[string]bool) // Used to prevent multiple warnings for the same unauthorised user
|
||||
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
|
||||
if resp.Result[i].Message.From.UserName != "" && resp.Result[i].Message.From.ID != 0 {
|
||||
username := resp.Result[i].Message.From.UserName
|
||||
if _, ok := t.AuthorisedClients[username]; !ok {
|
||||
if !knownBadUsers[username] {
|
||||
log.Warnf(log.CommunicationMgr, "Telegram: Received message from unauthorised user: %s\n", username)
|
||||
knownBadUsers[username] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.AuthorisedClients[username] = resp.Result[i].Message.From.ID
|
||||
}
|
||||
}
|
||||
|
||||
for userName, ID := range warmWelcomeList {
|
||||
for userName, ID := range t.AuthorisedClients {
|
||||
if ID == 0 {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
@@ -188,7 +216,11 @@ func (t *Telegram) HandleMessages(text string, chatID int64) error {
|
||||
// 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)
|
||||
vals := url.Values{}
|
||||
if t.Offset != 0 {
|
||||
vals.Set("offset", strconv.FormatInt(t.Offset+1, 10))
|
||||
}
|
||||
path := common.EncodeURLValues(fmt.Sprintf(apiURL, t.Token, methodGetUpdates), vals)
|
||||
return newUpdates, t.SendHTTPRequest(path, nil, &newUpdates)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/communications/base"
|
||||
@@ -19,12 +20,13 @@ func TestSetup(t *testing.T) {
|
||||
Enabled: false,
|
||||
Verbose: false,
|
||||
VerificationToken: "testest",
|
||||
AuthorisedClients: map[string]int64{"sender": 0},
|
||||
},
|
||||
}}
|
||||
commsCfg := cfg.GetCommunicationsConfig()
|
||||
var T Telegram
|
||||
T.Setup(&commsCfg)
|
||||
if T.Name != "Telegram" || T.Enabled || T.Token != "testest" || T.Verbose {
|
||||
if T.Name != "Telegram" || T.Enabled || T.Token != "testest" || T.Verbose || len(T.AuthorisedClients) != 1 {
|
||||
t.Error("telegram Setup() error, unexpected setup values",
|
||||
T.Name,
|
||||
T.Enabled,
|
||||
@@ -45,10 +47,18 @@ func TestPushEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
var T Telegram
|
||||
err := T.PushEvent(base.Event{})
|
||||
if err != nil {
|
||||
t.Error("telegram PushEvent() error", err)
|
||||
if !errors.Is(err, ErrNotConnected) {
|
||||
t.Errorf("expected %s, got %s", ErrNotConnected, err)
|
||||
}
|
||||
T.AuthorisedClients = append(T.AuthorisedClients, 1337)
|
||||
|
||||
T.Connected = true
|
||||
T.AuthorisedClients = map[string]int64{"sender": 0}
|
||||
err = T.PushEvent(base.Event{})
|
||||
if err != nil {
|
||||
t.Errorf("expected nil, got %s", err)
|
||||
}
|
||||
|
||||
T.AuthorisedClients = map[string]int64{"sender": 1337}
|
||||
err = T.PushEvent(base.Event{})
|
||||
if err.Error() != testErrNotFound {
|
||||
t.Errorf("telegram PushEvent() error, expected 'Not found' got '%s'",
|
||||
@@ -75,11 +85,6 @@ func TestHandleMessages(t *testing.T) {
|
||||
t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'",
|
||||
err)
|
||||
}
|
||||
err = T.HandleMessages(cmdSettings, chatID)
|
||||
if err.Error() != testErrNotFound {
|
||||
t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'",
|
||||
err)
|
||||
}
|
||||
err = T.HandleMessages("Not a command", chatID)
|
||||
if err.Error() != testErrNotFound {
|
||||
t.Errorf("telegram HandleMessages() error, expected 'Not found' got '%s'",
|
||||
|
||||
@@ -302,6 +302,10 @@ func (c *Config) CheckCommunicationsConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Communications.TelegramConfig.AuthorisedClients == nil {
|
||||
c.Communications.TelegramConfig.AuthorisedClients = map[string]int64{"user_example": 0}
|
||||
}
|
||||
|
||||
if c.Communications.SlackConfig.Name != "Slack" ||
|
||||
c.Communications.SMSGlobalConfig.Name != "SMSGlobal" ||
|
||||
c.Communications.SMTPConfig.Name != "SMTP" ||
|
||||
@@ -334,7 +338,10 @@ func (c *Config) CheckCommunicationsConfig() {
|
||||
}
|
||||
}
|
||||
if c.Communications.TelegramConfig.Enabled {
|
||||
if c.Communications.TelegramConfig.VerificationToken == "" {
|
||||
if _, ok := c.Communications.TelegramConfig.AuthorisedClients["user_example"]; ok ||
|
||||
len(c.Communications.TelegramConfig.AuthorisedClients) == 0 ||
|
||||
c.Communications.TelegramConfig.VerificationToken == "" ||
|
||||
c.Communications.TelegramConfig.VerificationToken == "testest" {
|
||||
c.Communications.TelegramConfig.Enabled = false
|
||||
log.Warnln(log.ConfigMgr, "Telegram enabled in config but variable data not set, disabling.")
|
||||
}
|
||||
|
||||
@@ -173,7 +173,10 @@
|
||||
"name": "Telegram",
|
||||
"enabled": false,
|
||||
"verbose": false,
|
||||
"verificationToken": "testest"
|
||||
"verificationToken": "testest",
|
||||
"authorisedClients": {
|
||||
"user_example": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteControl": {
|
||||
|
||||
5
testdata/configtest.json
vendored
5
testdata/configtest.json
vendored
@@ -173,7 +173,10 @@
|
||||
"name": "Telegram",
|
||||
"enabled": false,
|
||||
"verbose": false,
|
||||
"verificationToken": "testest"
|
||||
"verificationToken": "testest",
|
||||
"authorisedClients": {
|
||||
"user_example": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteControl": {
|
||||
|
||||
Reference in New Issue
Block a user