A bit of refactoring to make more sense for the user and the developer sides.

This commit is contained in:
Andrey Parhomenko 2023-09-25 19:52:57 +03:00
parent ce93a54cfa
commit 0abd04a9ea
6 changed files with 318 additions and 330 deletions

112
tg/inline.go Normal file
View file

@ -0,0 +1,112 @@
package tg
import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// The type represents keyboard to be emdedded into the messages (inline in Telegram terms).
type Inline struct {
*Keyboard
}
// Transform the keyboard to widget with the specified text.
func (kbd *Inline) Widget(text string) *InlineWidget {
ret := &InlineWidget{}
ret.Inline = kbd
ret.Text = text
return ret
}
// Convert the inline keyboard to markup for the tgbotapi.
func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
rows := [][]tgbotapi.InlineKeyboardButton{}
for _, row := range kbd.Rows {
buttons := []tgbotapi.InlineKeyboardButton{}
for _, button := range row {
buttons = append(buttons, button.ToTelegramInline())
}
rows = append(rows, buttons)
}
return tgbotapi.NewInlineKeyboardMarkup(rows...)
}
// The type implements message with an inline keyboard.
type InlineWidget struct {
Text string
*Inline
}
// Implementing the Sendable interface.
func (widget *InlineWidget) 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
}
// Implementing the Widget interface.
func (widget *InlineWidget) Serve(c *Context) {
for u := range c.Input() {
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 *InlineWidget) Filter(
u *Update,
msgs MessageMap,
) bool {
if widget == nil || u.CallbackQuery == nil {
return true
}
inlineMsg, inlineOk := msgs[""]
if !inlineOk {
return true
}
if u.CallbackQuery.Message.MessageID !=
inlineMsg.MessageID {
return true
}
return false
}

View file

@ -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 general keyboard type used both in Reply and Inline. // The general keyboard type used both in Reply and Inline.
@ -13,15 +13,6 @@ type Keyboard struct {
buttonMap ButtonMap buttonMap ButtonMap
} }
// The type represents reply keyboards.
type ReplyKeyboard struct {
*Keyboard
// If true will be removed after one press.
OneTime bool
// If true will remove the keyboard on send.
Remove bool
}
// Returns the new keyboard with specified rows. // Returns the new keyboard with specified rows.
func NewKeyboard(rows ...ButtonRow) *Keyboard { func NewKeyboard(rows ...ButtonRow) *Keyboard {
ret := &Keyboard{} ret := &Keyboard{}
@ -69,88 +60,16 @@ func (kbd Keyboard) ButtonMap() ButtonMap {
} }
// Convert the keyboard to the more specific inline one. // Convert the keyboard to the more specific inline one.
func (kbd *Keyboard) Inline() *InlineKeyboard { func (kbd *Keyboard) Inline() *Inline {
ret := &InlineKeyboard{} ret := &Inline{}
ret.Keyboard = kbd ret.Keyboard = kbd
return ret return ret
} }
func (kbd *Keyboard) Reply() *ReplyKeyboard { // Convert the keyboard to the more specific reply one.
ret := &ReplyKeyboard{} func (kbd *Keyboard) Reply() *Reply {
ret := &Reply{}
ret.Keyboard = kbd ret.Keyboard = kbd
return ret return ret
} }
// The type represents keyboard to be emdedded into the messages.
type InlineKeyboard struct {
*Keyboard
}
// 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
}
// Transform the keyboard to widget with the specified text.
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.
func (kbd *ReplyKeyboard) ToApi() any {
// Shades everything.
if kbd.Remove {
return tgbotapi.NewRemoveKeyboard(true)
}
rows := [][]tgbotapi.KeyboardButton{}
for _, row := range kbd.Rows {
buttons := []tgbotapi.KeyboardButton{}
for _, button := range row {
buttons = append(buttons, button.ToTelegram())
}
rows = append(rows, buttons)
}
if kbd.OneTime {
return tgbotapi.NewOneTimeReplyKeyboard(rows...)
}
return tgbotapi.NewReplyKeyboard(rows...)
}
// Convert the inline keyboard to markup for the tgbotapi.
func (kbd *InlineKeyboard) ToApi() tgbotapi.InlineKeyboardMarkup {
rows := [][]tgbotapi.InlineKeyboardButton{}
for _, row := range kbd.Rows {
buttons := []tgbotapi.InlineKeyboardButton{}
for _, button := range row {
buttons = append(buttons, button.ToTelegramInline())
}
rows = append(rows, buttons)
}
return tgbotapi.NewInlineKeyboardMarkup(rows...)
}
// Set if we should remove current keyboard on the user side
// when sending the keyboard.
func (kbd *ReplyKeyboard) WithRemove(remove bool) *ReplyKeyboard {
kbd.Remove = remove
return kbd
}
// Set if the keyboard should be hidden after
// one of buttons is pressede.
func (kbd *ReplyKeyboard) WithOneTime(oneTime bool) *ReplyKeyboard {
kbd.OneTime = oneTime
return kbd
}

View file

