mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
interact: support authorizer
This commit is contained in:
parent
086127e8f7
commit
72a925f659
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
prefixed "github.com/x-cray/logrus-prefixed-formatter"
|
||||||
tb "gopkg.in/tucnak/telebot.v2"
|
tb "gopkg.in/tucnak/telebot.v2"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
||||||
|
@ -104,6 +105,8 @@ func (m *PositionInteraction) Commands(i *interact.Interact) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.SetFormatter(&prefixed.TextFormatter{})
|
||||||
|
|
||||||
b, err := tb.NewBot(tb.Settings{
|
b, err := tb.NewBot(tb.Settings{
|
||||||
// You can also set custom API URL.
|
// You can also set custom API URL.
|
||||||
// If field is empty it equals to "https://api.telegram.org".
|
// If field is empty it equals to "https://api.telegram.org".
|
||||||
|
|
|
@ -16,7 +16,13 @@ const (
|
||||||
|
|
||||||
var ErrAuthenticationFailed = errors.New("authentication failed")
|
var ErrAuthenticationFailed = errors.New("authentication failed")
|
||||||
|
|
||||||
|
type Authorizer interface {
|
||||||
|
Authorize() error
|
||||||
|
}
|
||||||
|
|
||||||
type AuthInteract struct {
|
type AuthInteract struct {
|
||||||
|
Strict bool `json:"strict,omitempty"`
|
||||||
|
|
||||||
Mode AuthMode `json:"authMode"`
|
Mode AuthMode `json:"authMode"`
|
||||||
|
|
||||||
Token string `json:"authToken,omitempty"`
|
Token string `json:"authToken,omitempty"`
|
||||||
|
@ -25,21 +31,41 @@ type AuthInteract struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *AuthInteract) Commands(interact *Interact) {
|
func (it *AuthInteract) Commands(interact *Interact) {
|
||||||
|
if it.Strict {
|
||||||
|
interact.Command("/auth", func(reply Reply) error {
|
||||||
|
reply.Message("Enter your authentication token")
|
||||||
|
return nil
|
||||||
|
}).Next(func(token string, reply Reply) error {
|
||||||
|
if token == it.Token {
|
||||||
|
reply.Message("Token passed, please enter your one-time password")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrAuthenticationFailed
|
||||||
|
}).NamedNext(StateAuthenticated, func(code string, reply Reply, authorizer Authorizer) error {
|
||||||
|
if totp.Validate(code, it.OneTimePasswordKey.Secret()) {
|
||||||
|
reply.Message("Great! You're authenticated!")
|
||||||
|
return authorizer.Authorize()
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.Message("Incorrect authentication code")
|
||||||
|
return ErrAuthenticationFailed
|
||||||
|
})
|
||||||
|
} else {
|
||||||
interact.Command("/auth", func(reply Reply) error {
|
interact.Command("/auth", func(reply Reply) error {
|
||||||
reply.Message("Enter your authentication code")
|
reply.Message("Enter your authentication code")
|
||||||
return nil
|
return nil
|
||||||
}).NamedNext(StateAuthenticated, func(reply Reply, code string) error {
|
}).NamedNext(StateAuthenticated, func(code string, reply Reply, authorizer Authorizer) error {
|
||||||
switch it.Mode {
|
switch it.Mode {
|
||||||
case AuthModeToken:
|
case AuthModeToken:
|
||||||
if code == it.Token {
|
if code == it.Token {
|
||||||
reply.Message("Great! You're authenticated!")
|
reply.Message("Great! You're authenticated!")
|
||||||
return nil
|
return authorizer.Authorize()
|
||||||
}
|
}
|
||||||
|
|
||||||
case AuthModeOTP:
|
case AuthModeOTP:
|
||||||
if totp.Validate(code, it.OneTimePasswordKey.Secret()) {
|
if totp.Validate(code, it.OneTimePasswordKey.Secret()) {
|
||||||
reply.Message("Great! You're authenticated!")
|
reply.Message("Great! You're authenticated!")
|
||||||
return nil
|
return authorizer.Authorize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,3 +73,5 @@ func (it *AuthInteract) Commands(interact *Interact) {
|
||||||
return ErrAuthenticationFailed
|
return ErrAuthenticationFailed
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ type Reply interface {
|
||||||
RemoveKeyboard()
|
RemoveKeyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Responder func(reply Reply, response string) error
|
// Responder defines the logic of responding the message
|
||||||
|
type Responder func(message string, reply Reply, ctxObjects ...interface{}) error
|
||||||
|
|
||||||
type CustomInteraction interface {
|
type CustomInteraction interface {
|
||||||
Commands(interact *Interact)
|
Commands(interact *Interact)
|
||||||
|
@ -41,7 +42,7 @@ type CommandResponder interface {
|
||||||
type Messenger interface {
|
type Messenger interface {
|
||||||
TextMessageResponder
|
TextMessageResponder
|
||||||
CommandResponder
|
CommandResponder
|
||||||
Start()
|
Start(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interact implements the interaction between bot and message software.
|
// Interact implements the interaction between bot and message software.
|
||||||
|
@ -108,7 +109,7 @@ func (it *Interact) getNextState(currentState State) (nextState State, final boo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Interact) setState(s State) {
|
func (it *Interact) setState(s State) {
|
||||||
log.Infof("[interact]: transiting state from %s -> %s", it.currentState, s)
|
log.Infof("[interact] transiting state from %s -> %s", it.currentState, s)
|
||||||
it.currentState = s
|
it.currentState = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,8 +180,9 @@ func (it *Interact) runCommand(command string, args []string, ctxObjects ...inte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Interact) SetMessenger(messenger Messenger) {
|
func (it *Interact) SetMessenger(messenger Messenger) {
|
||||||
messenger.SetTextMessageResponder(func(reply Reply, response string) error {
|
// pass Responder function
|
||||||
return it.handleResponse(response, reply)
|
messenger.SetTextMessageResponder(func(message string, reply Reply, ctxObjects ...interface{}) error {
|
||||||
|
return it.handleResponse(message, append(ctxObjects, reply)...)
|
||||||
})
|
})
|
||||||
it.messenger = messenger
|
it.messenger = messenger
|
||||||
}
|
}
|
||||||
|
@ -218,9 +220,9 @@ func (it *Interact) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
commandName := n
|
commandName := n
|
||||||
it.messenger.AddCommand(commandName, func(reply Reply, response string) error {
|
it.messenger.AddCommand(commandName, func(message string, reply Reply, ctxObjects ...interface{}) error {
|
||||||
args := parseCommand(response)
|
args := parseCommand(message)
|
||||||
return it.runCommand(commandName, args, reply)
|
return it.runCommand(commandName, args, append(ctxObjects, reply)...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +235,7 @@ func (it *Interact) Start(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use go routine and context
|
// TODO: use go routine and context
|
||||||
it.messenger.Start()
|
it.messenger.Start(ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +258,6 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
fv := reflect.ValueOf(f)
|
fv := reflect.ValueOf(f)
|
||||||
ft := reflect.TypeOf(f)
|
ft := reflect.TypeOf(f)
|
||||||
|
|
||||||
objectIndex := 0
|
|
||||||
argIndex := 0
|
argIndex := 0
|
||||||
|
|
||||||
var rArgs []reflect.Value
|
var rArgs []reflect.Value
|
||||||
|
@ -268,11 +269,7 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
if objectIndex >= len(objects) {
|
for oi := 0; oi < len(objects); oi++ {
|
||||||
return "", fmt.Errorf("found interface type %s, but object args are empty", at)
|
|
||||||
}
|
|
||||||
|
|
||||||
for oi := objectIndex; oi < len(objects); oi++ {
|
|
||||||
obj := objects[oi]
|
obj := objects[oi]
|
||||||
objT := reflect.TypeOf(obj)
|
objT := reflect.TypeOf(obj)
|
||||||
objV := reflect.ValueOf(obj)
|
objV := reflect.ValueOf(obj)
|
||||||
|
@ -286,7 +283,6 @@ func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{})
|
||||||
if objT.Implements(at) {
|
if objT.Implements(at) {
|
||||||
found = true
|
found = true
|
||||||
rArgs = append(rArgs, objV)
|
rArgs = append(rArgs, objV)
|
||||||
objectIndex = oi + 1
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -38,56 +39,85 @@ func (r *TelegramReply) build() {
|
||||||
r.menu.Reply(rows...)
|
r.menu.Reply(rows...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TelegramAuthorizer struct {
|
||||||
|
Telegram *Telegram
|
||||||
|
Message *telebot.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TelegramAuthorizer) Authorize() error {
|
||||||
|
a.Telegram.Owner = a.Message.Sender
|
||||||
|
a.Telegram.OwnerChat = a.Message.Chat
|
||||||
|
|
||||||
|
log.Infof("[interact][telegram] authorized owner %+v and chat %+v", a.Message.Sender, a.Message.Chat)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Telegram struct {
|
type Telegram struct {
|
||||||
Bot *telebot.Bot
|
Bot *telebot.Bot `json:"-"`
|
||||||
|
|
||||||
|
// Owner is the authorized bot owner
|
||||||
|
// This field is exported in order to be stored in file
|
||||||
|
Owner *telebot.User `json:"owner"`
|
||||||
|
|
||||||
|
// OwnerChat is the chat of the authorized bot owner
|
||||||
|
// This field is exported in order to be stored in file
|
||||||
|
OwnerChat *telebot.Chat `json:"chat"`
|
||||||
|
|
||||||
// textMessageResponder is used for interact to register its message handler
|
// textMessageResponder is used for interact to register its message handler
|
||||||
textMessageResponder Responder
|
textMessageResponder Responder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Telegram) SetTextMessageResponder(textMessageResponder Responder) {
|
func (tm *Telegram) newAuthorizer(message *telebot.Message) *TelegramAuthorizer {
|
||||||
b.textMessageResponder = textMessageResponder
|
return &TelegramAuthorizer{
|
||||||
|
Telegram: tm,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Telegram) Start() {
|
func (tm *Telegram) SetTextMessageResponder(textMessageResponder Responder) {
|
||||||
b.Bot.Handle(telebot.OnText, func(m *telebot.Message) {
|
tm.textMessageResponder = textMessageResponder
|
||||||
log.Infof("onText: %+v", m)
|
}
|
||||||
|
|
||||||
reply := b.newReply()
|
func (tm *Telegram) Start(context.Context) {
|
||||||
if b.textMessageResponder != nil {
|
tm.Bot.Handle(telebot.OnText, func(m *telebot.Message) {
|
||||||
if err := b.textMessageResponder(reply, m.Text); err != nil {
|
log.Infof("[interact][telegram] onText: %+v", m)
|
||||||
log.WithError(err).Errorf("response handling error")
|
|
||||||
|
authorizer := tm.newAuthorizer(m)
|
||||||
|
reply := tm.newReply()
|
||||||
|
if tm.textMessageResponder != nil {
|
||||||
|
if err := tm.textMessageResponder(m.Text, reply, authorizer); err != nil {
|
||||||
|
log.WithError(err).Errorf("[interact][telegram] response handling error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.build()
|
reply.build()
|
||||||
if _, err := b.Bot.Send(m.Sender, reply.message, reply.menu); err != nil {
|
if _, err := tm.Bot.Send(m.Sender, reply.message, reply.menu); err != nil {
|
||||||
log.WithError(err).Errorf("message send error")
|
log.WithError(err).Errorf("[interact][telegram] message send error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
go b.Bot.Start()
|
go tm.Bot.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Telegram) AddCommand(command string, responder Responder) {
|
func (tm *Telegram) AddCommand(command string, responder Responder) {
|
||||||
b.Bot.Handle(command, func(m *telebot.Message) {
|
tm.Bot.Handle(command, func(m *telebot.Message) {
|
||||||
reply := b.newReply()
|
reply := tm.newReply()
|
||||||
if err := responder(reply, m.Payload); err != nil {
|
if err := responder(m.Payload, reply); err != nil {
|
||||||
log.WithError(err).Errorf("responder error")
|
log.WithError(err).Errorf("[interact][telegram] responder error")
|
||||||
b.Bot.Send(m.Sender, fmt.Sprintf("error: %v", err))
|
tm.Bot.Send(m.Sender, fmt.Sprintf("error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// build up the response objects
|
// build up the response objects
|
||||||
reply.build()
|
reply.build()
|
||||||
if _, err := b.Bot.Send(m.Sender, reply.message, reply.menu); err != nil {
|
if _, err := tm.Bot.Send(m.Sender, reply.message, reply.menu); err != nil {
|
||||||
log.WithError(err).Errorf("message send error")
|
log.WithError(err).Errorf("[interact][telegram] message send error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Telegram) newReply() *TelegramReply {
|
func (tm *Telegram) newReply() *TelegramReply {
|
||||||
return &TelegramReply{
|
return &TelegramReply{
|
||||||
bot: b.Bot,
|
bot: tm.Bot,
|
||||||
menu: &telebot.ReplyMarkup{ResizeReplyKeyboard: true},
|
menu: &telebot.ReplyMarkup{ResizeReplyKeyboard: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user