Skip to content

Commit

Permalink
implement the basic flow of interact
Browse files Browse the repository at this point in the history
  • Loading branch information
c9s committed Jan 13, 2022
1 parent ba4c694 commit 7eba6b2
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 74 deletions.
140 changes: 140 additions & 0 deletions examples/interact/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package main

import (
"context"
"fmt"
"os"
"strconv"
"strings"
"syscall"
"time"

log "github.com/sirupsen/logrus"
tb "gopkg.in/tucnak/telebot.v2"

"github.com/c9s/bbgo/pkg/cmd/cmdutil"
"github.com/c9s/bbgo/pkg/interact"
)

func parseFloatPercent(s string, bitSize int) (f float64, err error) {
i := strings.Index(s, "%")
if i < 0 {
return strconv.ParseFloat(s, bitSize)
}

f, err = strconv.ParseFloat(s[:i], bitSize)
if err != nil {
return 0, err
}
return f / 100.0, nil
}

type closePositionTask struct {
symbol string
percentage float64
confirmed bool
}

type PositionInteraction struct {
closePositionTask closePositionTask
}

func (m *PositionInteraction) Commands(i *interact.Interact) {
i.Command("/closePosition", func(reply interact.Reply) error {
// send symbol options
reply.Message("Choose your position")
for _, symbol := range []string{"BTCUSDT", "ETHUSDT"} {
reply.AddButton(symbol)
}

return nil
}).Next(func(reply interact.Reply, symbol string) error {
// get symbol from user
if len(symbol) == 0 {
reply.Message("Please enter a symbol")
return fmt.Errorf("empty symbol")
}
switch symbol {
case "BTCUSDT", "ETHUSDT":

default:
reply.Message("Invalid symbol")
return fmt.Errorf("invalid symbol")

}

m.closePositionTask.symbol = symbol

reply.Message("Choose or enter the percentage to close")
for _, symbol := range []string{"25%", "50%", "100%"} {
reply.AddButton(symbol)
}

// send percentage options
return nil
}).Next(func(reply interact.Reply, percentageStr string) error {
p, err := parseFloatPercent(percentageStr, 64)
if err != nil {
reply.Message("Not a valid percentage string")
return err
}

// get percentage from user
m.closePositionTask.percentage = p

// send confirmation
reply.Message("Are you sure to close the position?")
reply.AddButton("Yes")
return nil
}).Next(func(reply interact.Reply, confirm string) error {
switch strings.ToLower(confirm) {
case "yes":
m.closePositionTask.confirmed = true
reply.Message(fmt.Sprintf("Your %s position is closed", m.closePositionTask.symbol))
reply.RemoveKeyboard()

default:

}
// call position close

// reply result
return nil
})
}

func main() {
b, 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: os.Getenv("TELEGRAM_BOT_TOKEN"),
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
// Synchronous: false,
// Verbose: true,
// ParseMode: "",
// Reporter: nil,
// Client: nil,
// Offline: false,
})

if err != nil {
log.Fatal(err)
return
}

ctx := context.Background()

globalInteraction := interact.New()
globalInteraction.SetMessenger(&interact.Telegram{
Interact: globalInteraction,
Bot: b,
})

globalInteraction.AddCustomInteraction(&PositionInteraction{})
if err := globalInteraction.Start(ctx); err != nil {
log.Fatal(err)
}
cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM)
}
13 changes: 7 additions & 6 deletions examples/telebot/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package main

import (
"fmt"
"log"
"os"
"time"

"github.com/google/uuid"
log "github.com/sirupsen/logrus"
tb "gopkg.in/tucnak/telebot.v2"
)

Expand All @@ -33,7 +32,7 @@ func main() {
Token: os.Getenv("TELEGRAM_BOT_TOKEN"),
Poller: &tb.LongPoller{Timeout: 10 * time.Second},
// Synchronous: false,
Verbose: true,
Verbose: false,
// ParseMode: "",
// Reporter: nil,
// Client: nil,
Expand All @@ -55,6 +54,8 @@ func main() {

// On reply button pressed (message)
b.Handle(&btnHelp, func(m *tb.Message) {
log.Infof("btnHelp: %#v", m)

var (
// Inline buttons.
//
Expand Down Expand Up @@ -86,18 +87,18 @@ func main() {
})

b.Handle("/hello", func(m *tb.Message) {
fmt.Printf("message: %#v\n", m)
log.Infof("/hello %#v", m)
// b.Send(m.Sender, "Hello World!")
})

b.Handle(tb.OnText, func(m *tb.Message) {
fmt.Printf("text: %#v\n", m)
log.Infof("[onText] %#v", m)
// all the text messages that weren't
// captured by existing handlers
})

b.Handle(tb.OnQuery, func(q *tb.Query) {
fmt.Printf("query: %#v\n", q)
log.Infof("[onQuery] %#v", q)

// r := &tb.ReplyMarkup{}
// r.URL("test", "https://media.tenor.com/images/f176705ae1bb3c457e19d8cd71718ac0/tenor.gif")
Expand Down
70 changes: 70 additions & 0 deletions pkg/interact/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package interact

import "strconv"

// Command is a domain specific language syntax helper
// It's used for helping developer define the state and transition function
type Command struct {
// Name is the command name
Name string

// StateF is the command handler function
F interface{}

stateID int
states map[State]State
statesFunc map[State]interface{}
initState, lastState State
}

func NewCommand(name string, f interface{}) *Command {
c := &Command{
Name: name,
F: f,
states: make(map[State]State),
statesFunc: make(map[State]interface{}),
initState: State(name + "_" + strconv.Itoa(0)),
}
return c.Next(f)
}

// Transit defines the state transition that is not related to the last defined state.
func (c *Command) Transit(state1, state2 State, f interface{}) *Command {
c.states[state1] = state2
c.statesFunc[state1] = f
return c
}

func (c *Command) NamedNext(n string, f interface{}) *Command {
var curState State
if c.lastState == "" {
curState = State(c.Name + "_" + strconv.Itoa(c.stateID))
} else {
curState = c.lastState
}

nextState := State(n)
c.states[curState] = nextState
c.statesFunc[curState] = f
c.lastState = nextState
return c
}

// Next defines the next state with the transition function from the last defined state.
func (c *Command) Next(f interface{}) *Command {
var curState State
if c.lastState == "" {
curState = State(c.Name + "_" + strconv.Itoa(c.stateID))
} else {
curState = c.lastState
}

// generate the next state by the stateID
c.stateID++
nextState := State(c.Name + "_" + strconv.Itoa(c.stateID))

c.states[curState] = nextState
c.statesFunc[curState] = f
c.lastState = nextState
return c
}
Loading

0 comments on commit 7eba6b2

Please sign in to comment.