bbgo_origin/pkg/interact/interact.go

311 lines
6.1 KiB
Go
Raw Normal View History

2022-01-12 13:45:12 +00:00
package interact
2022-01-11 18:54:13 +00:00
import (
2022-01-13 14:15:05 +00:00
"context"
"fmt"
2022-01-11 18:54:13 +00:00
"reflect"
"strconv"
2022-01-12 09:49:34 +00:00
"strings"
"text/scanner"
2022-01-11 18:54:13 +00:00
2022-01-13 14:15:05 +00:00
log "github.com/sirupsen/logrus"
2022-01-11 18:54:13 +00:00
)
2022-01-13 14:15:05 +00:00
type Reply interface {
Message(message string)
AddButton(text string)
RemoveKeyboard()
}
2022-01-13 14:15:05 +00:00
type Responder func(reply Reply, response string) error
2022-01-13 14:15:05 +00:00
type CustomInteraction interface {
Commands(interact *Interact)
}
2022-01-13 14:15:05 +00:00
type State string
2022-01-13 14:15:05 +00:00
const (
StatePublic State = "public"
StateAuthenticated State = "authenticated"
)
2022-01-13 14:24:51 +00:00
type TextMessageResponder interface {
SetTextMessageResponder(responder Responder)
}
type CommandResponder interface {
2022-01-13 14:15:05 +00:00
AddCommand(command string, responder Responder)
2022-01-13 14:24:51 +00:00
}
type Messenger interface {
TextMessageResponder
CommandResponder
2022-01-13 14:15:05 +00:00
Start()
}
2022-01-11 18:54:13 +00:00
// Interact implements the interaction between bot and message software.
type Interact struct {
commands map[string]*Command
2022-01-13 14:15:05 +00:00
states map[State]State
statesFunc map[State]interface{}
originState, currentState State
messenger Messenger
}
func New() *Interact {
return &Interact{
2022-01-13 14:15:05 +00:00
commands: make(map[string]*Command),
originState: StatePublic,
currentState: StatePublic,
states: make(map[State]State),
statesFunc: make(map[State]interface{}),
}
}
2022-01-13 14:15:05 +00:00
func (i *Interact) SetOriginState(s State) {
i.originState = s
}
func (i *Interact) AddCustomInteraction(custom CustomInteraction) {
custom.Commands(i)
}
func (i *Interact) Command(command string, f interface{}) *Command {
cmd := NewCommand(command, f)
i.commands[command] = cmd
return cmd
}
2022-01-13 14:15:05 +00:00
func (i *Interact) getNextState(currentState State) (nextState State, final bool) {
var ok bool
2022-01-13 14:15:05 +00:00
final = false
nextState, ok = i.states[currentState]
if ok {
2022-01-13 14:15:05 +00:00
// check if it's the final state
if _, hasTransition := i.statesFunc[nextState]; !hasTransition {
final = true
}
return nextState, final
}
2022-01-13 14:15:05 +00:00
// state not found, return to the origin state
return i.originState, final
2022-01-11 18:54:13 +00:00
}
2022-01-13 14:15:05 +00:00
func (i *Interact) setState(s State) {
log.Infof("[interact]: tansiting state from %s -> %s", i.currentState, s)
i.currentState = s
}
func (i *Interact) handleResponse(text string, ctxObjects ...interface{}) error {
args := parseCommand(text)
2022-01-13 14:15:05 +00:00
f, ok := i.statesFunc[i.currentState]
if !ok {
2022-01-13 14:15:05 +00:00
return fmt.Errorf("state function of %s is not defined", i.currentState)
}
2022-01-13 14:15:05 +00:00
err := parseFuncArgsAndCall(f, args, ctxObjects...)
if err != nil {
return err
}
2022-01-13 14:15:05 +00:00
nextState, end := i.getNextState(i.currentState)
if end {
2022-01-13 14:15:05 +00:00
i.setState(i.originState)
return nil
}
2022-01-13 14:15:05 +00:00
i.setState(nextState)
return nil
}
2022-01-13 14:15:05 +00:00
func (i *Interact) runCommand(command string, args []string, ctxObjects ...interface{}) error {
cmd, ok := i.commands[command]
if !ok {
return fmt.Errorf("command %s not found", command)
}
2022-01-13 14:15:05 +00:00
i.setState(cmd.initState)
err := parseFuncArgsAndCall(cmd.F, args, ctxObjects...)
if err != nil {
return err
}
// if we can successfully execute the command, then we can go to the next state.
2022-01-13 14:15:05 +00:00
nextState, end := i.getNextState(i.currentState)
if end {
2022-01-13 14:15:05 +00:00
i.setState(i.originState)
return nil
}
2022-01-13 14:15:05 +00:00
i.setState(nextState)
return nil
2022-01-12 13:45:12 +00:00
}
2022-01-11 18:54:13 +00:00
2022-01-13 14:15:05 +00:00
func (i *Interact) SetMessenger(messenger Messenger) {
2022-01-13 14:24:51 +00:00
messenger.SetTextMessageResponder(func(reply Reply, response string) error {
return i.handleResponse(response, reply)
})
2022-01-13 14:15:05 +00:00
i.messenger = messenger
}
func (i *Interact) init() error {
for n, cmd := range i.commands {
_ = n
for s1, s2 := range cmd.states {
if _, exist := i.states[s1]; exist {
return fmt.Errorf("state %s already exists", s1)
}
i.states[s1] = s2
}
for s, f := range cmd.statesFunc {
i.statesFunc[s] = f
}
2022-01-13 14:15:05 +00:00
// register commands to the service
if i.messenger == nil {
return fmt.Errorf("messenger is not set")
}
i.messenger.AddCommand(n, func(reply Reply, response string) error {
args := parseCommand(response)
return i.runCommand(n, args, reply)
})
}
return nil
2022-01-12 13:45:12 +00:00
}
2022-01-13 14:15:05 +00:00
func (i *Interact) Start(ctx context.Context) error {
if err := i.init(); err != nil {
return err
}
// TODO: use go routine and context
i.messenger.Start()
return nil
2022-01-11 18:54:13 +00:00
}
2022-01-12 09:49:34 +00:00
func parseCommand(src string) (args []string) {
2022-01-11 18:54:13 +00:00
var s scanner.Scanner
2022-01-12 09:49:34 +00:00
s.Init(strings.NewReader(src))
s.Filename = "command"
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
text := s.TokenText()
if text[0] == '"' && text[len(text)-1] == '"' {
2022-01-12 09:49:34 +00:00
text, _ = strconv.Unquote(text)
2022-01-11 18:54:13 +00:00
}
args = append(args, text)
2022-01-11 18:54:13 +00:00
}
2022-01-12 09:49:34 +00:00
return args
2022-01-11 18:54:13 +00:00
}
func parseFuncArgsAndCall(f interface{}, args []string, objects ...interface{}) error {
2022-01-11 18:54:13 +00:00
fv := reflect.ValueOf(f)
ft := reflect.TypeOf(f)
objectIndex := 0
2022-01-12 17:48:14 +00:00
argIndex := 0
2022-01-11 18:54:13 +00:00
var rArgs []reflect.Value
for i := 0; i < ft.NumIn(); i++ {
at := ft.In(i)
switch k := at.Kind(); k {
case reflect.Interface:
found := false
2022-01-12 17:48:14 +00:00
if objectIndex >= len(objects) {
return fmt.Errorf("found interface type %s, but object args are empty", at)
}
for oi := objectIndex; oi < len(objects); oi++ {
obj := objects[oi]
objT := reflect.TypeOf(obj)
objV := reflect.ValueOf(obj)
fmt.Println(
at.PkgPath(),
at.Name(),
2022-01-12 17:48:14 +00:00
objT, "implements", at, "=", objT.Implements(at),
)
if objT.Implements(at) {
found = true
rArgs = append(rArgs, objV)
2022-01-12 17:48:14 +00:00
objectIndex = oi + 1
break
}
}
if !found {
return fmt.Errorf("can not find object implements %s", at)
}
2022-01-11 18:54:13 +00:00
case reflect.String:
2022-01-12 17:48:14 +00:00
av := reflect.ValueOf(args[argIndex])
2022-01-11 18:54:13 +00:00
rArgs = append(rArgs, av)
2022-01-12 17:48:14 +00:00
argIndex++
2022-01-11 18:54:13 +00:00
case reflect.Bool:
2022-01-12 17:48:14 +00:00
bv, err := strconv.ParseBool(args[argIndex])
2022-01-11 18:54:13 +00:00
if err != nil {
return err
}
av := reflect.ValueOf(bv)
rArgs = append(rArgs, av)
2022-01-12 17:48:14 +00:00
argIndex++
2022-01-11 18:54:13 +00:00
case reflect.Int64:
2022-01-12 17:48:14 +00:00
nf, err := strconv.ParseInt(args[argIndex], 10, 64)
2022-01-11 18:54:13 +00:00
if err != nil {
return err
}
av := reflect.ValueOf(nf)
rArgs = append(rArgs, av)
2022-01-12 17:48:14 +00:00
argIndex++
2022-01-11 18:54:13 +00:00
case reflect.Float64:
2022-01-12 17:48:14 +00:00
nf, err := strconv.ParseFloat(args[argIndex], 64)
2022-01-11 18:54:13 +00:00
if err != nil {
return err
}
av := reflect.ValueOf(nf)
rArgs = append(rArgs, av)
2022-01-12 17:48:14 +00:00
argIndex++
2022-01-11 18:54:13 +00:00
}
}
out := fv.Call(rArgs)
2022-01-12 18:06:29 +00:00
if ft.NumOut() == 0 {
return nil
}
// try to get the error object from the return value
for i := 0; i < ft.NumOut(); i++ {
outType := ft.Out(i)
2022-01-11 18:54:13 +00:00
switch outType.Kind() {
case reflect.Interface:
o := out[0].Interface()
switch ov := o.(type) {
case error:
return ov
}
}
}
return nil
}