Implemented basic group behaviour.
This commit is contained in:
parent
e3045862c4
commit
c2562cc54c
8 changed files with 409 additions and 204 deletions
|
@ -150,13 +150,29 @@ func mutateMessage(fn func(string) string) tx.ActionFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gBeh = tx.NewGroupBehaviour().
|
||||||
|
InitFunc(func(a *tx.GA) {
|
||||||
|
}).
|
||||||
|
WithCommands(
|
||||||
|
tx.NewGroupCommand("hello").ActionFunc(func(a *tx.GA) {
|
||||||
|
a.Send("Hello, World!")
|
||||||
|
}),
|
||||||
|
tx.NewGroupCommand("mycounter").ActionFunc(func(a *tx.GA) {
|
||||||
|
d := a.GetSessionValue().(*UserData)
|
||||||
|
a.Sendf("Your counter value is %d", d.Counter)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
token := os.Getenv("BOT_TOKEN")
|
token := os.Getenv("BOT_TOKEN")
|
||||||
|
|
||||||
bot, err := tx.NewBot(token, beh, nil)
|
bot, err := tx.NewBot(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
bot = bot.
|
||||||
|
WithBehaviour(beh).
|
||||||
|
WithGroupBehaviour(gBeh)
|
||||||
|
|
||||||
bot.Debug = true
|
bot.Debug = true
|
||||||
|
|
||||||
|
|
110
src/tx/action.go
110
src/tx/action.go
|
@ -1,10 +1,41 @@
|
||||||
package tx
|
package tx
|
||||||
|
|
||||||
import (
|
//apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Update = apix.Update
|
type Action interface {
|
||||||
|
Act(*Arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupAction interface {
|
||||||
|
Act(*GroupArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customized actions for the bot.
|
||||||
|
|
||||||
|
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.
|
// The argument for handling.
|
||||||
type Arg struct {
|
type Arg struct {
|
||||||
|
@ -15,14 +46,57 @@ type Arg struct {
|
||||||
}
|
}
|
||||||
type A = Arg
|
type A = Arg
|
||||||
|
|
||||||
|
// Changes screen of user to the Id one.
|
||||||
|
func (c *Arg) ChangeScreen(screenId ScreenId) error {
|
||||||
|
// Return if it will not change anything.
|
||||||
|
if c.CurrentScreenId == screenId {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.B.behaviour.ScreenExist(screenId) {
|
||||||
|
return ScreenNotExistErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the reading by sending the nil.
|
||||||
|
if c.readingUpdate {
|
||||||
|
c.updates <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
screen := c.B.behaviour.Screens[screenId]
|
||||||
|
screen.Render(c.Context)
|
||||||
|
|
||||||
|
c.Session.ChangeScreen(screenId)
|
||||||
|
c.KeyboardId = screen.KeyboardId
|
||||||
|
|
||||||
|
if screen.Action != nil {
|
||||||
|
c.run(screen.Action, c.U)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The argument for handling in group behaviour.
|
||||||
type GroupArg struct {
|
type GroupArg struct {
|
||||||
GroupArg *GroupContext
|
*GroupContext
|
||||||
U *Update
|
*Update
|
||||||
}
|
}
|
||||||
type GA = GroupArg
|
type GA = GroupArg
|
||||||
|
|
||||||
type Action interface {
|
func (a *GA) SentFromSid() SessionId {
|
||||||
Act(*Arg)
|
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 CA = ChannelArg
|
||||||
|
type ChannelAction struct {
|
||||||
|
Act (*ChannelArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
type JsonTyper interface {
|
type JsonTyper interface {
|
||||||
|
@ -37,23 +111,3 @@ type JsonAction struct {
|
||||||
func (ja JsonAction) UnmarshalJSON(bts []byte, ptr any) error {
|
func (ja JsonAction) UnmarshalJSON(bts []byte, ptr any) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customized action for the bot.
|
|
||||||
type ActionFunc func(*Arg)
|
|
||||||
|
|
||||||
// The type implements changing screen to the underlying ScreenId
|
|
||||||
type ScreenChange ScreenId
|
|
||||||
|
|
||||||
func (sc ScreenChange) Act(c *Arg) {
|
|
||||||
if !c.B.ScreenExist(ScreenId(sc)) {
|
|
||||||
panic(ScreenNotExistErr)
|
|
||||||
}
|
|
||||||
err := c.ChangeScreen(ScreenId(sc))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (af ActionFunc) Act(c *Arg) {
|
|
||||||
af(c)
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,10 @@ package tx
|
||||||
// The package implements
|
// The package implements
|
||||||
// behaviour for the Telegram bots.
|
// behaviour for the Telegram bots.
|
||||||
|
|
||||||
|
// The type describes behaviour for the bot in channels.
|
||||||
|
type ChannelBehaviour struct {
|
||||||
|
}
|
||||||
|
|
||||||
// The type describes behaviour for the bot in personal chats.
|
// The type describes behaviour for the bot in personal chats.
|
||||||
type Behaviour struct {
|
type Behaviour struct {
|
||||||
Start Action
|
Start Action
|
||||||
|
@ -11,18 +15,6 @@ type Behaviour struct {
|
||||||
Commands CommandMap
|
Commands CommandMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type describes behaviour for the bot in group chats.
|
|
||||||
type GroupBehaviour struct {
|
|
||||||
// Will be called on adding the bot to the group.
|
|
||||||
//Add GroupAction
|
|
||||||
// List of commands
|
|
||||||
Commands CommandMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type describes behaviour for the bot in channels.
|
|
||||||
type ChannelBehaviour struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns new empty behaviour.
|
// Returns new empty behaviour.
|
||||||
func NewBehaviour() *Behaviour {
|
func NewBehaviour() *Behaviour {
|
||||||
return &Behaviour{
|
return &Behaviour{
|
||||||
|
@ -128,3 +120,41 @@ func (beh *Behaviour) GetScreen(id ScreenId) *Screen {
|
||||||
screen := beh.Screens[id]
|
screen := beh.Screens[id]
|
||||||
return screen
|
return screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The type describes behaviour for the bot in group chats.
|
||||||
|
type GroupBehaviour struct {
|
||||||
|
Init GroupAction
|
||||||
|
// List of commands
|
||||||
|
Commands GroupCommandMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGroupBehaviour() *GroupBehaviour {
|
||||||
|
return &GroupBehaviour{
|
||||||
|
Commands: make(GroupCommandMap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *GroupBehaviour) WithInitAction(a GroupAction) *GroupBehaviour {
|
||||||
|
b.Init = a
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *GroupBehaviour) InitFunc(fn GroupActionFunc) *GroupBehaviour {
|
||||||
|
return b.WithInitAction(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *GroupBehaviour) WithCommands(
|
||||||
|
cmds ...*GroupCommand,
|
||||||
|
) *GroupBehaviour {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
if cmd.Name == "" {
|
||||||
|
panic("empty command name")
|
||||||
|
}
|
||||||
|
_, ok := b.Commands[cmd.Name]
|
||||||
|
if ok {
|
||||||
|
panic("duplicate command definition")
|
||||||
|
}
|
||||||
|
b.Commands[cmd.Name] = cmd
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
211
src/tx/bot.go
211
src/tx/bot.go
|
@ -1,60 +1,113 @@
|
||||||
package tx
|
package tx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
//"fmt"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
apix "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Update = apix.Update
|
||||||
|
type Chat = apix.Chat
|
||||||
|
type User = apix.User
|
||||||
|
|
||||||
// The wrapper around Telegram API.
|
// The wrapper around Telegram API.
|
||||||
type Bot struct {
|
type Bot struct {
|
||||||
*apix.BotAPI
|
*apix.BotAPI
|
||||||
*Behaviour
|
Me *User
|
||||||
sessions SessionMap
|
// Private bot behaviour.
|
||||||
|
behaviour *Behaviour
|
||||||
|
// Group bot behaviour.
|
||||||
|
groupBehaviour *GroupBehaviour
|
||||||
|
// Bot behaviour in channels.
|
||||||
|
channelBehaviour *ChannelBehaviour
|
||||||
|
sessions SessionMap
|
||||||
|
groupSessions GroupSessionMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the new bot for running the Behaviour.
|
// Return the new bot with empty sessions and behaviour.
|
||||||
func NewBot(token string, beh *Behaviour, sessions SessionMap) (*Bot, error) {
|
func NewBot(token string) (*Bot, error) {
|
||||||
bot, err := apix.NewBotAPI(token)
|
bot, err := apix.NewBotAPI(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make new sessions if no current are provided.
|
|
||||||
if sessions == nil {
|
|
||||||
sessions = make(SessionMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Bot{
|
return &Bot{
|
||||||
BotAPI: bot,
|
BotAPI: bot,
|
||||||
Behaviour: beh,
|
|
||||||
sessions: make(SessionMap),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) GetSessionValueBySid(
|
||||||
|
sid SessionId,
|
||||||
|
) (any, bool) {
|
||||||
|
v, ok := bot.sessions[sid]
|
||||||
|
return v.V, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) GetGroupSessionValue(
|
||||||
|
sid SessionId,
|
||||||
|
) (any, bool) {
|
||||||
|
v, ok := bot.groupSessions[sid]
|
||||||
|
return v.V, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) WithBehaviour(beh *Behaviour) *Bot {
|
||||||
|
b.behaviour = beh
|
||||||
|
b.sessions = make(SessionMap)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) WithSessions(sessions SessionMap) *Bot {
|
||||||
|
b.sessions = sessions
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) WithGroupBehaviour(beh *GroupBehaviour) *Bot {
|
||||||
|
b.groupBehaviour = beh
|
||||||
|
b.groupSessions = make(GroupSessionMap)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) WithGroupSessions(sessions GroupSessionMap) *Bot {
|
||||||
|
b.groupSessions = sessions
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// Run the bot with the Behaviour.
|
// Run the bot with the Behaviour.
|
||||||
func (bot *Bot) Run() error {
|
func (bot *Bot) Run() error {
|
||||||
|
if bot.behaviour == nil &&
|
||||||
|
bot.groupBehaviour == nil {
|
||||||
|
return errors.New("no behaviour defined")
|
||||||
|
}
|
||||||
bot.Debug = true
|
bot.Debug = true
|
||||||
uc := apix.NewUpdate(0)
|
uc := apix.NewUpdate(0)
|
||||||
uc.Timeout = 60
|
uc.Timeout = 60
|
||||||
updates := bot.GetUpdatesChan(uc)
|
updates := bot.GetUpdatesChan(uc)
|
||||||
privateChans := make(map[SessionId]chan *Update)
|
handles := make(map[string]chan *Update)
|
||||||
groupChans := make(map[SessionId]chan *Update)
|
|
||||||
|
if bot.behaviour != nil {
|
||||||
|
chn := make(chan *Update)
|
||||||
|
handles["private"] = chn
|
||||||
|
go bot.handlePrivate(chn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bot.groupBehaviour != nil {
|
||||||
|
chn := make(chan *Update)
|
||||||
|
handles["group"] = chn
|
||||||
|
handles["supergroup"] = chn
|
||||||
|
go bot.handleGroup(chn)
|
||||||
|
}
|
||||||
|
|
||||||
|
me, _ := bot.GetMe()
|
||||||
|
bot.Me = &me
|
||||||
for u := range updates {
|
for u := range updates {
|
||||||
var chatType string
|
chn, ok := handles[u.FromChat().Type]
|
||||||
|
if !ok {
|
||||||
if u.Message != nil {
|
continue
|
||||||
chatType = u.Message.Chat.Type
|
|
||||||
} else if u.CallbackQuery != nil {
|
|
||||||
chatType = u.Message.Chat.Type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch chatType {
|
chn <- &u
|
||||||
case "private":
|
|
||||||
bot.handlePrivate(&u, privateChans)
|
|
||||||
case "group", "supergroup":
|
|
||||||
bot.handleGroup(&u, groupChans)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -62,55 +115,69 @@ func (bot *Bot) Run() error {
|
||||||
|
|
||||||
// The function handles updates supposed for the private
|
// The function handles updates supposed for the private
|
||||||
// chat with the bot.
|
// chat with the bot.
|
||||||
func (bot *Bot) handlePrivate(u *Update, chans map[SessionId]chan *Update) {
|
func (bot *Bot) handlePrivate(updates chan *Update) {
|
||||||
|
chans := make(map[SessionId]chan *Update)
|
||||||
var sid SessionId
|
var sid SessionId
|
||||||
if u.Message != nil {
|
for u := range updates {
|
||||||
msg := u.Message
|
if u.Message != nil {
|
||||||
|
// Create new session if the one does not exist
|
||||||
if bot.Debug {
|
// for this user.
|
||||||
fmt.Printf("is command: %q\n", u.Message.IsCommand())
|
sid = SessionId(u.Message.Chat.ID)
|
||||||
fmt.Printf("command itself: %q\n", msg.Command())
|
if _, ok := bot.sessions[sid]; !ok {
|
||||||
fmt.Printf("command arguments: %q\n", msg.CommandArguments())
|
bot.sessions.Add(sid)
|
||||||
fmt.Printf("is to me: %q\n", bot.IsMessageToMe(*msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new session if the one does not exist
|
|
||||||
// for this user.
|
|
||||||
sid = SessionId(u.Message.Chat.ID)
|
|
||||||
if _, ok := bot.sessions[sid]; !ok {
|
|
||||||
bot.sessions.Add(sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "start" command resets the bot
|
|
||||||
// by executing the Start Action.
|
|
||||||
if u.Message.IsCommand() {
|
|
||||||
cmdName := CommandName(u.Message.Command())
|
|
||||||
if cmdName == "start" {
|
|
||||||
// Getting current session and context.
|
|
||||||
session := bot.sessions[sid]
|
|
||||||
ctx := &Context{
|
|
||||||
B: bot,
|
|
||||||
Session: session,
|
|
||||||
updates: make(chan *Update),
|
|
||||||
}
|
|
||||||
|
|
||||||
chn := make(chan *Update)
|
|
||||||
chans[sid] = chn
|
|
||||||
// Starting the goroutine for the user.
|
|
||||||
go ctx.handleUpdateChan(chn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The "start" command resets the bot
|
||||||
|
// by executing the Start Action.
|
||||||
|
if u.Message.IsCommand() {
|
||||||
|
cmdName := CommandName(u.Message.Command())
|
||||||
|
if cmdName == "start" {
|
||||||
|
// Getting current session and context.
|
||||||
|
session := bot.sessions[sid]
|
||||||
|
ctx := &Context{
|
||||||
|
B: bot,
|
||||||
|
Session: session,
|
||||||
|
updates: make(chan *Update),
|
||||||
|
}
|
||||||
|
|
||||||
|
chn := make(chan *Update)
|
||||||
|
chans[sid] = chn
|
||||||
|
// Starting the goroutine for the user.
|
||||||
|
go ctx.handleUpdateChan(chn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if u.CallbackQuery != nil {
|
||||||
|
sid = SessionId(u.CallbackQuery.Message.Chat.ID)
|
||||||
|
}
|
||||||
|
chn, ok := chans[sid]
|
||||||
|
// The bot MUST get the "start" command.
|
||||||
|
// It will do nothing otherwise.
|
||||||
|
if ok {
|
||||||
|
chn <- u
|
||||||
}
|
}
|
||||||
} else if u.CallbackQuery != nil {
|
|
||||||
sid = SessionId(u.CallbackQuery.Message.Chat.ID)
|
|
||||||
}
|
}
|
||||||
chn, ok := chans[sid]
|
}
|
||||||
// The bot MUST get the "start" command.
|
|
||||||
// It will do nothing otherwise.
|
func (bot *Bot) handleGroup(updates chan *Update) {
|
||||||
if ok {
|
var sid SessionId
|
||||||
|
chans := make(map[SessionId]chan *Update)
|
||||||
|
for u := range updates {
|
||||||
|
sid = SessionId(u.FromChat().ID)
|
||||||
|
// If no session add new.
|
||||||
|
if _, ok := bot.groupSessions[sid]; !ok {
|
||||||
|
bot.groupSessions.Add(sid)
|
||||||
|
session := bot.groupSessions[sid]
|
||||||
|
ctx := &GroupContext{
|
||||||
|
B: bot,
|
||||||
|
GroupSession: session,
|
||||||
|
updates: make(chan *Update),
|
||||||
|
}
|
||||||
|
chn := make(chan *Update)
|
||||||
|
chans[sid] = chn
|
||||||
|
go ctx.handleUpdateChan(chn)
|
||||||
|
}
|
||||||
|
|
||||||
|
chn := chans[sid]
|
||||||
chn <- u
|
chn <- u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not implemented yet.
|
|
||||||
func (bot *Bot) handleGroup(u *Update, chans map[SessionId]chan *Update) {
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,28 +7,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message = apix.Message
|
type Message = apix.Message
|
||||||
|
|
||||||
type CommandName string
|
type CommandName string
|
||||||
|
|
||||||
type CommandContext struct {
|
|
||||||
// The field declares way to interact with the group chat in
|
|
||||||
// general.
|
|
||||||
*Context
|
|
||||||
Message *Message
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandMap map[CommandName]*Command
|
|
||||||
|
|
||||||
type CommandHandlerFunc func(*CommandContext)
|
|
||||||
type CommandHandler interface {
|
|
||||||
Run(*Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Name CommandName
|
Name CommandName
|
||||||
Description string
|
Description string
|
||||||
Action Action
|
Action Action
|
||||||
}
|
}
|
||||||
|
type CommandMap map[CommandName]*Command
|
||||||
|
|
||||||
func NewCommand(name CommandName) *Command {
|
func NewCommand(name CommandName) *Command {
|
||||||
return &Command{
|
return &Command{
|
||||||
|
@ -49,3 +35,30 @@ func (c *Command) Desc(desc string) *Command {
|
||||||
c.Description = desc
|
c.Description = desc
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GroupCommand struct {
|
||||||
|
Name CommandName
|
||||||
|
Description string
|
||||||
|
Action GroupAction
|
||||||
|
}
|
||||||
|
type GroupCommandMap map[CommandName]*GroupCommand
|
||||||
|
|
||||||
|
func NewGroupCommand(name CommandName) *GroupCommand {
|
||||||
|
return &GroupCommand{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *GroupCommand) WithAction(a GroupAction) *GroupCommand {
|
||||||
|
cmd.Action = a
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *GroupCommand) ActionFunc(fn GroupActionFunc) *GroupCommand {
|
||||||
|
return cmd.WithAction(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *GroupCommand) Desc(desc string) *GroupCommand {
|
||||||
|
cmd.Description = desc
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
|
@ -17,31 +17,26 @@ type Context struct {
|
||||||
readingUpdate bool
|
readingUpdate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context for interaction inside groups.
|
|
||||||
type GroupContext struct {
|
|
||||||
*GroupSession
|
|
||||||
B *Bot
|
|
||||||
}
|
|
||||||
|
|
||||||
// Goroutie function to handle each user.
|
// 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
|
bot := c.B
|
||||||
session := c.Session
|
session := c.Session
|
||||||
c.run(bot.Start, nil)
|
beh := bot.behaviour
|
||||||
|
c.run(beh.Start, nil)
|
||||||
for u := range updates {
|
for u := range updates {
|
||||||
screen := bot.Screens[session.CurrentScreenId]
|
screen := bot.behaviour.Screens[session.CurrentScreenId]
|
||||||
// The part is added to implement custom update handling.
|
// The part is added to implement custom update handling.
|
||||||
if u.Message != nil {
|
if u.Message != nil {
|
||||||
var act Action
|
|
||||||
if u.Message.IsCommand() && !c.readingUpdate {
|
if u.Message.IsCommand() && !c.readingUpdate {
|
||||||
cmdName := CommandName(u.Message.Command())
|
cmdName := CommandName(u.Message.Command())
|
||||||
cmd, ok := bot.Behaviour.Commands[cmdName]
|
cmd, ok := beh.Commands[cmdName]
|
||||||
if ok {
|
if ok {
|
||||||
act = cmd.Action
|
act = cmd.Action
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
kbd := bot.Keyboards[screen.KeyboardId]
|
kbd := beh.Keyboards[screen.KeyboardId]
|
||||||
btns := kbd.buttonMap()
|
btns := kbd.buttonMap()
|
||||||
text := u.Message.Text
|
text := u.Message.Text
|
||||||
btn, ok := btns[text]
|
btn, ok := btns[text]
|
||||||
|
@ -65,10 +60,6 @@ func (c *Context) handleUpdateChan(updates chan *Update) {
|
||||||
act = btn.Action
|
act = btn.Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if act != nil {
|
|
||||||
c.run(act, u)
|
|
||||||
}
|
|
||||||
} 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
|
||||||
|
@ -77,49 +68,26 @@ func (c *Context) handleUpdateChan(updates chan *Update) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
kbd := bot.Keyboards[screen.InlineKeyboardId]
|
kbd := beh.Keyboards[screen.InlineKeyboardId]
|
||||||
btns := kbd.buttonMap()
|
btns := kbd.buttonMap()
|
||||||
btn, ok := btns[data]
|
btn, ok := btns[data]
|
||||||
if !ok && c.readingUpdate {
|
if !ok && c.readingUpdate {
|
||||||
c.updates <- u
|
c.updates <- u
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.run(btn.Action, u)
|
act = btn.Action
|
||||||
|
}
|
||||||
|
if act != nil {
|
||||||
|
c.run(act, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) run(a Action, u *Update) {
|
func (c *Context) run(a Action, u *Update) {
|
||||||
go a.Act(&A{c, u})
|
go a.Act(&A{
|
||||||
}
|
Context: c,
|
||||||
|
U: u,
|
||||||
// Changes screen of user to the Id one.
|
})
|
||||||
func (c *Arg) ChangeScreen(screenId ScreenId) error {
|
|
||||||
// Return if it will not change anything.
|
|
||||||
if c.CurrentScreenId == screenId {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.B.ScreenExist(screenId) {
|
|
||||||
return ScreenNotExistErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the reading by sending the nil.
|
|
||||||
if c.readingUpdate {
|
|
||||||
c.updates <- nil
|
|
||||||
}
|
|
||||||
|
|
||||||
screen := c.B.Screens[screenId]
|
|
||||||
screen.Render(c.Context)
|
|
||||||
|
|
||||||
c.Session.ChangeScreen(screenId)
|
|
||||||
c.KeyboardId = screen.KeyboardId
|
|
||||||
|
|
||||||
if screen.Action != nil {
|
|
||||||
c.run(screen.Action, c.U)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the next update ignoring current screen.
|
// Returns the next update ignoring current screen.
|
||||||
|
@ -158,3 +126,56 @@ func (c *Context) Send(v ...any) error {
|
||||||
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...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context for interaction inside groups.
|
||||||
|
type GroupContext struct {
|
||||||
|
*GroupSession
|
||||||
|
B *Bot
|
||||||
|
updates chan *Update
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GroupContext) run(a GroupAction, u *Update) {
|
||||||
|
go a.Act(&GA{
|
||||||
|
GroupContext: c,
|
||||||
|
Update: u,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GroupContext) handleUpdateChan(updates chan *Update) {
|
||||||
|
var act GroupAction
|
||||||
|
beh := c.B.groupBehaviour
|
||||||
|
for u := range updates {
|
||||||
|
if u.Message != nil {
|
||||||
|
msg := u.Message
|
||||||
|
if msg.IsCommand() {
|
||||||
|
cmdName := CommandName(msg.Command())
|
||||||
|
|
||||||
|
// Skipping the commands sent not to us.
|
||||||
|
atName := msg.CommandWithAt()[len(cmdName)+1:]
|
||||||
|
if c.B.Me.UserName != atName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd, ok := beh.Commands[cmdName]
|
||||||
|
if !ok {
|
||||||
|
// Some lack of command handling
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
act = cmd.Action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if act != nil {
|
||||||
|
c.run(act, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
msg := apix.NewMessage(c.Id.ToTelegram(), fmt.Sprint(v...))
|
||||||
|
_, err := c.B.Send(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (s *Screen) Render(c *Context) error {
|
||||||
msg := apix.NewMessage(id, s.Text.String())
|
msg := apix.NewMessage(id, s.Text.String())
|
||||||
|
|
||||||
if s.InlineKeyboardId != "" {
|
if s.InlineKeyboardId != "" {
|
||||||
kbd, ok := c.B.Keyboards[s.InlineKeyboardId]
|
kbd, ok := c.B.behaviour.Keyboards[s.InlineKeyboardId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return KeyboardNotExistErr
|
return KeyboardNotExistErr
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func (s *Screen) Render(c *Context) error {
|
||||||
|
|
||||||
// Replace keyboard with the new one.
|
// Replace keyboard with the new one.
|
||||||
if s.KeyboardId != "" {
|
if s.KeyboardId != "" {
|
||||||
kbd, ok := c.B.Keyboards[s.KeyboardId]
|
kbd, ok := c.B.behaviour.Keyboards[s.KeyboardId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return KeyboardNotExistErr
|
return KeyboardNotExistErr
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ package tx
|
||||||
// In fact is simply ID of the chat.
|
// In fact is simply ID of the chat.
|
||||||
type SessionId int64
|
type SessionId int64
|
||||||
|
|
||||||
|
// Convert the SessionId to Telegram API's type.
|
||||||
|
func (si SessionId) ToTelegram() int64 {
|
||||||
|
return int64(si)
|
||||||
|
}
|
||||||
|
|
||||||
// The type represents current state of
|
// The type represents current state of
|
||||||
// user interaction per each of them.
|
// user interaction per each of them.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -17,20 +22,6 @@ type Session struct {
|
||||||
V any
|
V any
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type represents map of sessions using
|
|
||||||
// as key.
|
|
||||||
type SessionMap map[SessionId]*Session
|
|
||||||
|
|
||||||
// Session information for a group.
|
|
||||||
type GroupSession struct {
|
|
||||||
Id SessionId
|
|
||||||
// Information for each user in the group.
|
|
||||||
V map[SessionId]any
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map for every user in every chat sessions.
|
|
||||||
type GroupSessionMap map[SessionId]*GroupSession
|
|
||||||
|
|
||||||
// Return new empty session with specified user ID.
|
// Return new empty session with specified user ID.
|
||||||
func NewSession(id SessionId) *Session {
|
func NewSession(id SessionId) *Session {
|
||||||
return &Session{
|
return &Session{
|
||||||
|
@ -39,6 +30,28 @@ func NewSession(id SessionId) *Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changes screen of user to the Id one for the session.
|
||||||
|
func (c *Session) ChangeScreen(screenId ScreenId) {
|
||||||
|
c.PreviousScreenId = c.CurrentScreenId
|
||||||
|
c.CurrentScreenId = screenId
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type represents map of sessions using
|
||||||
|
// as key.
|
||||||
|
type SessionMap map[SessionId]*Session
|
||||||
|
|
||||||
|
// Add new empty session by it's ID.
|
||||||
|
func (sm SessionMap) Add(sid SessionId) {
|
||||||
|
sm[sid] = NewSession(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session information for a group.
|
||||||
|
type GroupSession struct {
|
||||||
|
Id SessionId
|
||||||
|
// Information for each user in the group.
|
||||||
|
V map[SessionId]any
|
||||||
|
}
|
||||||
|
|
||||||
// Returns new empty group session with specified group and user IDs.
|
// Returns new empty group session with specified group and user IDs.
|
||||||
func NewGroupSession(id SessionId) *GroupSession {
|
func NewGroupSession(id SessionId) *GroupSession {
|
||||||
return &GroupSession{
|
return &GroupSession{
|
||||||
|
@ -47,18 +60,9 @@ func NewGroupSession(id SessionId) *GroupSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changes screen of user to the Id one for the session.
|
// Map for every group the bot is in.
|
||||||
func (c *Session) ChangeScreen(screenId ScreenId) {
|
type GroupSessionMap map[SessionId]*GroupSession
|
||||||
c.PreviousScreenId = c.CurrentScreenId
|
|
||||||
c.CurrentScreenId = screenId
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the SessionId to Telegram API's type.
|
func (sm GroupSessionMap) Add(sid SessionId) {
|
||||||
func (si SessionId) ToTelegram() int64 {
|
sm[sid] = NewGroupSession(sid)
|
||||||
return int64(si)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new empty session by it's ID.
|
|
||||||
func (sm SessionMap) Add(sid SessionId) {
|
|
||||||
sm[sid] = NewSession(sid)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue