Making the widgets more modulable. Needs to be finished.
This commit is contained in:
parent
5e1faf0c44
commit
57f85fdacc
15 changed files with 464 additions and 270 deletions
|
@ -4,29 +4,33 @@ tmp_dir = "tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = []
|
args_bin = []
|
||||||
bin = "./exe/test.exe"
|
bin = "exe/test.exe"
|
||||||
cmd = "go build -o ./exe/ ./cmd/..."
|
cmd = "go build -o ./exe/ ./cmd/test"
|
||||||
delay = 0
|
delay = 0
|
||||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
|
||||||
|
exclude_dir = ["app/volume", "assets", "tmp", "vendor", "testdata"]
|
||||||
exclude_file = []
|
exclude_file = []
|
||||||
exclude_regex = ["_test.go"]
|
exclude_regex = ["_test.go", ".exe"]
|
||||||
|
|
||||||
exclude_unchanged = false
|
exclude_unchanged = false
|
||||||
follow_symlink = false
|
follow_symlink = false
|
||||||
full_bin = ""
|
full_bin = ""
|
||||||
|
|
||||||
include_dir = []
|
include_dir = []
|
||||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
include_file = []
|
include_file = []
|
||||||
|
|
||||||
kill_delay = "0s"
|
kill_delay = "0s"
|
||||||
log = "build-errors.log"
|
log = "build-errors.log"
|
||||||
poll = false
|
poll = true
|
||||||
poll_interval = 0
|
poll_interval = 0
|
||||||
rerun = false
|
rerun = false
|
||||||
rerun_delay = 500
|
rerun_delay = 500
|
||||||
send_interrupt = false
|
send_interrupt = true
|
||||||
stop_on_error = true
|
stop_on_error = true
|
||||||
|
|
||||||
[color]
|
[color]
|
||||||
app = ""
|
app = "red"
|
||||||
build = "yellow"
|
build = "yellow"
|
||||||
main = "magenta"
|
main = "magenta"
|
||||||
runner = "green"
|
runner = "green"
|
|
@ -26,18 +26,21 @@ func NewMutateMessageWidget(fn func(string) string) *MutateMessageWidget {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *MutateMessageWidget) Serve(c *tg.Context, updates chan *tg.Update) error {
|
func (w *MutateMessageWidget) Serve(c *tg.Context, updates *tg.UpdateChan) {
|
||||||
for _, arg := range c.Args {
|
for _, arg := range c.Args {
|
||||||
c.Sendf("%v", arg)
|
c.Sendf("%v", arg)
|
||||||
}
|
}
|
||||||
for u := range updates {
|
for u := range updates.Chan() {
|
||||||
if u.Message == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
text := u.Message.Text
|
text := u.Message.Text
|
||||||
c.Sendf("%s", w.Mutate(text))
|
c.Sendf("%s", w.Mutate(text))
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
func (w *MutateMessageWidget) Filter(u *tg.Update, _ tg.MessageMap) bool {
|
||||||
|
if u.Message == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractSessionData(c *tg.Context) *SessionData {
|
func ExtractSessionData(c *tg.Context) *SessionData {
|
||||||
|
@ -116,14 +119,14 @@ var beh = tg.NewBehaviour().
|
||||||
c.Session.Data = &SessionData{}
|
c.Session.Data = &SessionData{}
|
||||||
}).WithScreens(
|
}).WithScreens(
|
||||||
tg.NewScreen("start", tg.NewPage(
|
tg.NewScreen("start", tg.NewPage(
|
||||||
"The bot started!",
|
"",
|
||||||
).WithInline(
|
).WithInline(
|
||||||
tg.NewInline().Row(
|
tg.NewInline().Row(
|
||||||
tg.NewButton("GoT Github page").
|
tg.NewButton("GoT Github page").
|
||||||
WithUrl("https://github.com/mojosa-software/got"),
|
WithUrl("https://github.com/mojosa-software/got"),
|
||||||
),
|
).Widget(""),
|
||||||
).WithReply(
|
).WithReply(
|
||||||
navKeyboard,
|
navKeyboard.Widget("The bot started!"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tg.NewScreen("start/inc-dec", tg.NewPage(
|
tg.NewScreen("start/inc-dec", tg.NewPage(
|
||||||
|
@ -132,7 +135,7 @@ var beh = tg.NewBehaviour().
|
||||||
"by saving the counter for each of users "+
|
"by saving the counter for each of users "+
|
||||||
"separately. ",
|
"separately. ",
|
||||||
).WithReply(
|
).WithReply(
|
||||||
incDecKeyboard,
|
incDecKeyboard.Widget("Press the buttons to increment and decrement"),
|
||||||
).ActionFunc(func(c *tg.Context) {
|
).ActionFunc(func(c *tg.Context) {
|
||||||
// The function will be calleb before serving page.
|
// The function will be calleb before serving page.
|
||||||
d := ExtractSessionData(c)
|
d := ExtractSessionData(c)
|
||||||
|
@ -143,7 +146,7 @@ var beh = tg.NewBehaviour().
|
||||||
tg.NewScreen("start/upper-case", tg.NewPage(
|
tg.NewScreen("start/upper-case", tg.NewPage(
|
||||||
"Type text and the bot will send you the upper case version to you",
|
"Type text and the bot will send you the upper case version to you",
|
||||||
).WithReply(
|
).WithReply(
|
||||||
navToStartKeyboard,
|
navToStartKeyboard.Widget(""),
|
||||||
).WithSub(
|
).WithSub(
|
||||||
NewMutateMessageWidget(strings.ToUpper),
|
NewMutateMessageWidget(strings.ToUpper),
|
||||||
),
|
),
|
||||||
|
@ -152,7 +155,7 @@ var beh = tg.NewBehaviour().
|
||||||
tg.NewScreen("start/lower-case", tg.NewPage(
|
tg.NewScreen("start/lower-case", tg.NewPage(
|
||||||
"Type text and the bot will send you the lower case version",
|
"Type text and the bot will send you the lower case version",
|
||||||
).WithReply(
|
).WithReply(
|
||||||
navToStartKeyboard,
|
navToStartKeyboard.Widget(""),
|
||||||
).WithSub(
|
).WithSub(
|
||||||
NewMutateMessageWidget(strings.ToLower),
|
NewMutateMessageWidget(strings.ToLower),
|
||||||
),
|
),
|
||||||
|
@ -161,7 +164,7 @@ var beh = tg.NewBehaviour().
|
||||||
tg.NewScreen("start/send-location", tg.NewPage(
|
tg.NewScreen("start/send-location", tg.NewPage(
|
||||||
"Send your location and I will tell where you are!",
|
"Send your location and I will tell where you are!",
|
||||||
).WithReply(
|
).WithReply(
|
||||||
sendLocationKeyboard,
|
sendLocationKeyboard.Widget(""),
|
||||||
).WithInline(
|
).WithInline(
|
||||||
tg.NewInline().Row(
|
tg.NewInline().Row(
|
||||||
tg.NewButton(
|
tg.NewButton(
|
||||||
|
@ -172,7 +175,7 @@ var beh = tg.NewBehaviour().
|
||||||
d := ExtractSessionData(c)
|
d := ExtractSessionData(c)
|
||||||
c.Sendf("Counter = %d", d.Counter)
|
c.Sendf("Counter = %d", d.Counter)
|
||||||
}),
|
}),
|
||||||
),
|
).Widget("Press the button to display your counter"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).WithCommands(
|
).WithCommands(
|
||||||
|
@ -189,9 +192,9 @@ var beh = tg.NewBehaviour().
|
||||||
}),
|
}),
|
||||||
tg.NewCommand("read").
|
tg.NewCommand("read").
|
||||||
Desc("reads a string and sends it back").
|
Desc("reads a string and sends it back").
|
||||||
WidgetFunc(func(c *tg.Context, updates chan *tg.Update) error {
|
WidgetFunc(func(c *tg.Context, updates *tg.UpdateChan) {
|
||||||
c.Sendf("Type text and I will send it back to you")
|
c.Sendf("Type text and I will send it back to you")
|
||||||
for u := range updates {
|
for u := range updates.Chan() {
|
||||||
if u.Message == nil {
|
if u.Message == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -199,7 +202,6 @@ var beh = tg.NewBehaviour().
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
c.Sendf("Done")
|
c.Sendf("Done")
|
||||||
return nil
|
|
||||||
}),
|
}),
|
||||||
tg.NewCommand("image").
|
tg.NewCommand("image").
|
||||||
Desc("sends a sample image").
|
Desc("sends a sample image").
|
||||||
|
|
40
tg/bot.go
40
tg/bot.go
|
@ -50,9 +50,9 @@ func (bot *Bot) Debug(debug bool) *Bot {
|
||||||
func (bot *Bot) Send(
|
func (bot *Bot) Send(
|
||||||
sid SessionId, v Sendable,
|
sid SessionId, v Sendable,
|
||||||
) (*Message, error) {
|
) (*Message, error) {
|
||||||
config, err := v.SendConfig(sid, bot)
|
config := v.SendConfig(sid, bot)
|
||||||
if err != nil {
|
if config.Error != nil {
|
||||||
return nil, err
|
return nil, config.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := bot.Api.Send(config.ToApi())
|
msg, err := bot.Api.Send(config.ToApi())
|
||||||
|
@ -64,18 +64,22 @@ func (bot *Bot) Send(
|
||||||
|
|
||||||
func (bot *Bot) Render(
|
func (bot *Bot) Render(
|
||||||
sid SessionId, r Renderable,
|
sid SessionId, r Renderable,
|
||||||
) ([]*Message, error) {
|
) (MessageMap, error) {
|
||||||
configs, err := r.Render(sid, bot)
|
configs := r.Render(sid, bot)
|
||||||
if err != nil {
|
if configs == nil {
|
||||||
return []*Message{}, err
|
return nil, MapCollisionErr
|
||||||
}
|
}
|
||||||
messages := []*Message{}
|
messages := make(MessageMap)
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
|
_, collision := messages[config.Name]
|
||||||
|
if collision {
|
||||||
|
return messages, MapCollisionErr
|
||||||
|
}
|
||||||
msg, err := bot.Api.Send(config.ToApi())
|
msg, err := bot.Api.Send(config.ToApi())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return messages, err
|
return messages, err
|
||||||
}
|
}
|
||||||
messages = append(messages, &msg)
|
messages[config.Name] = &msg
|
||||||
}
|
}
|
||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
@ -204,7 +208,7 @@ 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(updates chan *Update) {
|
func (bot *Bot) handlePrivate(updates chan *Update) {
|
||||||
chans := make(map[SessionId]chan *Update)
|
chans := make(map[SessionId] *UpdateChan )
|
||||||
var sid SessionId
|
var sid SessionId
|
||||||
for u := range updates {
|
for u := range updates {
|
||||||
sid = SessionId(u.FromChat().ID)
|
sid = SessionId(u.FromChat().ID)
|
||||||
|
@ -224,9 +228,12 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
|
||||||
Bot: bot,
|
Bot: bot,
|
||||||
Session: session,
|
Session: session,
|
||||||
}
|
}
|
||||||
chn := make(chan *Update)
|
chn := NewUpdateChan()
|
||||||
chans[sid] = chn
|
chans[sid] = chn
|
||||||
go ctx.handleUpdateChan(chn)
|
go (&Context{
|
||||||
|
context: ctx,
|
||||||
|
Update: u,
|
||||||
|
}).Serve(chn)
|
||||||
}
|
}
|
||||||
} else if u.Message != nil {
|
} else if u.Message != nil {
|
||||||
// Create session on any message
|
// Create session on any message
|
||||||
|
@ -237,16 +244,19 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
|
||||||
Bot: bot,
|
Bot: bot,
|
||||||
Session: lsession,
|
Session: lsession,
|
||||||
}
|
}
|
||||||
chn := make(chan *Update)
|
chn := NewUpdateChan()
|
||||||
chans[sid] = chn
|
chans[sid] = chn
|
||||||
go ctx.handleUpdateChan(chn)
|
go (&Context{
|
||||||
|
context: ctx,
|
||||||
|
Update: u,
|
||||||
|
}).Serve(chn)
|
||||||
}
|
}
|
||||||
|
|
||||||
chn, ok := chans[sid]
|
chn, ok := chans[sid]
|
||||||
// The bot MUST get the "start" command.
|
// The bot MUST get the "start" command.
|
||||||
// It will do nothing otherwise.
|
// It will do nothing otherwise.
|
||||||
if ok {
|
if ok {
|
||||||
chn <- u
|
chn.Send(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,21 @@ func (w *CommandWidget) WithUsageFunc(fn ActionFunc) *CommandWidget {
|
||||||
return w.WithUsage(fn)
|
return w.WithUsage(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (widget *CommandWidget) Serve(c *Context, updates chan *Update) error {
|
func (widget *Command) Filter(
|
||||||
|
u *Update,
|
||||||
|
msgs ...*Message,
|
||||||
|
) bool {
|
||||||
|
/*if u.Message == nil || !u.Message.IsCommand() {
|
||||||
|
return false
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *CommandWidget) Serve(
|
||||||
|
c *Context,
|
||||||
|
updates *UpdateChan,
|
||||||
|
) {
|
||||||
commanders := make(map[CommandName] BotCommander)
|
commanders := make(map[CommandName] BotCommander)
|
||||||
for k, v := range widget.Commands {
|
for k, v := range widget.Commands {
|
||||||
commanders[k] = v
|
commanders[k] = v
|
||||||
|
@ -153,7 +167,7 @@ func (widget *CommandWidget) Serve(c *Context, updates chan *Update) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdUpdates chan *Update
|
var cmdUpdates chan *Update
|
||||||
for u := range updates {
|
for u := range updates.Chan() {
|
||||||
if c.ScreenId() == "" && u.Message != nil {
|
if c.ScreenId() == "" && u.Message != nil {
|
||||||
// Skipping and executing the preinit action
|
// Skipping and executing the preinit action
|
||||||
// while we have the empty screen.
|
// while we have the empty screen.
|
||||||
|
@ -178,13 +192,12 @@ func (widget *CommandWidget) Serve(c *Context, updates chan *Update) error {
|
||||||
if cmdUpdates != nil {
|
if cmdUpdates != nil {
|
||||||
close(cmdUpdates)
|
close(cmdUpdates)
|
||||||
}
|
}
|
||||||
cmdUpdates = make(chan *Update)
|
cmdUpdates := NewUpdateChan()
|
||||||
go func() {
|
go func() {
|
||||||
cmd.Widget.Serve(
|
cmd.Widget.Serve(
|
||||||
&Context{context: c.context, Update: u},
|
&Context{context: c.context, Update: u},
|
||||||
cmdUpdates,
|
cmdUpdates,
|
||||||
)
|
)
|
||||||
close(cmdUpdates)
|
|
||||||
cmdUpdates = nil
|
cmdUpdates = nil
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -199,5 +212,4 @@ func (widget *CommandWidget) Serve(c *Context, updates chan *Update) error {
|
||||||
c.Skip(u)
|
c.Skip(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ var (
|
||||||
NotAvailableErr = errors.New("the context is not available")
|
NotAvailableErr = errors.New("the context is not available")
|
||||||
EmptyKeyboardTextErr = errors.New("got empty text for a keyboard")
|
EmptyKeyboardTextErr = errors.New("got empty text for a keyboard")
|
||||||
ActionNotDefinedErr = errors.New("action was not defined")
|
ActionNotDefinedErr = errors.New("action was not defined")
|
||||||
|
MapCollisionErr = errors.New("map collision occured")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (wut WrongUpdateType) Error() string {
|
func (wut WrongUpdateType) Error() string {
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (f *File) SendData() string {
|
||||||
}
|
}
|
||||||
func (f *File) SendConfig(
|
func (f *File) SendConfig(
|
||||||
sid SessionId, bot *Bot,
|
sid SessionId, bot *Bot,
|
||||||
) (*SendConfig, error) {
|
) (*SendConfig) {
|
||||||
var config SendConfig
|
var config SendConfig
|
||||||
cid := sid.ToApi()
|
cid := sid.ToApi()
|
||||||
|
|
||||||
|
@ -85,9 +85,9 @@ func (f *File) SendConfig(
|
||||||
|
|
||||||
config.Image = &photo
|
config.Image = &photo
|
||||||
default:
|
default:
|
||||||
return nil, UnknownFileTypeErr
|
panic(UnknownFileTypeErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return &config, nil
|
return &config
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,13 +83,9 @@ func (c *groupContext) Sendf(
|
||||||
format string,
|
format string,
|
||||||
v ...any,
|
v ...any,
|
||||||
) (*Message, error) {
|
) (*Message, error) {
|
||||||
msg, err := c.Send(NewMessage(
|
return c.Send(NewMessage(
|
||||||
c.Session.Id, fmt.Sprintf(format, v...),
|
fmt.Sprintf(format, v...),
|
||||||
))
|
))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends into the chat specified values converted to strings.
|
// Sends into the chat specified values converted to strings.
|
||||||
|
|
|
@ -10,6 +10,7 @@ type Keyboard struct {
|
||||||
// defined action for the button.
|
// defined action for the button.
|
||||||
Action *action
|
Action *action
|
||||||
Rows []ButtonRow
|
Rows []ButtonRow
|
||||||
|
buttonMap ButtonMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type represents reply keyboards.
|
// The type represents reply keyboards.
|
||||||
|
@ -59,6 +60,14 @@ func (kbd *InlineKeyboard) ActionFunc(fn ActionFunc) *InlineKeyboard {
|
||||||
return kbd.WithAction(fn)
|
return kbd.WithAction(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform the keyboard to widget with the specified text.
|
||||||
|
func (kbd *InlineKeyboard) Widget(text string) *InlineKeyboardWidget {
|
||||||
|
ret := &InlineKeyboardWidget{}
|
||||||
|
ret.InlineKeyboard = kbd
|
||||||
|
ret.Text = text
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a new button row to the current keyboard.
|
// Adds a new button row to the current keyboard.
|
||||||
func (kbd *ReplyKeyboard) Row(btns ...*Button) *ReplyKeyboard {
|
func (kbd *ReplyKeyboard) Row(btns ...*Button) *ReplyKeyboard {
|
||||||
// For empty row. We do not need that.
|
// For empty row. We do not need that.
|
||||||
|
@ -80,6 +89,13 @@ func (kbd *ReplyKeyboard) ActionFunc(fn ActionFunc) *ReplyKeyboard {
|
||||||
return kbd.WithAction(fn)
|
return kbd.WithAction(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kbd *ReplyKeyboard) Widget(text string) *ReplyKeyboardWidget {
|
||||||
|
ret := &ReplyKeyboardWidget{}
|
||||||
|
ret.ReplyKeyboard = kbd
|
||||||
|
ret.Text = text
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the Keyboard to the Telegram API type of reply keyboard.
|
// Convert the Keyboard to the Telegram API type of reply keyboard.
|
||||||
func (kbd *ReplyKeyboard) ToApi() any {
|
func (kbd *ReplyKeyboard) ToApi() any {
|
||||||
// Shades everything.
|
// Shades everything.
|
||||||
|
@ -133,12 +149,16 @@ func (kbd *ReplyKeyboard) WithOneTime(oneTime bool) *ReplyKeyboard {
|
||||||
|
|
||||||
// Returns the map of buttons. Used to define the Action.
|
// Returns the map of buttons. Used to define the Action.
|
||||||
func (kbd Keyboard) ButtonMap() ButtonMap {
|
func (kbd Keyboard) ButtonMap() ButtonMap {
|
||||||
|
if kbd.buttonMap != nil {
|
||||||
|
return kbd.buttonMap
|
||||||
|
}
|
||||||
ret := make(ButtonMap)
|
ret := make(ButtonMap)
|
||||||
for _, vi := range kbd.Rows {
|
for _, vi := range kbd.Rows {
|
||||||
for _, vj := range vi {
|
for _, vj := range vi {
|
||||||
ret[vj.Key()] = vj
|
ret[vj.Key()] = vj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
kbd.buttonMap = ret
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,48 +4,23 @@ import (
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Simple text message type.
|
||||||
type MessageConfig struct {
|
type MessageConfig struct {
|
||||||
To SessionId
|
|
||||||
ReplyTo MessageId
|
|
||||||
Text string
|
Text string
|
||||||
Inline *InlineKeyboard
|
|
||||||
Reply *ReplyKeyboard
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(to SessionId, text string) *MessageConfig {
|
// Return new message with the specified text.
|
||||||
|
func NewMessage(text string) *MessageConfig {
|
||||||
ret := &MessageConfig{}
|
ret := &MessageConfig{}
|
||||||
ret.To = to
|
|
||||||
ret.Text = text
|
ret.Text = text
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *MessageConfig) WithInline(
|
|
||||||
inline *InlineKeyboard,
|
|
||||||
) *MessageConfig {
|
|
||||||
config.Inline = inline
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *MessageConfig) WithReply(
|
|
||||||
reply *ReplyKeyboard,
|
|
||||||
) *MessageConfig {
|
|
||||||
config.Reply = reply
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *MessageConfig) SendConfig(
|
func (config *MessageConfig) SendConfig(
|
||||||
sid SessionId, bot *Bot,
|
sid SessionId, bot *Bot,
|
||||||
) (*SendConfig, error) {
|
) (*SendConfig) {
|
||||||
var ret SendConfig
|
var ret SendConfig
|
||||||
msg := tgbotapi.NewMessage(config.To.ToApi(), config.Text)
|
msg := tgbotapi.NewMessage(sid.ToApi(), config.Text)
|
||||||
if config.Inline != nil {
|
|
||||||
msg.ReplyMarkup = config.Inline.ToApi()
|
|
||||||
}
|
|
||||||
// Reply shades the inline.
|
|
||||||
if config.Reply != nil {
|
|
||||||
msg.ReplyMarkup = config.Reply.ToApi()
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Message = &msg
|
ret.Message = &msg
|
||||||
return &ret, nil
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
214
tg/page.go
214
tg/page.go
|
@ -1,7 +1,7 @@
|
||||||
package tg
|
package tg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The basic widget to provide keyboard functionality
|
// The basic widget to provide keyboard functionality
|
||||||
|
@ -9,9 +9,9 @@ import (
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Text string
|
Text string
|
||||||
SubWidget Widget
|
SubWidget Widget
|
||||||
Inline *InlineKeyboard
|
Inline *InlineKeyboardWidget
|
||||||
Reply *ReplyKeyboard
|
|
||||||
Action Action
|
Action Action
|
||||||
|
Reply *ReplyKeyboardWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return new page with the specified text.
|
// Return new page with the specified text.
|
||||||
|
@ -22,13 +22,13 @@ func NewPage(text string) *Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the inline keyboard.
|
// Set the inline keyboard.
|
||||||
func (p *Page) WithInline(inline *InlineKeyboard) *Page {
|
func (p *Page) WithInline(inline *InlineKeyboardWidget) *Page {
|
||||||
p.Inline = inline
|
p.Inline = inline
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the reply keyboard.
|
// Set the reply keyboard.
|
||||||
func (p *Page) WithReply(reply *ReplyKeyboard) *Page {
|
func (p *Page) WithReply(reply *ReplyKeyboardWidget) *Page {
|
||||||
p.Reply = reply
|
p.Reply = reply
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
@ -51,150 +51,76 @@ func (p *Page) WithSub(sub Widget) *Page {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) Serve(
|
|
||||||
c *Context, updates chan *Update,
|
|
||||||
) error {
|
|
||||||
msgs, err := c.Render(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The inline message is always returned
|
func (p *Page) Render(
|
||||||
// and the reply one is useless in our case.
|
|
||||||
inlineMsg := msgs[0]
|
|
||||||
|
|
||||||
if p.Action != nil {
|
|
||||||
c.run(p.Action, c.Update)
|
|
||||||
}
|
|
||||||
var subUpdates chan *Update
|
|
||||||
if p.SubWidget != nil {
|
|
||||||
subUpdates = make(chan *Update)
|
|
||||||
go p.SubWidget.Serve(c, subUpdates)
|
|
||||||
defer close(subUpdates)
|
|
||||||
}
|
|
||||||
for u := range updates {
|
|
||||||
var act Action
|
|
||||||
if u.Message != nil {
|
|
||||||
text := u.Message.Text
|
|
||||||
kbd := p.Reply
|
|
||||||
if kbd == nil {
|
|
||||||
if subUpdates != nil {
|
|
||||||
subUpdates <- u
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
btns := kbd.ButtonMap()
|
|
||||||
btn, ok := btns[text]
|
|
||||||
if !ok {
|
|
||||||
if u.Message.Location != nil {
|
|
||||||
for _, b := range btns {
|
|
||||||
if b.SendLocation {
|
|
||||||
btn = b
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if subUpdates != nil {
|
|
||||||
subUpdates <- u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if btn != nil {
|
|
||||||
act = btn.Action
|
|
||||||
} else if kbd.Action != nil {
|
|
||||||
act = kbd.Action
|
|
||||||
}
|
|
||||||
} else if u.CallbackQuery != nil {
|
|
||||||
if u.CallbackQuery.Message.MessageID != inlineMsg.MessageID {
|
|
||||||
if subUpdates != nil {
|
|
||||||
subUpdates <- u
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cb := tgbotapi.NewCallback(
|
|
||||||
u.CallbackQuery.ID,
|
|
||||||
u.CallbackQuery.Data,
|
|
||||||
)
|
|
||||||
data := u.CallbackQuery.Data
|
|
||||||
|
|
||||||
_, err := c.Bot.Api.Request(cb)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
kbd := p.Inline
|
|
||||||
if kbd == nil {
|
|
||||||
if subUpdates != nil {
|
|
||||||
subUpdates <- u
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
btns := kbd.ButtonMap()
|
|
||||||
btn, ok := btns[data]
|
|
||||||
if !ok {
|
|
||||||
if subUpdates != nil {
|
|
||||||
subUpdates <- u
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if btn != nil {
|
|
||||||
act = btn.Action
|
|
||||||
} else if kbd.Action != nil {
|
|
||||||
act = kbd.Action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Run(act, u)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Page) Render(
|
|
||||||
sid SessionId, bot *Bot,
|
sid SessionId, bot *Bot,
|
||||||
) ([]*SendConfig, error) {
|
) ([]*SendConfig) {
|
||||||
cid := sid.ToApi()
|
reply := p.Reply
|
||||||
reply := s.Reply
|
inline := p.Inline
|
||||||
inline := s.Inline
|
|
||||||
ret := []*SendConfig{}
|
ret := []*SendConfig{}
|
||||||
var txt string
|
|
||||||
// Screen text and inline keyboard.
|
if p.Text != "" {
|
||||||
if s.Text != "" {
|
cfg := NewMessage(p.Text).SendConfig(sid, bot).
|
||||||
txt = s.Text
|
WithName("page/text")
|
||||||
} else if inline != nil {
|
ret = append(ret, cfg)
|
||||||
// Default to send the keyboard.
|
|
||||||
txt = ">"
|
|
||||||
}
|
}
|
||||||
if txt != "" {
|
if inline != nil {
|
||||||
msgConfig := tgbotapi.NewMessage(cid, txt)
|
cfg := inline.SendConfig(sid, bot).
|
||||||
if inline != nil {
|
WithName("page/inline")
|
||||||
msgConfig.ReplyMarkup = inline.ToApi()
|
ret = append(ret, cfg)
|
||||||
} else if reply != nil {
|
}
|
||||||
msgConfig.ReplyMarkup = reply.ToApi()
|
if p.Reply != nil {
|
||||||
ret = append(ret, &SendConfig{Message: &msgConfig})
|
cfg := reply.SendConfig(sid, bot).
|
||||||
return ret, nil
|
WithName("page/reply")
|
||||||
} else {
|
ret = append(ret, cfg)
|
||||||
msgConfig.ReplyMarkup = NewReply().
|
|
||||||
WithRemove(true).
|
|
||||||
ToApi()
|
|
||||||
ret = append(ret, &SendConfig{Message: &msgConfig})
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
ret = append(ret, &SendConfig{Message: &msgConfig})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screen text and reply keyboard.
|
return ret
|
||||||
if reply != nil {
|
|
||||||
msgConfig := tgbotapi.NewMessage(cid, ">")
|
|
||||||
msgConfig.ReplyMarkup = reply.ToApi()
|
|
||||||
ret = append(ret, &SendConfig{
|
|
||||||
Message: &msgConfig,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Removing keyboard if there is none.
|
|
||||||
msgConfig := tgbotapi.NewMessage(cid, ">")
|
|
||||||
msgConfig.ReplyMarkup = NewReply().
|
|
||||||
WithRemove(true).
|
|
||||||
ToApi()
|
|
||||||
ret = append(ret, &SendConfig{Message: &msgConfig})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Page) Filter(
|
||||||
|
u *Update, msgs MessageMap,
|
||||||
|
) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) Serve(
|
||||||
|
c *Context, updates *UpdateChan,
|
||||||
|
) {
|
||||||
|
msgs, _ := c.Render(p)
|
||||||
|
inlineMsg := msgs["page/inline"]
|
||||||
|
if p.Action != nil {
|
||||||
|
c.Run(p.Action, c.Update)
|
||||||
|
}
|
||||||
|
|
||||||
|
subUpdates := c.RunWidgetBg(p.SubWidget)
|
||||||
|
defer subUpdates.Close()
|
||||||
|
|
||||||
|
inlineUpdates := c.RunWidgetBg(p.Inline)
|
||||||
|
defer inlineUpdates.Close()
|
||||||
|
|
||||||
|
replyUpdates := c.RunWidgetBg(p.Reply)
|
||||||
|
defer replyUpdates.Close()
|
||||||
|
|
||||||
|
subFilter, subFilterOk := p.SubWidget.(Filterer)
|
||||||
|
for u := range updates.Chan() {
|
||||||
|
switch {
|
||||||
|
case !p.Inline.Filter(u, MessageMap{"": inlineMsg}) :
|
||||||
|
inlineUpdates.Send(u)
|
||||||
|
case !p.Reply.Filter(u, msgs) :
|
||||||
|
replyUpdates.Send(u )
|
||||||
|
case p.SubWidget != nil :
|
||||||
|
if subFilterOk {
|
||||||
|
if subFilter.Filter(u, msgs) {
|
||||||
|
subUpdates.Send(u)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subUpdates.Send(u)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ type context struct {
|
||||||
Session *Session
|
Session *Session
|
||||||
// To reach the bot abilities inside callbacks.
|
// To reach the bot abilities inside callbacks.
|
||||||
Bot *Bot
|
Bot *Bot
|
||||||
skippedUpdates chan *Update
|
skippedUpdates *UpdateChan
|
||||||
// Current screen ID.
|
// Current screen ID.
|
||||||
screenId, prevScreenId ScreenId
|
screenId, prevScreenId ScreenId
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,12 @@ type context struct {
|
||||||
// handling functions. Is provided to Act() function always.
|
// handling functions. Is provided to Act() function always.
|
||||||
|
|
||||||
// Goroutie function to handle each user.
|
// Goroutie function to handle each user.
|
||||||
func (c *context) handleUpdateChan(updates chan *Update) {
|
func (c *Context) Serve(updates *UpdateChan) {
|
||||||
beh := c.Bot.behaviour
|
beh := c.Bot.behaviour
|
||||||
if beh.Init != nil {
|
if beh.Init != nil {
|
||||||
c.run(beh.Init, nil)
|
c.Run(beh.Init, c.Update)
|
||||||
}
|
}
|
||||||
beh.Root.Serve(&Context{
|
beh.Root.Serve(c, updates)
|
||||||
context: c,
|
|
||||||
}, updates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,14 +51,12 @@ func (c *Context) Run(a Action, u *Update) {
|
||||||
// Skip the update sending it down to
|
// Skip the update sending it down to
|
||||||
// the underlying widget.
|
// the underlying widget.
|
||||||
func (c *Context) Skip(u *Update) {
|
func (c *Context) Skip(u *Update) {
|
||||||
if c.skippedUpdates != nil {
|
c.skippedUpdates.Send(u)
|
||||||
c.skippedUpdates <- u
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders the Renedrable object to the side of client
|
// Renders the Renedrable object to the side of client
|
||||||
// and returns the messages it sent.
|
// and returns the messages it sent.
|
||||||
func (c *Context) Render(v Renderable) ([]*Message, error) {
|
func (c *Context) Render(v Renderable) (MessageMap, error) {
|
||||||
return c.Bot.Render(c.Session.Id, v)
|
return c.Bot.Render(c.Session.Id, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,13 +67,7 @@ func (c *Context) Send(v Sendable) (*Message, error) {
|
||||||
|
|
||||||
// Sends the formatted with fmt.Sprintf message to the user.
|
// Sends the formatted with fmt.Sprintf message to the user.
|
||||||
func (c *Context) Sendf(format string, v ...any) (*Message, error) {
|
func (c *Context) Sendf(format string, v ...any) (*Message, error) {
|
||||||
msg, err := c.Send(NewMessage(
|
return c.Send(NewMessage(fmt.Sprintf(format, v...)))
|
||||||
c.Session.Id, fmt.Sprintf(format, v...),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface to interact with the user.
|
// Interface to interact with the user.
|
||||||
|
@ -130,10 +120,8 @@ func (c *Context) ChangeScreen(screenId ScreenId, args ...any) error {
|
||||||
c.screenId = screenId
|
c.screenId = screenId
|
||||||
|
|
||||||
// Making the new channel for the widget.
|
// Making the new channel for the widget.
|
||||||
if c.skippedUpdates != nil {
|
c.skippedUpdates.Close()
|
||||||
close(c.skippedUpdates)
|
c.skippedUpdates = NewUpdateChan()
|
||||||
}
|
|
||||||
c.skippedUpdates = make(chan *Update)
|
|
||||||
if screen.Widget != nil {
|
if screen.Widget != nil {
|
||||||
// Running the widget if the screen has one.
|
// Running the widget if the screen has one.
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -142,6 +130,8 @@ func (c *Context) ChangeScreen(screenId ScreenId, args ...any) error {
|
||||||
Update: c.Update,
|
Update: c.Update,
|
||||||
Args: args,
|
Args: args,
|
||||||
}, c.skippedUpdates)
|
}, c.skippedUpdates)
|
||||||
|
|
||||||
|
c.skippedUpdates.Close()
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
panic("no widget defined for the screen")
|
panic("no widget defined for the screen")
|
||||||
|
@ -149,3 +139,7 @@ func (c *Context) ChangeScreen(screenId ScreenId, args ...any) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) ChangeToPrevScreen() {
|
||||||
|
c.ChangeScreen(c.PrevScreenId())
|
||||||
|
}
|
||||||
|
|
|
@ -11,9 +11,6 @@ type Screen struct {
|
||||||
Id ScreenId
|
Id ScreenId
|
||||||
// The widget to run when reaching the screen.
|
// The widget to run when reaching the screen.
|
||||||
Widget Widget
|
Widget Widget
|
||||||
|
|
||||||
// Needs implementation later.
|
|
||||||
Dynamic DynamicWidget
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map structure for the screens.
|
// Map structure for the screens.
|
||||||
|
@ -27,8 +24,3 @@ func NewScreen(id ScreenId, widget Widget) *Screen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screen) WithDynamic(dynamic DynamicWidget) *Screen {
|
|
||||||
s.Dynamic = dynamic
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
23
tg/send.go
23
tg/send.go
|
@ -10,25 +10,38 @@ type Image any
|
||||||
// Implementing the interface lets the
|
// Implementing the interface lets the
|
||||||
// value to be sent.
|
// value to be sent.
|
||||||
type Sendable interface {
|
type Sendable interface {
|
||||||
SendConfig(SessionId, *Bot) (*SendConfig, error)
|
SendConfig(SessionId, *Bot) *SendConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type Renderable interface {
|
type Renderable interface {
|
||||||
Render(SessionId, *Bot) ([]*SendConfig, error)
|
Render(SessionId, *Bot) ([]*SendConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Errorer interface {
|
||||||
|
Err() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type is used as an endpoint to send messages
|
// The type is used as an endpoint to send messages
|
||||||
// via bot.Send .
|
// via bot.Send .
|
||||||
type SendConfig struct {
|
type SendConfig struct {
|
||||||
// Simple message with text.
|
// The name will be used to store
|
||||||
// to add text use lower image
|
// the message in the map.
|
||||||
// or see the ParseMode for tgbotapi .
|
Name string
|
||||||
|
// Message with text and keyboard.
|
||||||
Message *tgbotapi.MessageConfig
|
Message *tgbotapi.MessageConfig
|
||||||
|
|
||||||
// The image to be sent.
|
// The image to be sent.
|
||||||
Image *tgbotapi.PhotoConfig
|
Image *tgbotapi.PhotoConfig
|
||||||
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *SendConfig) WithName(name string) *SendConfig {
|
||||||
|
cfg.Name = name
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageMap map[string] *Message
|
||||||
|
|
||||||
// Convert to the bot.Api.Send format.
|
// Convert to the bot.Api.Send format.
|
||||||
func (config *SendConfig) ToApi() tgbotapi.Chattable {
|
func (config *SendConfig) ToApi() tgbotapi.Chattable {
|
||||||
if config.Message != nil {
|
if config.Message != nil {
|
||||||
|
|
|
@ -14,9 +14,6 @@ func (si SessionId) ToApi() int64 {
|
||||||
type Session struct {
|
type Session struct {
|
||||||
// Id of the chat of the user.
|
// Id of the chat of the user.
|
||||||
Id SessionId
|
Id SessionId
|
||||||
// True if the session started.
|
|
||||||
// (got the '/start' command.
|
|
||||||
started bool
|
|
||||||
// Custom value for each user.
|
// Custom value for each user.
|
||||||
Data any
|
Data any
|
||||||
}
|
}
|
||||||
|
|
262
tg/widget.go
262
tg/widget.go
|
@ -1,7 +1,7 @@
|
||||||
package tg
|
package tg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implementing the interface provides
|
// Implementing the interface provides
|
||||||
|
@ -12,7 +12,80 @@ type Widget interface {
|
||||||
// widget MUST end its work.
|
// widget MUST end its work.
|
||||||
// Mostly made by looping over the
|
// Mostly made by looping over the
|
||||||
// updates range.
|
// updates range.
|
||||||
Serve(*Context, chan *Update) error
|
Serve(*Context, *UpdateChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs implementation.
|
||||||
|
// Behaviour can be the root widget or something like
|
||||||
|
// that.
|
||||||
|
type RootWidget interface {
|
||||||
|
Widget
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementing the interface provides way
|
||||||
|
// to know exactly what kind of updates
|
||||||
|
// the widget needs.
|
||||||
|
type Filterer interface {
|
||||||
|
// Return true if should filter the update
|
||||||
|
// and not send it inside the widget.
|
||||||
|
Filter(*Update, MessageMap) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type represents general update channel.
|
||||||
|
type UpdateChan struct {
|
||||||
|
chn chan *Update
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return new update channel.
|
||||||
|
func NewUpdateChan() *UpdateChan {
|
||||||
|
ret := &UpdateChan{}
|
||||||
|
ret.chn = make(chan *Update)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (updates *UpdateChan) Chan() chan *Update {
|
||||||
|
return updates.chn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send an update to the channel.
|
||||||
|
func (updates *UpdateChan) Send(u *Update) {
|
||||||
|
if updates != nil && updates.chn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updates.chn <- u
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read an update from the channel.
|
||||||
|
func (updates *UpdateChan) Read() *Update {
|
||||||
|
if updates == nil || updates.chn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return <-updates.chn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the channel is closed.
|
||||||
|
func (updates *UpdateChan) Closed() bool {
|
||||||
|
return updates.chn == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the channel. Used in defers.
|
||||||
|
func (updates *UpdateChan) Close() {
|
||||||
|
if updates == nil || updates.chn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close(updates.chn)
|
||||||
|
updates.chn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) RunWidgetBg(widget Widget) *UpdateChan {
|
||||||
|
if widget == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := NewUpdateChan()
|
||||||
|
go widget.Serve(c, updates)
|
||||||
|
|
||||||
|
return updates
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementing the interface provides
|
// Implementing the interface provides
|
||||||
|
@ -22,9 +95,188 @@ type DynamicWidget interface {
|
||||||
|
|
||||||
// The function that implements the Widget
|
// The function that implements the Widget
|
||||||
// interface.
|
// interface.
|
||||||
type WidgetFunc func(*Context, chan *Update) error
|
type WidgetFunc func(*Context, *UpdateChan)
|
||||||
|
|
||||||
func (wf WidgetFunc) Serve(c *Context, updates chan *Update) error {
|
func (wf WidgetFunc) Serve(c *Context, updates *UpdateChan) {
|
||||||
return wf(c, updates)
|
wf(c, updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf WidgetFunc) Filter(
|
||||||
|
u *Update,
|
||||||
|
msgs ...*Message,
|
||||||
|
) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type implements message with an inline keyboard.
|
||||||
|
type InlineKeyboardWidget struct {
|
||||||
|
Text string
|
||||||
|
*InlineKeyboard
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type implements dynamic inline keyboard widget.
|
||||||
|
// Aka message with inline keyboard.
|
||||||
|
func NewInlineKeyboardWidget(
|
||||||
|
text string,
|
||||||
|
inline *InlineKeyboard,
|
||||||
|
) *InlineKeyboardWidget {
|
||||||
|
ret := &InlineKeyboardWidget{}
|
||||||
|
ret.Text = text
|
||||||
|
ret.InlineKeyboard = inline
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
func (widget *InlineKeyboardWidget) SendConfig(
|
||||||
|
sid SessionId,
|
||||||
|
bot *Bot,
|
||||||
|
) (*SendConfig) {
|
||||||
|
var text string
|
||||||
|
if widget.Text != "" {
|
||||||
|
text = widget.Text
|
||||||
|
} else {
|
||||||
|
text = ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text)
|
||||||
|
msgConfig.ReplyMarkup = widget.ToApi()
|
||||||
|
|
||||||
|
ret := &SendConfig{}
|
||||||
|
ret.Message = &msgConfig
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *InlineKeyboardWidget) Serve(
|
||||||
|
c *Context,
|
||||||
|
updates *UpdateChan,
|
||||||
|
) {
|
||||||
|
for u := range updates.Chan() {
|
||||||
|
var act Action
|
||||||
|
if u.CallbackQuery == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cb := tgbotapi.NewCallback(
|
||||||
|
u.CallbackQuery.ID,
|
||||||
|
u.CallbackQuery.Data,
|
||||||
|
)
|
||||||
|
data := u.CallbackQuery.Data
|
||||||
|
|
||||||
|
_, err := c.Bot.Api.Request(cb)
|
||||||
|
if err != nil {
|
||||||
|
//return err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
btns := widget.ButtonMap()
|
||||||
|
btn, ok := btns[data]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if btn != nil {
|
||||||
|
act = btn.Action
|
||||||
|
} else if widget.Action != nil {
|
||||||
|
act = widget.Action
|
||||||
|
}
|
||||||
|
c.Run(act, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *InlineKeyboardWidget) Filter(
|
||||||
|
u *Update,
|
||||||
|
msgs MessageMap,
|
||||||
|
) bool {
|
||||||
|
if widget == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if u.CallbackQuery == nil || len(msgs) < 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
inlineMsg, inlineOk := msgs[""]
|
||||||
|
if inlineOk {
|
||||||
|
if u.CallbackQuery.Message.MessageID !=
|
||||||
|
inlineMsg.MessageID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type implements dynamic reply keyboard widget.
|
||||||
|
type ReplyKeyboardWidget struct {
|
||||||
|
Text string
|
||||||
|
*ReplyKeyboard
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns new empty reply keyboard widget.
|
||||||
|
func NewReplyKeyboardWidget(
|
||||||
|
text string,
|
||||||
|
reply *ReplyKeyboard,
|
||||||
|
) *ReplyKeyboardWidget {
|
||||||
|
ret := &ReplyKeyboardWidget{}
|
||||||
|
ret.Text = text
|
||||||
|
ret.ReplyKeyboard = reply
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *ReplyKeyboardWidget) SendConfig(
|
||||||
|
sid SessionId,
|
||||||
|
bot *Bot,
|
||||||
|
) (*SendConfig) {
|
||||||
|
var text string
|
||||||
|
if widget.Text != "" {
|
||||||
|
text = widget.Text
|
||||||
|
} else {
|
||||||
|
text = ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
msgConfig := tgbotapi.NewMessage(sid.ToApi(), text)
|
||||||
|
msgConfig.ReplyMarkup = widget.ToApi()
|
||||||
|
|
||||||
|
ret := &SendConfig{}
|
||||||
|
ret.Message = &msgConfig
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *ReplyKeyboardWidget) Filter(
|
||||||
|
u *Update,
|
||||||
|
msgs MessageMap,
|
||||||
|
) bool {
|
||||||
|
if widget == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if u.Message == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok := widget.ButtonMap()[u.Message.Text]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *ReplyKeyboardWidget) Serve(
|
||||||
|
c *Context,
|
||||||
|
updates *UpdateChan,
|
||||||
|
) {
|
||||||
|
for u := range updates.Chan() {
|
||||||
|
var btn *Button
|
||||||
|
text := u.Message.Text
|
||||||
|
btns := widget.ButtonMap()
|
||||||
|
btn, ok := btns[text]
|
||||||
|
if !ok {
|
||||||
|
if u.Message.Location != nil {
|
||||||
|
for _, b := range btns {
|
||||||
|
if b.SendLocation {
|
||||||
|
btn = b
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if btn != nil {
|
||||||
|
c.Run(btn.Action, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue