diff --git a/cmd/test/main.go b/cmd/test/main.go index e82b650..40fdd01 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -12,27 +12,26 @@ type UserData struct { Counter int } -var startScreenButton = tx.NewButton(). - WithText("🏠 To the start screen"). +var startScreenButton = tx.NewButton("🏠 To the start screen"). ScreenChange("start") var beh = tx.NewBehaviour(). // The function will be called every time // the bot is started. - OnStartFunc(func(c *tx.Context) { + OnStartFunc(func(c *tx.A) { c.V = &UserData{} c.ChangeScreen("start") }).WithKeyboards( // Increment/decrement keyboard. tx.NewKeyboard("inc/dec").Row( - tx.NewButton().WithText("+").ActionFunc(func(c *tx.Context) { + tx.NewButton("+").ActionFunc(func(c *tx.A) { d := c.V.(*UserData) d.Counter++ c.Sendf("%d", d.Counter) }), - tx.NewButton().WithText("-").ActionFunc(func(c *tx.Context) { + tx.NewButton("-").ActionFunc(func(c *tx.A) { d := c.V.(*UserData) d.Counter-- c.Sendf("%d", d.Counter) @@ -43,14 +42,37 @@ var beh = tx.NewBehaviour(). // The navigational keyboard. tx.NewKeyboard("nav").Row( - tx.NewButton().WithText("Inc/Dec").ScreenChange("inc/dec"), + tx.NewButton("Inc/Dec").ScreenChange("inc/dec"), ).Row( - tx.NewButton().WithText("Upper case").ScreenChange("upper-case"), - tx.NewButton().WithText("Lower case").ScreenChange("lower-case"), + tx.NewButton("Upper case").ScreenChange("upper-case"), + tx.NewButton("Lower case").ScreenChange("lower-case"), + ).Row( + tx.NewButton("Send location"). + WithSendLocation(true). + ActionFunc(func(c *tx.A) { + var err error + if c.U.Message.Location != nil { + l := c.U.Message.Location + err = c.Sendf( + "Longitude: %f\n"+ + "Latitude: %f\n"+ + "Heading: %d"+ + "", + l.Longitude, + l.Latitude, + l.Heading, + ) + } else { + err = c.Send("Somehow wrong location was sent") + } + if err != nil { + c.Send(err) + } + }), ), tx.NewKeyboard("istart").Row( - tx.NewButton().WithText("GoT Github page"). + tx.NewButton("GoT Github page"). WithUrl("https://github.com/mojosa-software/got"), ), @@ -78,7 +100,7 @@ var beh = tx.NewBehaviour(). ). Keyboard("inc/dec"). // The function will be called when reaching the screen. - ActionFunc(func(c *tx.Context) { + ActionFunc(func(c *tx.A) { d := c.V.(*UserData) c.Sendf("Current counter value = %d", d.Counter) }), @@ -95,12 +117,12 @@ var beh = tx.NewBehaviour(). ).WithCommands( tx.NewCommand("hello"). Desc("sends the 'Hello, World!' message back"). - ActionFunc(func(c *tx.Context) { + ActionFunc(func(c *tx.A) { c.Send("Hello, World!") }), tx.NewCommand("read"). Desc("reads a string and sends it back"). - ActionFunc(func(c *tx.Context) { + ActionFunc(func(c *tx.A) { c.Send("Type some text:") msg, err := c.ReadTextMessage() if err != nil { @@ -111,7 +133,7 @@ var beh = tx.NewBehaviour(). ) func mutateMessage(fn func(string) string) tx.ActionFunc { - return func(c *tx.Context) { + return func(c *tx.A) { for { msg, err := c.ReadTextMessage() if err == tx.NotAvailableErr { diff --git a/src/tx/action.go b/src/tx/action.go index f0acfa9..ae1b8bf 100644 --- a/src/tx/action.go +++ b/src/tx/action.go @@ -1,18 +1,37 @@ package tx -// Implementing the intereface lets you -// provide behaviour for the buttons etc. +import ( + apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" +) + +type Update = apix.Update + +// The argument for handling. +type Arg struct { + // Current context. + *Context + // The update that made the action to be called. + U *Update +} +type A = Arg + +type GroupArg struct { + GroupArg *GroupContext + U *Update +} +type GA = GroupArg + type Action interface { - Act(*Context) + Act(*Arg) } // Customized action for the bot. -type ActionFunc func(*Context) +type ActionFunc func(*Arg) // The type implements changing screen to the underlying ScreenId type ScreenChange ScreenId -func (sc ScreenChange) Act(c *Context) { +func (sc ScreenChange) Act(c *Arg) { if !c.B.ScreenExist(ScreenId(sc)) { panic(ScreenNotExistErr) } @@ -22,6 +41,6 @@ func (sc ScreenChange) Act(c *Context) { } } -func (af ActionFunc) Act(c *Context) { +func (af ActionFunc) Act(c *Arg) { af(c) } diff --git a/src/tx/button.go b/src/tx/button.go index 1fae13e..113715c 100644 --- a/src/tx/button.go +++ b/src/tx/button.go @@ -6,10 +6,11 @@ import ( // The type wraps Telegram API's button to provide Action functionality. type Button struct { - Text string - Data string - Url string - Action Action + Text string + Data string + Url string + SendLocation bool + Action Action } type ButtonMap map[string]*Button @@ -17,26 +18,32 @@ type ButtonMap map[string]*Button // Represents the reply button row. type ButtonRow []*Button -// Returns new button with specified text and action. -func NewButton() *Button { - return &Button{} -} - -func (btn *Button) WithText(text string) *Button { - btn.Text = text - return btn +// Returns new button with the specified text and no action. +func NewButton(text string) *Button { + return &Button{ + Text: text, + } } +// Set the URL for the button. Only for inline buttons. func (btn *Button) WithUrl(url string) *Button { btn.Url = url return btn } +// Set the action when pressing the button. +// By default is nil and does nothing. func (btn *Button) WithAction(a Action) *Button { btn.Action = a return btn } +// Sets whether the button must send owner's location. +func (btn *Button) WithSendLocation(ok bool) *Button { + btn.SendLocation = ok + return btn +} + func (btn *Button) ActionFunc(fn ActionFunc) *Button { return btn.WithAction(fn) } @@ -46,7 +53,11 @@ func (btn *Button) ScreenChange(sc ScreenChange) *Button { } func (btn *Button) ToTelegram() apix.KeyboardButton { - return apix.NewKeyboardButton(btn.Text) + ret := apix.NewKeyboardButton(btn.Text) + if btn.SendLocation { + ret.RequestLocation = true + } + return ret } func (btn *Button) ToTelegramInline() apix.InlineKeyboardButton { diff --git a/src/tx/context.go b/src/tx/context.go index 04bce78..9c607c4 100644 --- a/src/tx/context.go +++ b/src/tx/context.go @@ -27,7 +27,7 @@ type GroupContext struct { func (c *Context) handleUpdateChan(updates chan *Update) { bot := c.B session := c.Session - bot.Start.Act(c) + c.run(bot.Start, nil) for u := range updates { screen := bot.Screens[session.CurrentScreenId] // The part is added to implement custom update handling. @@ -45,11 +45,20 @@ func (c *Context) handleUpdateChan(updates chan *Update) { btns := kbd.buttonMap() text := u.Message.Text btn, ok := btns[text] - // Sending wrong messages to - // the currently reading goroutine. - if !ok && c.readingUpdate { - c.updates <- u - continue + if !ok { + if u.Message.Location != nil { + for _, b := range btns { + if b.SendLocation { + btn = b + ok = true + } + } + } else if c.readingUpdate { + // Skipping the update sending it to + // the reading goroutine. + c.updates <- u + continue + } } if ok { @@ -58,7 +67,7 @@ func (c *Context) handleUpdateChan(updates chan *Update) { } if act != nil { - c.run(act) + c.run(act, u) } } else if u.CallbackQuery != nil { cb := apix.NewCallback(u.CallbackQuery.ID, u.CallbackQuery.Data) @@ -70,18 +79,22 @@ func (c *Context) handleUpdateChan(updates chan *Update) { } kbd := bot.Keyboards[screen.InlineKeyboardId] btns := kbd.buttonMap() - btn := btns[data] - btn.Action.Act(c) + btn, ok := btns[data] + if !ok && c.readingUpdate { + c.updates <- u + continue + } + c.run(btn.Action, u) } } } -func (c *Context) run(a Action) { - go a.Act(c) +func (c *Context) run(a Action, u *Update) { + go a.Act(&A{c, u}) } // Changes screen of user to the Id one. -func (c *Context) ChangeScreen(screenId ScreenId) error { +func (c *Arg) ChangeScreen(screenId ScreenId) error { // Return if it will not change anything. if c.CurrentScreenId == screenId { return nil @@ -97,13 +110,13 @@ func (c *Context) ChangeScreen(screenId ScreenId) error { } screen := c.B.Screens[screenId] - screen.Render(c) + screen.Render(c.Context) c.Session.ChangeScreen(screenId) c.KeyboardId = screen.KeyboardId if screen.Action != nil { - c.run(screen.Action) + c.run(screen.Action, c.U) } return nil diff --git a/src/tx/session.go b/src/tx/session.go index f15fff3..f4f9a19 100644 --- a/src/tx/session.go +++ b/src/tx/session.go @@ -1,11 +1,5 @@ package tx -import ( - apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" -) - -type Update = apix.Update - // Represents unique value to identify chats. // In fact is simply ID of the chat. type SessionId int64