@ -7,11 +7,11 @@ import (
// The basic widget to provide keyboard functionality // The basic widget to provide keyboard functionality
// without implementing much. // without implementing much.
type Page struct { type Page struct {
Action Action
Text string Text string
SubWidget Widget SubWidget Widget
Inline *InlineKeyboardWidget Inline *InlineWidget
Action Action Reply *ReplyWidget
Reply *ReplyKeyboardWidget
} }
// Return new page with the specified text. // Return new page with the specified text.
@ -26,13 +26,13 @@ func (p *Page) WithText(text string) *Page {
} }
// Set the inline keyboard. // Set the inline keyboard.
func (p *Page) WithInline(inline *InlineKeyboardWidget) *Page { func (p *Page) WithInline(inline *InlineWidget) *Page {
p.Inline = inline p.Inline = inline
return p return p
} }
// Set the reply keyboard. // Set the reply keyboard.
func (p *Page) WithReply(reply *ReplyKeyboardWidget) *Page { func (p *Page) WithReply(reply *ReplyWidget) *Page {
p.Reply = reply p.Reply = reply
return p return p
} }

143
tg/reply.go Normal file
View file

@ -0,0 +1,143 @@
package tg
import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// The type represents reply keyboards.
type Reply struct {
*Keyboard
// If true will be removed after one press.
OneTime bool
// If true will remove the keyboard on send.
Remove bool
}
// Set if we should remove current keyboard on the user side
// when sending the keyboard.
func (kbd *Reply) WithRemove(remove bool) *Reply {
kbd.Remove = remove
return kbd
}
// Set if the keyboard should be hidden after
// one of buttons is pressede.
func (kbd *Reply) WithOneTime(oneTime bool) *Reply{
kbd.OneTime = oneTime
return kbd
}
// Convert the Keyboard to the Telegram API type of reply keyboard.
func (kbd *Reply) ToApi() any {
// Shades everything.
if kbd.Remove {
return tgbotapi.NewRemoveKeyboard(true)
}
rows := [][]tgbotapi.KeyboardButton{}
for _, row := range kbd.Rows {
buttons := []tgbotapi.KeyboardButton{}
for _, button := range row {
buttons = append(buttons, button.ToTelegram())
}
rows = append(rows, buttons)
}
if kbd.OneTime {
return tgbotapi.NewOneTimeReplyKeyboard(rows...)
}
return tgbotapi.NewReplyKeyboard(rows...)
}
// Transform the keyboard to widget with the specified text.
func (kbd *Reply) Widget(text string) *ReplyWidget {
ret := &ReplyWidget{}
ret.Reply = kbd
ret.Text = text
return ret
}
// The type implements reply keyboard widget.
type ReplyWidget struct {
Text string
*Reply
}
// Implementing the sendable interface.
func (widget *ReplyWidget) SendConfig(
sid SessionId,
bot *Bot,
) (*SendConfig) {
if widget == nil {
msgConfig := tgbotapi.NewMessage(sid.ToApi(), ">")
msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
return &SendConfig{
Message: &msgConfig,
}
}
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 *ReplyWidget) 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 {
if u.Message.Location != nil {
locBtn := widget.ButtonMap().LocationButton()
if locBtn == nil {
return true
}
} else {
return true
}
}
return false
}
// Implementing the Widget interface.
func (widget *ReplyWidget) Serve(c *Context) {
for u := range c.Input() {
if u.Message == nil || u.Message.Text == "" {
continue
}
var btn *Button
text := u.Message.Text
btns := widget.ButtonMap()
btn, ok := btns[text]
if !ok {
if u.Message.Location != nil {
btn = btns.LocationButton()
}
}
if btn != nil {
c.Run(btn.Action, u)
}
}
}

51
tg/update.go Normal file
View file

@ -0,0 +1,51 @@
package tg
// 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.
// Returns true if the update was sent.
func (updates *UpdateChan) Send(u *Update) bool {
if updates == nil || updates.chn == nil {
return false
}
updates.chn <- u
return true
}
// 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==nil || 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
}

View file

@ -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"
//"fmt" //"fmt"
) )
@ -52,60 +52,6 @@ func (f Func) Serve(c *Context) {
f(c) f(c)
} }
// 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.
// Returns true if the update was sent.
func (updates *UpdateChan) Send(u *Update) bool {
if updates == nil || updates.chn == nil {
return false
}
updates.chn <- u
return true
}
// 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==nil || 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
}
// Implementing the interface provides
type DynamicWidget interface {
MakeWidget() Widget
}
// The function that implements the Widget // The function that implements the Widget
// interface. // interface.
@ -115,187 +61,4 @@ func (wf WidgetFunc) Serve(c *Context) {
wf(c) wf(c)
} }
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) {
for u := range c.Input() {
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 || u.CallbackQuery == nil {
return true
}
inlineMsg, inlineOk := msgs[""]
if !inlineOk {
return true
}
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) {
if widget == nil {
msgConfig := tgbotapi.NewMessage(sid.ToApi(), ">")
msgConfig.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
return &SendConfig{
Message: &msgConfig,
}
}
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 {
if u.Message.Location != nil {
locBtn := widget.ButtonMap().LocationButton()
if locBtn == nil {
return true
}
} else {
return true
}
}
return false
}
func (widget *ReplyKeyboardWidget) Serve(c *Context) {
for u := range c.Input() {
var btn *Button
text := u.Message.Text
btns := widget.ButtonMap()
btn, ok := btns[text]
if !ok {
if u.Message.Location != nil {
btn = btns.LocationButton()
}
}
if btn != nil {
c.Run(btn.Action, u)
}
}
}