Implemented synchronous reading from the client letting us read messages.
This commit is contained in:
parent
fbfa0ec388
commit
cd965fd811
6 changed files with 165 additions and 47 deletions
|
@ -3,18 +3,24 @@ package main
|
|||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mojosa-software/got/src/tx"
|
||||
)
|
||||
|
||||
var navKeyboard = tx.NewKeyboard("nav").Row(
|
||||
tx.NewButton().WithText("Inc/Dec").ScreenChange("inc/dec"),
|
||||
).Row(
|
||||
tx.NewButton().WithText("Upper case").ScreenChange("upper-case"),
|
||||
tx.NewButton().WithText("Lower case").ScreenChange("lower-case"),
|
||||
)
|
||||
var startScreenButton = tx.NewButton().
|
||||
WithText("🏠 To the start screen").
|
||||
ScreenChange("start")
|
||||
|
||||
var incKeyboard = tx.NewKeyboard("inc/dec").Row(
|
||||
var beh = tx.NewBehaviour().
|
||||
// The function will be called every time
|
||||
// the bot is started.
|
||||
OnStartFunc(func(c *tx.Context) {
|
||||
c.V["counter"] = new(int)
|
||||
c.ChangeScreen("start")
|
||||
}).WithKeyboards(
|
||||
// Increment/decrement keyboard.
|
||||
tx.NewKeyboard("inc/dec").Row(
|
||||
tx.NewButton().WithText("+").ActionFunc(func(c *tx.Context) {
|
||||
counter := c.V["counter"].(*int)
|
||||
*counter++
|
||||
|
@ -25,29 +31,48 @@ var incKeyboard = tx.NewKeyboard("inc/dec").Row(
|
|||
*counter--
|
||||
c.Sendf("%d", *counter)
|
||||
}),
|
||||
)
|
||||
|
||||
var startScreen = tx.NewScreen("start").
|
||||
WithText("The bot started!").
|
||||
Keyboard("nav")
|
||||
|
||||
var incScreen = tx.NewScreen("inc/dec").
|
||||
WithText("The screen shows how user separated data works").
|
||||
IKeyboard("inc/dec").
|
||||
Keyboard("nav")
|
||||
|
||||
var beh = tx.NewBehaviour().
|
||||
OnStartFunc(func(c *tx.Context) {
|
||||
// The function will be called every time
|
||||
// the bot is started.
|
||||
c.V["counter"] = new(int)
|
||||
c.ChangeScreen("start")
|
||||
}).WithKeyboards(
|
||||
navKeyboard,
|
||||
incKeyboard,
|
||||
).Row(
|
||||
startScreenButton,
|
||||
),
|
||||
// The navigational keyboard.
|
||||
tx.NewKeyboard("nav").Row(
|
||||
tx.NewButton().WithText("Inc/Dec").ScreenChange("inc/dec"),
|
||||
).Row(
|
||||
tx.NewButton().WithText("Upper case").ScreenChange("upper-case"),
|
||||
tx.NewButton().WithText("Lower case").ScreenChange("lower-case"),
|
||||
),
|
||||
// The keyboard to return to the start screen.
|
||||
tx.NewKeyboard("nav-start").Row(
|
||||
startScreenButton,
|
||||
),
|
||||
).WithScreens(
|
||||
startScreen,
|
||||
incScreen,
|
||||
tx.NewScreen("start").
|
||||
WithText("The bot started!").
|
||||
Keyboard("nav"),
|
||||
tx.NewScreen("inc/dec").
|
||||
WithText(
|
||||
"The screen shows how"+
|
||||
"user separated data works"+
|
||||
"by saving the counter for each of them",
|
||||
).
|
||||
Keyboard("inc/dec").
|
||||
// The function will be called when reaching the screen.
|
||||
ActionFunc(func(c *tx.Context) {
|
||||
counter := c.V["counter"].(*int)
|
||||
c.Sendf("Current counter value equals %d", *counter)
|
||||
}),
|
||||
tx.NewScreen("upper-case").
|
||||
WithText("Type text and the bot will send you the upper case version to you").
|
||||
Keyboard("nav-start").
|
||||
ActionFunc(func(c *tx.Context) {
|
||||
for {
|
||||
s, err := c.ReadTextMessage()
|
||||
if err == tx.NotAvailableErr {
|
||||
break
|
||||
}
|
||||
c.Sendf("%s", strings.ToUpper(s))
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -61,6 +61,7 @@ func (bot *Bot) Run() error {
|
|||
ctx := &Context{
|
||||
B: bot,
|
||||
Session: session,
|
||||
updates: make(chan *Update),
|
||||
}
|
||||
|
||||
chn := make(chan *Update)
|
||||
|
|
|
@ -11,16 +11,18 @@ import (
|
|||
type Context struct {
|
||||
*Session
|
||||
B *Bot
|
||||
updates chan *Update
|
||||
available bool
|
||||
}
|
||||
|
||||
// Goroutie function to handle each user.
|
||||
func (ctx *Context) handleUpdateChan(updates chan *Update) {
|
||||
bot := ctx.B
|
||||
session := ctx.Session
|
||||
bot.Start.Act(ctx)
|
||||
func (c *Context) handleUpdateChan(updates chan *Update) {
|
||||
bot := c.B
|
||||
session := c.Session
|
||||
bot.Start.Act(c)
|
||||
for u := range updates {
|
||||
screen := bot.Screens[session.CurrentScreenId]
|
||||
|
||||
// The part is added to implement custom update handling.
|
||||
if u.Message != nil {
|
||||
|
||||
kbd := bot.Keyboards[screen.KeyboardId]
|
||||
|
@ -28,12 +30,26 @@ func (ctx *Context) handleUpdateChan(updates chan *Update) {
|
|||
text := u.Message.Text
|
||||
btn, ok := btns[text]
|
||||
|
||||
// Skipping wrong text messages.
|
||||
if !ok {
|
||||
/*if ok {
|
||||
c.available = false
|
||||
btn.Action.Act(c)
|
||||
c.available = true
|
||||
continue
|
||||
}*/
|
||||
|
||||
// Sending wrong messages to
|
||||
// the currently reading goroutine.
|
||||
if !ok && c.ReadingUpdate {
|
||||
c.updates <- u
|
||||
continue
|
||||
}
|
||||
|
||||
btn.Action.Act(ctx)
|
||||
if !ok {
|
||||
}
|
||||
|
||||
if ok {
|
||||
c.run(btn.Action)
|
||||
}
|
||||
} else if u.CallbackQuery != nil {
|
||||
cb := apix.NewCallback(u.CallbackQuery.ID, u.CallbackQuery.Data)
|
||||
data := u.CallbackQuery.Data
|
||||
|
@ -45,11 +61,20 @@ func (ctx *Context) handleUpdateChan(updates chan *Update) {
|
|||
kbd := bot.Keyboards[screen.InlineKeyboardId]
|
||||
btns := kbd.buttonMap()
|
||||
btn := btns[data]
|
||||
btn.Action.Act(ctx)
|
||||
btn.Action.Act(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) run(a Action) {
|
||||
c.available = true
|
||||
go a.Act(c)
|
||||
}
|
||||
|
||||
func (c *Context) Available() bool {
|
||||
return c.available
|
||||
}
|
||||
|
||||
// Changes screen of user to the Id one.
|
||||
func (c *Context) ChangeScreen(screenId ScreenId) error {
|
||||
// Return if it will not change anything.
|
||||
|
@ -67,16 +92,55 @@ func (c *Context) ChangeScreen(screenId ScreenId) error {
|
|||
c.Session.ChangeScreen(screenId)
|
||||
c.KeyboardId = screen.KeyboardId
|
||||
|
||||
c.available = false
|
||||
if screen.Action != nil {
|
||||
c.run(screen.Action)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the next update ignoring current screen.
|
||||
func (c *Context) ReadUpdate() (*Update, error) {
|
||||
var (
|
||||
u *Update
|
||||
)
|
||||
c.ReadingUpdate = true
|
||||
for {
|
||||
select {
|
||||
case u = <-c.updates:
|
||||
c.ReadingUpdate = false
|
||||
return u, nil
|
||||
default:
|
||||
if !c.available {
|
||||
return nil, NotAvailableErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Returns the next text message that the user sends.
|
||||
func (c *Context) ReadTextMessage() (string, error) {
|
||||
u, err := c.ReadUpdate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Message == nil {
|
||||
return "", WrongUpdateType{}
|
||||
}
|
||||
|
||||
return u.Message.Text, nil
|
||||
}
|
||||
|
||||
// Sends to the user specified text.
|
||||
func (c *Context) Send(text string) error {
|
||||
msg := apix.NewMessage(c.Id.ToTelegram(), text)
|
||||
func (c *Context) Send(v ...any) error {
|
||||
msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...))
|
||||
_, err := c.B.Send(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
// Sends the formatted with fmt.Sprintf message to the user.
|
||||
func (c *Context) Sendf(format string, v ...any) error {
|
||||
return c.Send(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
|
|
@ -2,10 +2,23 @@ package tx
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type WrongUpdateType struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
var (
|
||||
ScreenNotExistErr = errors.New("screen does not exist")
|
||||
SessionNotExistErr = errors.New("session does not exist")
|
||||
KeyboardNotExistErr = errors.New("keyboard does not exist")
|
||||
NotAvailableErr = errors.New("the context is not available")
|
||||
)
|
||||
|
||||
func (wut WrongUpdateType) Error() string {
|
||||
if wut.Type == "" {
|
||||
return "wrong update type"
|
||||
}
|
||||
return fmt.Sprintf("wrong update type '%s'", wut.Type)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ type Screen struct {
|
|||
|
||||
// Keyboard to be displayed on the screen.
|
||||
KeyboardId KeyboardId
|
||||
|
||||
// Action called on the reaching the screen.
|
||||
Action Action
|
||||
}
|
||||
|
||||
// Map structure for the screens.
|
||||
|
@ -52,6 +55,15 @@ func (s *Screen) Keyboard(kbdId KeyboardId) *Screen {
|
|||
return s
|
||||
}
|
||||
|
||||
func (s *Screen) WithAction(a Action) *Screen {
|
||||
s.Action = a
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Screen) ActionFunc(a ActionFunc) *Screen {
|
||||
return s.WithAction(a)
|
||||
}
|
||||
|
||||
// Rendering the screen text to string to be sent or printed.
|
||||
func (st ScreenText) String() string {
|
||||
return string(st)
|
||||
|
|
|
@ -19,9 +19,12 @@ type Session struct {
|
|||
CurrentScreenId ScreenId
|
||||
// ID of the previous screen.
|
||||
PreviousScreenId ScreenId
|
||||
// The currently showed on display keyboard.
|
||||
// The currently showed on display keyboard inside Action.
|
||||
KeyboardId KeyboardId
|
||||
|
||||
// Is true if currently reading the Update.
|
||||
ReadingUpdate bool
|
||||
|
||||
// Custom data for each user.
|
||||
V map[string]any
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue