diff --git a/airfile b/airfile new file mode 100644 index 0000000..aefa582 --- /dev/null +++ b/airfile @@ -0,0 +1,44 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./exe/test.exe" + cmd = "go build -o ./exe/ ./cmd/..." + delay = 0 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/cmd/test/main.go b/cmd/test/main.go index d3f1c04..65a482c 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -17,12 +17,12 @@ var ( ScreenChange("start") incDecKeyboard = tx.NewKeyboard("").Row( - tx.NewButton("+").ActionFunc(func(c *tx.A) { + tx.NewButton("+").ActionFunc(func(c *tx.Context) { d := c.V.(*UserData) d.Counter++ c.Sendf("%d", d.Counter) }), - tx.NewButton("-").ActionFunc(func(c *tx.A) { + tx.NewButton("-").ActionFunc(func(c *tx.Context) { d := c.V.(*UserData) d.Counter-- c.Sendf("%d", d.Counter) @@ -46,10 +46,11 @@ var ( Row( tx.NewButton("Send location"). WithSendLocation(true). - ActionFunc(func(c *tx.A) { + ActionFunc(func(c *tx.Context) { var err error - if c.U.Message.Location != nil { - l := c.U.Message.Location + u := c.Update + if u.Message.Location != nil { + l := u.Message.Location err = c.Sendf( "Longitude: %f\n"+ "Latitude: %f\n"+ @@ -77,7 +78,7 @@ var ( ) var beh = tx.NewBehaviour(). - WithInitFunc(func(c *tx.A) { + WithInitFunc(func(c *tx.Context) { // The session initialization. c.V = &UserData{} c.ChangeScreen("start") @@ -108,7 +109,7 @@ var beh = tx.NewBehaviour(). ). WithKeyboard(incDecKeyboard). // The function will be called when reaching the screen. - ActionFunc(func(c *tx.A) { + ActionFunc(func(c *tx.Context) { d := c.V.(*UserData) c.Sendf("Current counter value = %d", d.Counter) }), @@ -130,7 +131,7 @@ var beh = tx.NewBehaviour(). tx.NewKeyboard("").Row( tx.NewButton("Check"). WithData("check"). - ActionFunc(func(a *tx.A) { + ActionFunc(func(a *tx.Context) { d := a.V.(*UserData) a.Sendf("Counter = %d", d.Counter) }), @@ -139,12 +140,12 @@ var beh = tx.NewBehaviour(). ).WithCommands( tx.NewCommand("hello"). Desc("sends the 'Hello, World!' message back"). - ActionFunc(func(c *tx.A) { + ActionFunc(func(c *tx.Context) { c.Send("Hello, World!") }), tx.NewCommand("read"). Desc("reads a string and sends it back"). - ActionFunc(func(c *tx.A) { + ActionFunc(func(c *tx.Context) { c.Send("Type some text:") msg, err := c.ReadTextMessage() if err != nil { @@ -155,7 +156,7 @@ var beh = tx.NewBehaviour(). ) func mutateMessage(fn func(string) string) tx.ActionFunc { - return func(c *tx.A) { + return func(c *tx.Context) { for { msg, err := c.ReadTextMessage() if err == tx.NotAvailableErr { @@ -173,15 +174,15 @@ func mutateMessage(fn func(string) string) tx.ActionFunc { } var gBeh = tx.NewGroupBehaviour(). - InitFunc(func(a *tx.GA) { + InitFunc(func(c *tx.GC) { }). WithCommands( - tx.NewGroupCommand("hello").ActionFunc(func(a *tx.GA) { - a.Send("Hello, World!") + tx.NewGroupCommand("hello").ActionFunc(func(c *tx.GC) { + c.Send("Hello, World!") }), - tx.NewGroupCommand("mycounter").ActionFunc(func(a *tx.GA) { - d := a.GetSessionValue().(*UserData) - a.Sendf("Your counter value is %d", d.Counter) + tx.NewGroupCommand("mycounter").ActionFunc(func(c *tx.GC) { + d := c.GetSessionValue().(*UserData) + c.Sendf("Your counter value is %d", d.Counter) }), ) diff --git a/mkfile b/mkfile index 3bccf6b..4c32c16 100644 --- a/mkfile +++ b/mkfile @@ -1,5 +1,8 @@ all: build +run-air:V: + air -c airfile + build:V: go build -o exe/ ./cmd/... diff --git a/src/tx/action.go b/src/tx/action.go index 3d41543..7812ed3 100644 --- a/src/tx/action.go +++ b/src/tx/action.go @@ -22,106 +22,16 @@ func newAction(a Action) *action { } } -func (a *action) Act(arg *A) { +func (a *action) Act(c *Context) { if a.Action != nil { - a.Action.Act(arg) + a.Action.Act(c) } } -// Customized actions for the bot. -type Action interface { - Act(*Arg) -} - -// Customized actions for the -type GroupAction interface { - Act(*GroupArg) -} - -type ActionFunc func(*Arg) - -func (af ActionFunc) Act(a *Arg) { - af(a) -} - -type GroupActionFunc func(*GroupArg) - -func (af GroupActionFunc) Act(a *GroupArg) { - af(a) -} - -// The type implements changing screen to the underlying ScreenId -type ScreenChange ScreenId - -func (sc ScreenChange) Act(c *Arg) { - if !c.B.behaviour.ScreenExist(ScreenId(sc)) { - panic(ScreenNotExistErr) - } - err := c.ChangeScreen(ScreenId(sc)) - if err != nil { - panic(err) - } -} - -// The argument for handling. -type Arg struct { - // Current context. - *Context - // The update that made the action to be called. - U *Update -} -type A = Arg - -// Changes screen of user to the Id one. -func (c *Arg) ChangeScreen(screenId ScreenId) error { - if !c.B.behaviour.ScreenExist(screenId) { - return ScreenNotExistErr - } - - // Stop the reading by sending the nil, - // since we change the screen and - // current goroutine needs to be stopped. - if c.readingUpdate { - c.updates <- nil - } - - // Getting the screen and changing to - // then executing its action. - screen := c.B.behaviour.Screens[screenId] - c.prevScreen = c.curScreen - c.curScreen = screen - screen.Render(c.Context) - if screen.Action != nil { - c.run(screen.Action, c.U) - } - - return nil -} - -// The argument for handling in group behaviour. -type GroupArg struct { - *GroupContext - *Update -} -type GA = GroupArg - -func (a *GA) SentFromSid() SessionId { - return SessionId(a.SentFrom().ID) -} - -func (a *GA) GetSessionValue() any { - v, _ := a.B.GetSessionValueBySid(a.SentFromSid()) - return v -} - // The argument for handling in channenl behaviours. -type ChannelArg struct { +type ChannelContext struct { } -type CA = ChannelArg +type CC = ChannelContext type ChannelAction struct { - Act (*ChannelArg) -} - -type JsonTyper interface { - JsonType() string + Act (*ChannelContext) } diff --git a/src/tx/bot.go b/src/tx/bot.go index c92863d..6a5b568 100644 --- a/src/tx/bot.go +++ b/src/tx/bot.go @@ -128,13 +128,12 @@ func (bot *Bot) handlePrivate(updates chan *Update) { _, chnOk = chans[sid] // Making the bot ignore anything except "start" // before the session started - if u.Message.IsCommand() && - (!sessionOk) { + if u.Message.IsCommand() && !sessionOk { cmdName := CommandName(u.Message.Command()) if cmdName == "start" { session := bot.sessions[sid] - ctx := &Context{ - B: bot, + ctx := &context{ + Bot: bot, Session: session, updates: make(chan *Update), } @@ -166,8 +165,8 @@ func (bot *Bot) handleGroup(updates chan *Update) { if _, ok := bot.groupSessions[sid]; !ok { bot.groupSessions.Add(sid) session := bot.groupSessions[sid] - ctx := &GroupContext{ - B: bot, + ctx := &groupContext{ + Bot: bot, GroupSession: session, updates: make(chan *Update), } diff --git a/src/tx/context.go b/src/tx/context.go index d68769b..d3ef6b8 100644 --- a/src/tx/context.go +++ b/src/tx/context.go @@ -6,11 +6,9 @@ import ( apix "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) -// The type represents way to interact with user in -// handling functions. Is provided to Act() function always. -type Context struct { +type context struct { *Session - B *Bot + *Bot updates chan *Update // Is true if currently reading the Update. readingUpdate bool @@ -18,11 +16,13 @@ type Context struct { curScreen, prevScreen *Screen } +// The type represents way to interact with user in +// handling functions. Is provided to Act() function always. + // Goroutie function to handle each user. -func (c *Context) handleUpdateChan(updates chan *Update) { +func (c *context) handleUpdateChan(updates chan *Update) { var act Action - bot := c.B - beh := bot.behaviour + beh := c.behaviour if beh.Init != nil { c.run(beh.Init, nil) @@ -73,7 +73,7 @@ func (c *Context) handleUpdateChan(updates chan *Update) { cb := apix.NewCallback(u.CallbackQuery.ID, u.CallbackQuery.Data) data := u.CallbackQuery.Data - _, err := bot.Request(cb) + _, err := c.Request(cb) if err != nil { panic(err) } @@ -103,15 +103,15 @@ func (c *Context) handleUpdateChan(updates chan *Update) { } } -func (c *Context) run(a Action, u *Update) { - go a.Act(&A{ - Context: c, - U: u, +func (c *context) run(a Action, u *Update) { + go a.Act(&Context{ + context: c, + Update: u, }) } // Returns the next update ignoring current screen. -func (c *Context) ReadUpdate() (*Update, error) { +func (c *context) ReadUpdate() (*Update, error) { c.readingUpdate = true u := <-c.updates c.readingUpdate = false @@ -123,7 +123,7 @@ func (c *Context) ReadUpdate() (*Update, error) { } // Returns the next text message that the user sends. -func (c *Context) ReadTextMessage() (string, error) { +func (c *context) ReadTextMessage() (string, error) { u, err := c.ReadUpdate() if err != nil { return "", err @@ -136,34 +136,34 @@ func (c *Context) ReadTextMessage() (string, error) { } // Sends to the user specified text. -func (c *Context) Send(v ...any) error { +func (c *context) Send(v ...any) error { msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...)) - _, err := c.B.Send(msg) + _, err := c.Bot.Send(msg) 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...)) } // Context for interaction inside groups. -type GroupContext struct { +type groupContext struct { *GroupSession - B *Bot + *Bot updates chan *Update } -func (c *GroupContext) run(a GroupAction, u *Update) { - go a.Act(&GA{ - GroupContext: c, +func (c *groupContext) run(a GroupAction, u *Update) { + go a.Act(&GroupContext{ + groupContext: c, Update: u, }) } -func (c *GroupContext) handleUpdateChan(updates chan *Update) { +func (c *groupContext) handleUpdateChan(updates chan *Update) { var act GroupAction - beh := c.B.groupBehaviour + beh := c.groupBehaviour for u := range updates { if u.Message != nil { msg := u.Message @@ -172,7 +172,7 @@ func (c *GroupContext) handleUpdateChan(updates chan *Update) { // Skipping the commands sent not to us. atName := msg.CommandWithAt()[len(cmdName)+1:] - if c.B.Me.UserName != atName { + if c.Bot.Me.UserName != atName { continue } cmd, ok := beh.Commands[cmdName] @@ -189,13 +189,13 @@ func (c *GroupContext) handleUpdateChan(updates chan *Update) { } } -func (c *GroupContext) Sendf(format string, v ...any) error { +func (c *groupContext) Sendf(format string, v ...any) error { return c.Send(fmt.Sprintf(format, v...)) } // Sends into the chat specified values converted to strings. -func (c *GroupContext) Send(v ...any) error { +func (c *groupContext) Send(v ...any) error { msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...)) - _, err := c.B.Send(msg) + _, err := c.Bot.Send(msg) return err } diff --git a/src/tx/group.go b/src/tx/group.go new file mode 100644 index 0000000..6a2f15e --- /dev/null +++ b/src/tx/group.go @@ -0,0 +1,29 @@ +package tx + +// Customized actions for the group behaviour. +type GroupAction interface { + Act(*GroupContext) +} + +// The handler function type. +type GroupActionFunc func(*GroupContext) + +func (af GroupActionFunc) Act(a *GroupContext) { + af(a) +} + +type GC = GroupContext + +func (c *GroupContext) SentFromSid() SessionId { + return SessionId(c.SentFrom().ID) +} + +func (a *GroupContext) GetSessionValue() any { + v, _ := a.Bot.GetSessionValueBySid(a.SentFromSid()) + return v +} + +type GroupContext struct { + *groupContext + *Update +} diff --git a/src/tx/private.go b/src/tx/private.go new file mode 100644 index 0000000..70b4a1c --- /dev/null +++ b/src/tx/private.go @@ -0,0 +1,59 @@ +package tx + +// Interface to interact with the user. +type Context struct { + *context + *Update +} + +// Customized actions for the bot. +type Action interface { + Act(*Context) +} + +type ActionFunc func(*Context) + +func (af ActionFunc) Act(c *Context) { + af(c) +} + +// The type implements changing screen to the underlying ScreenId +type ScreenChange ScreenId + +func (sc ScreenChange) Act(c *Context) { + if !c.behaviour.ScreenExist(ScreenId(sc)) { + panic(ScreenNotExistErr) + } + err := c.ChangeScreen(ScreenId(sc)) + if err != nil { + panic(err) + } +} + +type C = Context + +// Changes screen of user to the Id one. +func (c *Context) ChangeScreen(screenId ScreenId) error { + if !c.behaviour.ScreenExist(screenId) { + return ScreenNotExistErr + } + + // Stop the reading by sending the nil, + // since we change the screen and + // current goroutine needs to be stopped. + if c.readingUpdate { + c.updates <- nil + } + + // Getting the screen and changing to + // then executing its action. + screen := c.behaviour.Screens[screenId] + c.prevScreen = c.curScreen + c.curScreen = screen + screen.Render(c.context) + if screen.Action != nil { + c.run(screen.Action, c.Update) + } + + return nil +} diff --git a/src/tx/screen.go b/src/tx/screen.go index 9fd96f5..909fd53 100644 --- a/src/tx/screen.go +++ b/src/tx/screen.go @@ -62,7 +62,7 @@ func (s *Screen) ActionFunc(a ActionFunc) *Screen { } // Renders output of the screen only to the side of the user. -func (s *Screen) Render(c *Context) error { +func (s *Screen) Render(c *context) error { id := c.Id.ToTelegram() kbd := s.Keyboard iKbd := s.InlineKeyboard @@ -87,13 +87,13 @@ func (s *Screen) Render(c *Context) error { msg.ReplyMarkup = iKbd.toTelegramInline() } else if kbd != nil { msg.ReplyMarkup = kbd.toTelegramReply() - if _, err := c.B.Send(msg); err != nil { + if _, err := c.Bot.Send(msg); err != nil { return err } return nil } else { msg.ReplyMarkup = apix.NewRemoveKeyboard(true) - if _, err := c.B.Send(msg); err != nil { + if _, err := c.Bot.Send(msg); err != nil { return err } return nil @@ -121,7 +121,7 @@ func (s *Screen) Render(c *Context) error { for _, m := range ch { if m != nil { - if _, err := c.B.Send(m); err != nil { + if _, err := c.Bot.Send(m); err != nil { return err } }