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