mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
slack: add reply and session struct
This commit is contained in:
parent
2cf29bd1ec
commit
f5f8f15670
|
@ -45,7 +45,7 @@ func (m *PositionInteraction) Commands(i *interact.Interact) {
|
||||||
// send symbol options
|
// send symbol options
|
||||||
reply.Message("Choose your position")
|
reply.Message("Choose your position")
|
||||||
for _, symbol := range []string{"BTCUSDT", "ETHUSDT"} {
|
for _, symbol := range []string{"BTCUSDT", "ETHUSDT"} {
|
||||||
reply.AddButton(symbol)
|
reply.AddButton(symbol, symbol, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -68,7 +68,7 @@ func (m *PositionInteraction) Commands(i *interact.Interact) {
|
||||||
|
|
||||||
reply.Message("Choose or enter the percentage to close")
|
reply.Message("Choose or enter the percentage to close")
|
||||||
for _, symbol := range []string{"25%", "50%", "100%"} {
|
for _, symbol := range []string{"25%", "50%", "100%"} {
|
||||||
reply.AddButton(symbol)
|
reply.AddButton(symbol, symbol, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// send percentage options
|
// send percentage options
|
||||||
|
@ -85,7 +85,7 @@ func (m *PositionInteraction) Commands(i *interact.Interact) {
|
||||||
|
|
||||||
// send confirmation
|
// send confirmation
|
||||||
reply.Message("Are you sure to close the position?")
|
reply.Message("Are you sure to close the position?")
|
||||||
reply.AddButton("Yes")
|
reply.AddButton("Yes", "confirm", "yes")
|
||||||
return nil
|
return nil
|
||||||
}).Next(func(reply interact.Reply, confirm string) error {
|
}).Next(func(reply interact.Reply, confirm string) error {
|
||||||
switch strings.ToLower(confirm) {
|
switch strings.ToLower(confirm) {
|
||||||
|
|
|
@ -44,6 +44,12 @@ func NewCoreInteraction(environment *Environment, trader *Trader) *CoreInteracti
|
||||||
|
|
||||||
func (it *CoreInteraction) Commands(i *interact.Interact) {
|
func (it *CoreInteraction) Commands(i *interact.Interact) {
|
||||||
i.PrivateCommand("/sessions", "List Exchange Sessions", func(reply interact.Reply) error {
|
i.PrivateCommand("/sessions", "List Exchange Sessions", func(reply interact.Reply) error {
|
||||||
|
switch r := reply.(type) {
|
||||||
|
case *interact.SlackReply:
|
||||||
|
// call slack specific api to build the reply object
|
||||||
|
_ = r
|
||||||
|
}
|
||||||
|
|
||||||
message := "Your connected sessions:\n"
|
message := "Your connected sessions:\n"
|
||||||
for name, session := range it.environment.Sessions() {
|
for name, session := range it.environment.Sessions() {
|
||||||
message += "- " + name + " (" + session.ExchangeName.String() + ")\n"
|
message += "- " + name + " (" + session.ExchangeName.String() + ")\n"
|
||||||
|
@ -56,7 +62,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
|
||||||
i.PrivateCommand("/balances", "Show balances", func(reply interact.Reply) error {
|
i.PrivateCommand("/balances", "Show balances", func(reply interact.Reply) error {
|
||||||
reply.Message("Please select an exchange session")
|
reply.Message("Please select an exchange session")
|
||||||
for name := range it.environment.Sessions() {
|
for name := range it.environment.Sessions() {
|
||||||
reply.AddButton(name)
|
reply.AddButton(name, "session", name)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}).Next(func(sessionName string, reply interact.Reply) error {
|
}).Next(func(sessionName string, reply interact.Reply) error {
|
||||||
|
@ -86,7 +92,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
|
||||||
found := false
|
found := false
|
||||||
for signature, strategy := range it.exchangeStrategies {
|
for signature, strategy := range it.exchangeStrategies {
|
||||||
if _, ok := strategy.(PositionReader); ok {
|
if _, ok := strategy.(PositionReader); ok {
|
||||||
reply.AddButton(signature)
|
reply.AddButton(signature, "strategy", signature)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +137,7 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
|
||||||
found := false
|
found := false
|
||||||
for signature, strategy := range it.exchangeStrategies {
|
for signature, strategy := range it.exchangeStrategies {
|
||||||
if _, ok := strategy.(PositionCloser); ok {
|
if _, ok := strategy.(PositionCloser); ok {
|
||||||
reply.AddButton(signature)
|
reply.AddButton(signature, strategy, signature)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,8 +179,8 @@ func (it *CoreInteraction) Commands(i *interact.Interact) {
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Message("Choose or enter the percentage to close")
|
reply.Message("Choose or enter the percentage to close")
|
||||||
for _, symbol := range []string{"5%", "25%", "50%", "80%", "100%"} {
|
for _, p := range []string{"5%", "25%", "50%", "80%", "100%"} {
|
||||||
reply.AddButton(symbol)
|
reply.AddButton(p, "percentage", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,8 +1,33 @@
|
||||||
package interact
|
package interact
|
||||||
|
|
||||||
|
type Button struct {
|
||||||
|
Text string
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
type Reply interface {
|
type Reply interface {
|
||||||
|
// Send sends the message directly to the client's session
|
||||||
Send(message string)
|
Send(message string)
|
||||||
|
|
||||||
|
// Message sets the message to the reply
|
||||||
Message(message string)
|
Message(message string)
|
||||||
AddButton(text string)
|
|
||||||
|
// AddButton adds the button to the reply
|
||||||
|
AddButton(text string, name, value string)
|
||||||
|
|
||||||
|
// RemoveKeyboard hides the keyboard from the client user interface
|
||||||
RemoveKeyboard()
|
RemoveKeyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ButtonReply can be used if your reply needs button user interface.
|
||||||
|
type ButtonReply interface {
|
||||||
|
// AddButton adds the button to the reply
|
||||||
|
AddButton(text string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialogReply can be used if your reply needs Dialog user interface
|
||||||
|
type DialogReply interface {
|
||||||
|
// AddButton adds the button to the reply
|
||||||
|
Dialog(title, text string, buttons []string)
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@ package interact
|
||||||
// Responder defines the logic of responding the message
|
// Responder defines the logic of responding the message
|
||||||
type Responder func(session Session, message string, reply Reply, ctxObjects ...interface{}) error
|
type Responder func(session Session, message string, reply Reply, ctxObjects ...interface{}) error
|
||||||
|
|
||||||
|
type CallbackResponder interface {
|
||||||
|
SetCallbackResponder(responder Responder)
|
||||||
|
}
|
||||||
|
|
||||||
type TextMessageResponder interface {
|
type TextMessageResponder interface {
|
||||||
SetTextMessageResponder(responder Responder)
|
SetTextMessageResponder(responder Responder)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,108 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
"github.com/slack-go/slack/slackevents"
|
"github.com/slack-go/slack/slackevents"
|
||||||
"github.com/slack-go/slack/socketmode"
|
"github.com/slack-go/slack/socketmode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type SlackReply struct {
|
||||||
|
// uuid is the unique id of this question
|
||||||
|
// can be used as the callback id
|
||||||
|
uuid string
|
||||||
|
|
||||||
|
session *SlackSession
|
||||||
|
|
||||||
|
client *slack.Client
|
||||||
|
|
||||||
|
message string
|
||||||
|
|
||||||
|
accessories []*slack.Accessory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reply *SlackReply) Send(message string) {
|
||||||
|
cID, tsID, err := reply.client.PostMessage(
|
||||||
|
reply.session.ChannelID,
|
||||||
|
slack.MsgOptionText(message, false),
|
||||||
|
slack.MsgOptionAsUser(false), // Add this if you want that the bot would post message as a user, otherwise it will send response using the default slackbot
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Errorf("slack post message error: channel=%s thread=%s", cID, tsID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reply *SlackReply) Message(message string) {
|
||||||
|
reply.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveKeyboard is not supported by Slack
|
||||||
|
func (reply *SlackReply) RemoveKeyboard() {}
|
||||||
|
|
||||||
|
func (reply *SlackReply) AddButton(text string, name string, value string) {
|
||||||
|
actionID := reply.uuid + ":" + value
|
||||||
|
reply.accessories = append(reply.accessories, slack.NewAccessory(
|
||||||
|
slack.NewButtonBlockElement(
|
||||||
|
// action id should be unique
|
||||||
|
actionID,
|
||||||
|
value,
|
||||||
|
&slack.TextBlockObject{
|
||||||
|
Type: slack.PlainTextType,
|
||||||
|
Text: text,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reply *SlackReply) build() map[string]interface{} {
|
||||||
|
var blocks []slack.Block
|
||||||
|
|
||||||
|
blocks = append(blocks, slack.NewSectionBlock(
|
||||||
|
&slack.TextBlockObject{
|
||||||
|
Type: slack.MarkdownType,
|
||||||
|
Text: reply.message,
|
||||||
|
},
|
||||||
|
nil, // fields
|
||||||
|
nil, // accessory
|
||||||
|
nil, // options
|
||||||
|
))
|
||||||
|
|
||||||
|
blocks = append(blocks, slack.NewActionBlock("",
|
||||||
|
slack.NewButtonBlockElement(
|
||||||
|
"actionID",
|
||||||
|
"value",
|
||||||
|
slack.NewTextBlockObject(
|
||||||
|
slack.PlainTextType, "text", true, false),
|
||||||
|
)))
|
||||||
|
|
||||||
|
var payload = map[string]interface{}{
|
||||||
|
"blocks": blocks,
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
type SlackSession struct {
|
type SlackSession struct {
|
||||||
BaseSession
|
BaseSession
|
||||||
|
|
||||||
|
ChannelID string
|
||||||
|
UserID string
|
||||||
|
|
||||||
|
// questions is used to store the questions that we added in the reply
|
||||||
|
// the key is the client generated callback id
|
||||||
|
questions map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSlackSession() *SlackSession {
|
||||||
|
return &SlackSession{
|
||||||
|
questions: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackSession) ID() string {
|
||||||
|
return fmt.Sprintf("%s-%s", s.UserID, s.ChannelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlackSessionMap map[int64]*SlackSession
|
type SlackSessionMap map[int64]*SlackSession
|
||||||
|
@ -31,6 +125,8 @@ type Slack struct {
|
||||||
textMessageResponder Responder
|
textMessageResponder Responder
|
||||||
|
|
||||||
authorizedCallbacks []func(userSession *SlackSession)
|
authorizedCallbacks []func(userSession *SlackSession)
|
||||||
|
|
||||||
|
eventsApiCallbacks []func(slackevents.EventsAPIEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSlack(client *slack.Client) *Slack {
|
func NewSlack(client *slack.Client) *Slack {
|
||||||
|
@ -68,15 +164,15 @@ func (s *Slack) listen() {
|
||||||
case socketmode.EventTypeEventsAPI:
|
case socketmode.EventTypeEventsAPI:
|
||||||
eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent)
|
eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("Ignored %+v\n", evt)
|
logrus.Debugf("ignored %+v", evt)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Event received: %+v\n", eventsAPIEvent)
|
logrus.Debugf("event received: %+v", eventsAPIEvent)
|
||||||
|
|
||||||
s.socket.Ack(*evt.Request)
|
s.socket.Ack(*evt.Request)
|
||||||
|
|
||||||
|
s.EmitEventsApi(eventsAPIEvent)
|
||||||
|
|
||||||
switch eventsAPIEvent.Type {
|
switch eventsAPIEvent.Type {
|
||||||
case slackevents.CallbackEvent:
|
case slackevents.CallbackEvent:
|
||||||
innerEvent := eventsAPIEvent.InnerEvent
|
innerEvent := eventsAPIEvent.InnerEvent
|
||||||
|
@ -95,20 +191,19 @@ func (s *Slack) listen() {
|
||||||
case socketmode.EventTypeInteractive:
|
case socketmode.EventTypeInteractive:
|
||||||
callback, ok := evt.Data.(slack.InteractionCallback)
|
callback, ok := evt.Data.(slack.InteractionCallback)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("Ignored %+v\n", evt)
|
logrus.Debugf("ignored %+v", evt)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Interaction received: %+v\n", callback)
|
logrus.Debugf("interaction received: %+v", callback)
|
||||||
|
|
||||||
var payload interface{}
|
var payload interface{}
|
||||||
|
|
||||||
switch callback.Type {
|
switch callback.Type {
|
||||||
case slack.InteractionTypeBlockActions:
|
case slack.InteractionTypeBlockActions:
|
||||||
// See https://api.slack.com/apis/connections/socket-implement#button
|
// See https://api.slack.com/apis/connections/socket-implement#button
|
||||||
|
logrus.Debugf("button clicked!")
|
||||||
|
|
||||||
s.socket.Debugf("button clicked!")
|
|
||||||
case slack.InteractionTypeShortcut:
|
case slack.InteractionTypeShortcut:
|
||||||
case slack.InteractionTypeViewSubmission:
|
case slack.InteractionTypeViewSubmission:
|
||||||
// See https://api.slack.com/apis/connections/socket-implement#modal
|
// See https://api.slack.com/apis/connections/socket-implement#modal
|
||||||
|
@ -118,47 +213,44 @@ func (s *Slack) listen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.socket.Ack(*evt.Request, payload)
|
s.socket.Ack(*evt.Request, payload)
|
||||||
|
|
||||||
case socketmode.EventTypeSlashCommand:
|
case socketmode.EventTypeSlashCommand:
|
||||||
cmd, ok := evt.Data.(slack.SlashCommand)
|
cmd, ok := evt.Data.(slack.SlashCommand)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Printf("Ignored %+v\n", evt)
|
logrus.Debugf("ignored %+v", evt)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.socket.Debugf("Slash command received: %+v", cmd)
|
logrus.Debugf("slash command received: %+v", cmd)
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
session := s.newSession(evt)
|
||||||
"blocks": []slack.Block{
|
reply := s.newReply(session)
|
||||||
slack.NewSectionBlock(
|
if err := s.textMessageResponder(session, "", reply); err != nil {
|
||||||
&slack.TextBlockObject{
|
continue
|
||||||
Type: slack.MarkdownType,
|
}
|
||||||
Text: "foo",
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
slack.NewAccessory(
|
|
||||||
slack.NewButtonBlockElement(
|
|
||||||
"",
|
|
||||||
"somevalue",
|
|
||||||
&slack.TextBlockObject{
|
|
||||||
Type: slack.PlainTextType,
|
|
||||||
Text: "bar",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
|
|
||||||
|
payload := reply.build()
|
||||||
s.socket.Ack(*evt.Request, payload)
|
s.socket.Ack(*evt.Request, payload)
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Unexpected event type received: %s\n", evt.Type)
|
logrus.Debugf("unexpected event type received: %s", evt.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Slack) newSession(evt socketmode.Event) *SlackSession {
|
||||||
|
return NewSlackSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) newReply(session *SlackSession) *SlackReply {
|
||||||
|
return &SlackReply{
|
||||||
|
uuid: uuid.New().String(),
|
||||||
|
session: session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Slack) Start(ctx context.Context) {
|
func (s *Slack) Start(ctx context.Context) {
|
||||||
go s.listen()
|
go s.listen()
|
||||||
if err := s.socket.Run() ; err != nil {
|
if err := s.socket.Run(); err != nil {
|
||||||
logrus.WithError(err).Errorf("slack socketmode error")
|
logrus.WithError(err).Errorf("slack socketmode error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (r *TelegramReply) RemoveKeyboard() {
|
||||||
r.set = true
|
r.set = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TelegramReply) AddButton(text string) {
|
func (r *TelegramReply) AddButton(text string, name string, value string) {
|
||||||
var button = r.menu.Text(text)
|
var button = r.menu.Text(text)
|
||||||
if len(r.buttons) == 0 {
|
if len(r.buttons) == 0 {
|
||||||
r.buttons = append(r.buttons, []telebot.Btn{})
|
r.buttons = append(r.buttons, []telebot.Btn{})
|
||||||
|
@ -101,6 +101,8 @@ type Telegram struct {
|
||||||
// textMessageResponder is used for interact to register its message handler
|
// textMessageResponder is used for interact to register its message handler
|
||||||
textMessageResponder Responder
|
textMessageResponder Responder
|
||||||
|
|
||||||
|
callbackResponder CallbackResponder
|
||||||
|
|
||||||
commands []*Command
|
commands []*Command
|
||||||
|
|
||||||
authorizedCallbacks []func(s *TelegramSession)
|
authorizedCallbacks []func(s *TelegramSession)
|
||||||
|
@ -114,8 +116,12 @@ func NewTelegram(bot *telebot.Bot) *Telegram {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *Telegram) SetTextMessageResponder(textMessageResponder Responder) {
|
func (tm *Telegram) SetCallbackResponder(responder CallbackResponder) {
|
||||||
tm.textMessageResponder = textMessageResponder
|
tm.callbackResponder = responder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *Telegram) SetTextMessageResponder(responder Responder) {
|
||||||
|
tm.textMessageResponder = responder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *Telegram) Start(context.Context) {
|
func (tm *Telegram) Start(context.Context) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user