Merge pull request #67 from c9s/feature/add-telegram-bot-notifier

feature: add telegram bot notifier
This commit is contained in:
Che-Chia (David) Chang 2020-12-06 14:12:32 +08:00 committed by GitHub
commit 1b17cba2eb
6 changed files with 158 additions and 10 deletions

View File

@ -40,6 +40,9 @@ Add your dotenv file:
```
SLACK_TOKEN=
TELEGRAM_BOT_TOKEN=
TELEGRAM_AUTH_TOKEN=
BINANCE_API_KEY=
BINANCE_API_SECRET=
@ -210,6 +213,21 @@ streambook := types.NewStreamBook(symbol)
streambook.BindStream(stream)
```
## Telegram Integration
- In telegram: @botFather
- /newbot
- input bot display name. ex. `bbgo_bot`
- input bot username. This should be global unique. ex. `PeqFqJxP_bbgo_bot`
- Botfather return bot token. Keep bot token safe
- Set `TELEGRAM_BOT_TOKEN` in `.env.local`
- Set `TELEGRAM_AUTH_TOKEN` in `.env.local`. Generate your own auth token. ex. 92463901, or kx2UX@eM
- Run bbgo
- In telegram: search your bot `PeqFqJxP_bbgo_bot`
- /start
- /auth 92463901
- done! your session will route to telegram
## Support
### By contributing pull requests

1
go.mod
View File

@ -43,6 +43,7 @@ require (
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
gonum.org/v1/gonum v0.8.1
gopkg.in/tucnak/telebot.v2 v2.3.5
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
)

3
go.sum
View File

@ -276,6 +276,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
@ -443,6 +444,8 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tucnak/telebot.v2 v2.3.5 h1:TdMJTlG8kvepsvZdy/gPeYEBdwKdwFFjH1AQTua9BOU=
gopkg.in/tucnak/telebot.v2 v2.3.5/go.mod h1:BgaIIx50PSRS9pG59JH+geT82cfvoJU/IaI5TJdN3v8=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -39,6 +39,9 @@ func init() {
RootCmd.PersistentFlags().String("slack-channel", "dev-bbgo", "slack trading channel")
RootCmd.PersistentFlags().String("slack-error-channel", "bbgo-error", "slack error channel")
RootCmd.PersistentFlags().String("telegram-bot-token", "", "telegram bot token from bot father")
RootCmd.PersistentFlags().String("telegram-auth-token", "", "telegram auth token")
RootCmd.PersistentFlags().String("binance-api-key", "", "binance api key")
RootCmd.PersistentFlags().String("binance-api-secret", "", "binance api secret")
@ -54,18 +57,18 @@ func Execute() {
// setup the config paths for looking up the config file
/*
viper.AddConfigPath("config")
viper.AddConfigPath("$HOME/.bbgo")
viper.AddConfigPath("/etc/bbgo")
viper.AddConfigPath("config")
viper.AddConfigPath("$HOME/.bbgo")
viper.AddConfigPath("/etc/bbgo")
// set the config file name and format for loading the config file.
viper.SetConfigName("bbgo")
viper.SetConfigType("yaml")
// set the config file name and format for loading the config file.
viper.SetConfigName("bbgo")
viper.SetConfigType("yaml")
err := viper.ReadInConfig()
if err != nil {
log.WithError(err).Fatal("failed to load config file")
}
err := viper.ReadInConfig()
if err != nil {
log.WithError(err).Fatal("failed to load config file")
}
*/
// Once the flags are defined, we can bind config keys with flags.

View File

@ -22,6 +22,7 @@ import (
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
"github.com/c9s/bbgo/pkg/notifier/slacknotifier"
"github.com/c9s/bbgo/pkg/notifier/telegramnotifier"
"github.com/c9s/bbgo/pkg/slack/slacklog"
"github.com/c9s/bbgo/pkg/types"
)
@ -121,6 +122,20 @@ func runConfig(basectx context.Context, userConfig *bbgo.Config) error {
}
}
// for telegram
telegramBotToken := viper.GetString("telegram-bot-token")
telegramAuthToken := viper.GetString("telegram-auth-token")
if len(telegramBotToken) > 0 && len(telegramAuthToken) > 0 {
log.Infof("adding telegram notifier")
var notifier = telegramnotifier.New(telegramBotToken, telegramAuthToken)
// start telegram bot
go notifier.Bot.Start()
notification.AddNotifier(notifier)
}
environ.Notifiability = notification
if userConfig.Notifications != nil {

View File

@ -0,0 +1,108 @@
package telegramnotifier
import (
"fmt"
"time"
log "github.com/sirupsen/logrus"
tb "gopkg.in/tucnak/telebot.v2"
)
type Notifier struct {
Bot *tb.Bot
chatUser *tb.User
channel string
}
type NotifyOption func(notifier *Notifier)
// start bot daemon
func New(botToken, authToken string, options ...NotifyOption) *Notifier {
notifier := &Notifier{
chatUser: &tb.User{},
Bot: &tb.Bot{},
}
for _, o := range options {
o(notifier)
}
bot, err := tb.NewBot(tb.Settings{
// You can also set custom API URL.
// If field is empty it equals to "https://api.telegram.org".
// URL: "http://195.129.111.17:8012",
Token: botToken,
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
})
if err != nil {
panic(err)
}
bot.Handle("/help", func(m *tb.Message) {
helpMsg := `
help - print help message
auth - authorize current telegram user to access telegram bot with authToken. ex. /auth my-token
info - print information about current chat
`
bot.Send(m.Sender, helpMsg)
})
// auth check authToken and then set sender id
bot.Handle("/auth", func(m *tb.Message) {
log.Info("Receive message: ", m) //debug
if m.Payload == authToken {
notifier.chatUser = m.Sender
bot.Send(m.Sender, "User authorized")
} else {
bot.Send(m.Sender, "Error: User authorization failed. Auth token does not match!")
}
})
bot.Handle("/info", func(m *tb.Message) {
if m.Sender.ID == notifier.chatUser.ID {
bot.Send(notifier.chatUser,
fmt.Sprintf("Welcome! your username: %s, user ID: %d",
notifier.chatUser.Username,
notifier.chatUser.ID,
))
} else {
log.Warningf("Incorrect user tried to access bot! sender username: %s id: %d", m.Sender.Username, m.Sender.ID)
}
})
notifier.Bot = bot
return notifier
}
func (n *Notifier) Notify(format string, args ...interface{}) {
n.NotifyTo(n.channel, format, args...)
}
func (n *Notifier) NotifyTo(channel, format string, args ...interface{}) {
if n.chatUser.ID == 0 {
log.Warningf("Telegram bot has no authenticated user. Skip notification")
return
}
var telegramArgsOffset = -1
var nonTelegramArgs = args
if telegramArgsOffset > -1 {
nonTelegramArgs = args[:telegramArgsOffset]
}
log.Infof(format, nonTelegramArgs...)
_, err := n.Bot.Send(n.chatUser, fmt.Sprintf(format, nonTelegramArgs...))
if err != nil {
log.WithError(err).
WithField("chatUser", n.chatUser).
Errorf("telegram error: %s", err.Error())
}
return
}