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