save the work about getting rid of pointers.
This commit is contained in:
parent
5186fec028
commit
3f2f16a79e
14 changed files with 348 additions and 413 deletions
13
action.go
13
action.go
|
@ -1,13 +0,0 @@
|
|||
package tg
|
||||
|
||||
import (
|
||||
//"reflect"
|
||||
)
|
||||
|
||||
// The argument for handling in channenl behaviours.
|
||||
type ChannelContext struct {
|
||||
}
|
||||
type CC = ChannelContext
|
||||
type ChannelAction struct {
|
||||
Act (*ChannelContext)
|
||||
}
|
65
bot.go
65
bot.go
|
@ -13,16 +13,15 @@ type User = tgbotapi.User
|
|||
// The wrapper around Telegram API.
|
||||
type Bot struct {
|
||||
// Custom data value.
|
||||
Data any
|
||||
Api *tgbotapi.BotAPI
|
||||
Me *User
|
||||
data any
|
||||
api *tgbotapi.BotAPI
|
||||
me User
|
||||
// Private bot behaviour.
|
||||
behaviour *Behaviour
|
||||
// Group bot behaviour.
|
||||
//groupBehaviour *GroupBehaviour
|
||||
// Bot behaviour in channels.
|
||||
//channelBehaviour *ChannelBehaviour
|
||||
contexts map[SessionId] *context
|
||||
sessions SessionMap
|
||||
//groupSessions GroupSessionMap
|
||||
}
|
||||
|
@ -35,40 +34,49 @@ func NewBot(token string) (*Bot, error) {
|
|||
}
|
||||
|
||||
return &Bot{
|
||||
Api: bot,
|
||||
contexts: make(map[SessionId] *context),
|
||||
api: bot,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bot *Bot) Debug(debug bool) *Bot {
|
||||
bot.Api.Debug = debug
|
||||
bot.api.Debug = debug
|
||||
return bot
|
||||
}
|
||||
|
||||
func (bot *Bot) Api() *tgbotapi.BotAPI {
|
||||
return bot.api
|
||||
}
|
||||
|
||||
func (bot *Bot) Me() User {
|
||||
return bot.me
|
||||
}
|
||||
|
||||
// Send the Renderable to the specified session client side.
|
||||
// Can be used for both group and private sessions because
|
||||
// SessionId represents both for chat IDs.
|
||||
func (bot *Bot) Send(
|
||||
sid SessionId, v Sendable,
|
||||
) (*Message, error) {
|
||||
) (Message, error) {
|
||||
config := v.SendConfig(sid, bot)
|
||||
if config.Error != nil {
|
||||
return nil, config.Error
|
||||
return Message{}, config.Error
|
||||
}
|
||||
|
||||
msg, err := bot.Api.Send(config.ToApi())
|
||||
msg, err := bot.api.Send(config.ToApi())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Message{}, err
|
||||
}
|
||||
return &msg, nil
|
||||
v.SetMessage(msg)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (bot *Bot) Sendf(
|
||||
sid SessionId, format string, v ...any,
|
||||
) (*Message, error){
|
||||
) (Message, error){
|
||||
msg := Messagef(format, v...)
|
||||
return bot.Send(
|
||||
sid,
|
||||
NewMessage(format, v...),
|
||||
&msg,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -76,7 +84,7 @@ func (bot *Bot) Sendf(
|
|||
func (bot *Bot) SendRaw(
|
||||
sid SessionId, v tgbotapi.Chattable,
|
||||
) (*Message, error) {
|
||||
msg, err := bot.Api.Send(v)
|
||||
msg, err := bot.api.Send(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -117,7 +125,7 @@ func (b *Bot) WithGroupSessions(sessions GroupSessionMap) *Bot {
|
|||
func (bot *Bot) DeleteCommands() {
|
||||
//tgbotapi.NewBotCommandScopeAllPrivateChats(),
|
||||
cfg := tgbotapi.NewDeleteMyCommands()
|
||||
bot.Api.Request(cfg)
|
||||
bot.api.Request(cfg)
|
||||
}
|
||||
|
||||
// Setting the command on the user side.
|
||||
|
@ -151,7 +159,7 @@ func (bot *Bot) SetCommands(
|
|||
botCmds...,
|
||||
)
|
||||
|
||||
_, err := bot.Api.Request(cfg)
|
||||
_, err := bot.api.Request(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -170,11 +178,11 @@ func (bot *Bot) Run() error {
|
|||
|
||||
uc := tgbotapi.NewUpdate(0)
|
||||
uc.Timeout = 10
|
||||
updates := bot.Api.GetUpdatesChan(uc)
|
||||
handles := make(map[string] chan *Update)
|
||||
updates := bot.api.GetUpdatesChan(uc)
|
||||
handles := make(map[string] chan Update)
|
||||
|
||||
if bot.behaviour != nil {
|
||||
chn := make(chan *Update)
|
||||
chn := make(chan Update)
|
||||
handles["private"] = chn
|
||||
go bot.handlePrivate(chn)
|
||||
}
|
||||
|
@ -195,10 +203,10 @@ func (bot *Bot) Run() error {
|
|||
}*/
|
||||
|
||||
me, _ := bot.Api.GetMe()
|
||||
bot.Me = &me
|
||||
bot.me = me
|
||||
for up := range updates {
|
||||
u := &Update{
|
||||
Update: &up,
|
||||
u := Update{
|
||||
Update: up,
|
||||
}
|
||||
|
||||
// Sometimes returns nil.
|
||||
|
@ -220,7 +228,7 @@ func (bot *Bot) Run() error {
|
|||
|
||||
// The function handles updates supposed for the private
|
||||
// chat with the bot.
|
||||
func (bot *Bot) handlePrivate(updates chan *Update) {
|
||||
func (bot *Bot) handlePrivate(updates chan Update) {
|
||||
var sid SessionId
|
||||
for u := range updates {
|
||||
sid = SessionId(u.FromChat().ID)
|
||||
|
@ -245,12 +253,13 @@ func (bot *Bot) handlePrivate(updates chan *Update) {
|
|||
bot.contexts[sid] = ctx
|
||||
}
|
||||
|
||||
go (&Context{
|
||||
context: ctx,
|
||||
go Context{
|
||||
session: session,
|
||||
bot: bot,
|
||||
Update: u,
|
||||
input: ctx.updates,
|
||||
}).serve()
|
||||
ctx.updates.Send(u)
|
||||
}.serve()
|
||||
ctx.session.updates.Send(u)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
26
button.go
26
button.go
|
@ -16,7 +16,7 @@ type Button struct {
|
|||
Action Action
|
||||
}
|
||||
|
||||
type ButtonMap map[string]*Button
|
||||
type ButtonMap map[string]Button
|
||||
|
||||
// Returns the only location button in the map.
|
||||
func (btnMap ButtonMap) LocationButton() *Button {
|
||||
|
@ -32,14 +32,14 @@ func (btnMap ButtonMap) LocationButton() *Button {
|
|||
type ButtonRow []*Button
|
||||
|
||||
// Returns new button with the specified text and no action.
|
||||
func NewButton(format string, v ...any) *Button {
|
||||
func Buttonf(format string, v ...any) Button {
|
||||
return &Button{
|
||||
Text: fmt.Sprintf(format, v...),
|
||||
}
|
||||
}
|
||||
|
||||
// Randomize buttons data to make the key unique.
|
||||
func (btn *Button) Rand() *Button {
|
||||
func (btn Button) Rand() Button {
|
||||
rData := make([]byte, 8)
|
||||
rand.Read(rData)
|
||||
data := make([]byte, base64.StdEncoding.EncodedLen(len(rData)))
|
||||
|
@ -49,41 +49,37 @@ func (btn *Button) Rand() *Button {
|
|||
}
|
||||
|
||||
// Set the URL for the button. Only for inline buttons.
|
||||
func (btn *Button) WithUrl(format string, v ...any) *Button {
|
||||
func (btn Button) WithUrl(format string, v ...any) Button {
|
||||
btn.Url = fmt.Sprintf(format, v...)
|
||||
return btn
|
||||
}
|
||||
|
||||
// Set the action when pressing the button.
|
||||
// By default is nil and does nothing.
|
||||
func (btn *Button) WithAction(a Action) *Button {
|
||||
func (btn Button) WithAction(a Action) Button {
|
||||
btn.Action = a
|
||||
return btn
|
||||
}
|
||||
|
||||
func (btn *Button) WithData(dat string) *Button {
|
||||
func (btn Button) WithData(dat string) Button {
|
||||
btn.Data = dat
|
||||
return btn
|
||||
}
|
||||
|
||||
// Sets whether the button must send owner's location.
|
||||
func (btn *Button) WithSendLocation(ok bool) *Button {
|
||||
func (btn Button) WithSendLocation(ok bool) Button {
|
||||
btn.SendLocation = ok
|
||||
return btn
|
||||
}
|
||||
|
||||
func (btn *Button) ActionFunc(fn ActionFunc) *Button {
|
||||
return btn.WithAction(fn)
|
||||
}
|
||||
|
||||
func (btn *Button) Go(pth Path, args ...any) *Button {
|
||||
func (btn Button) Go(pth Path, args ...any) Button {
|
||||
return btn.WithAction(ScreenGo{
|
||||
Path: pth,
|
||||
Args: args,
|
||||
})
|
||||
}
|
||||
|
||||
func (btn *Button) ToTelegram() apix.KeyboardButton {
|
||||
func (btn Button) ToTelegram() apix.KeyboardButton {
|
||||
ret := apix.NewKeyboardButton(btn.Text)
|
||||
if btn.SendLocation {
|
||||
ret.RequestLocation = true
|
||||
|
@ -91,7 +87,7 @@ func (btn *Button) ToTelegram() apix.KeyboardButton {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (btn *Button) ToTelegramInline() apix.InlineKeyboardButton {
|
||||
func (btn Button) ToTelegramInline() apix.InlineKeyboardButton {
|
||||
if btn.Data != "" {
|
||||
return apix.NewInlineKeyboardButtonData(btn.Text, btn.Data)
|
||||
}
|
||||
|
@ -105,7 +101,7 @@ func (btn *Button) ToTelegramInline() apix.InlineKeyboardButton {
|
|||
}
|
||||
|
||||
// Return the key of the button to identify it by messages and callbacks.
|
||||
func (btn *Button) Key() string {
|
||||
func (btn Button) Key() string {
|
||||
if btn == nil {
|
||||
return ""
|
||||
}
|
||||
|
|
40
command.go
40
command.go
|
@ -1,8 +1,6 @@
|
|||
package tg
|
||||
|
||||
import (
|
||||
//"flag"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
|
@ -14,7 +12,6 @@ const (
|
|||
)
|
||||
|
||||
type CommandName string
|
||||
|
||||
type Command struct {
|
||||
Name CommandName
|
||||
Type CommandType
|
||||
|
@ -22,9 +19,10 @@ type Command struct {
|
|||
Action Action
|
||||
Widget Widget
|
||||
}
|
||||
|
||||
type CommandMap map[CommandName]*Command
|
||||
|
||||
func NewCommand(name CommandName, desc string) *Command {
|
||||
func NewCommand(name CommandName, desc string) Command {
|
||||
if name == "" || desc == "" {
|
||||
panic("name and description cannot be an empty string")
|
||||
}
|
||||
|
@ -39,27 +37,23 @@ func (c *Command) WithAction(a Action) *Command {
|
|||
return c
|
||||
}
|
||||
|
||||
func (c *Command) ActionFunc(af ActionFunc) *Command {
|
||||
return c.WithAction(af)
|
||||
}
|
||||
|
||||
func (c *Command) WithWidget(w Widget) *Command {
|
||||
func (c Command) WithWidget(w Widget) Command {
|
||||
c.Widget = w
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Command) WidgetFunc(fn Func) *Command {
|
||||
func (c Command) WidgetFunc(fn Func) Command {
|
||||
return c.WithWidget(fn)
|
||||
}
|
||||
|
||||
func (c *Command) ToApi() tgbotapi.BotCommand {
|
||||
func (c Command) ToApi() tgbotapi.BotCommand {
|
||||
ret := tgbotapi.BotCommand{}
|
||||
ret.Command = string(c.Name)
|
||||
ret.Description = c.Description
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Command) Go(pth Path, args ...any) *Command {
|
||||
func (c Command) Go(pth Path, args ...any) Command {
|
||||
return c.WithAction(ScreenGo{
|
||||
Path: pth,
|
||||
Args: args,
|
||||
|
@ -76,13 +70,12 @@ type CommandCompo struct {
|
|||
|
||||
// Returns new empty CommandCompo.
|
||||
func NewCommandCompo(cmds ...*Command) *CommandCompo {
|
||||
ret := (&CommandCompo{}).WithCommands(cmds...)
|
||||
//ret.Commands = make(CommandMap)
|
||||
ret := CommandCompo{}.WithCommands(cmds...)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Set the commands to handle.
|
||||
func (w *CommandCompo) WithCommands(cmds ...*Command) *CommandCompo {
|
||||
func (w CommandCompo) WithCommands(cmds ...*Command) *CommandCompo {
|
||||
if w.Commands == nil {
|
||||
w.Commands = make(CommandMap)
|
||||
}
|
||||
|
@ -105,23 +98,18 @@ func (w *CommandCompo) WithPreStart(a Action) *CommandCompo {
|
|||
return w
|
||||
}
|
||||
|
||||
// Set the prestart action with function.
|
||||
func (w *CommandCompo) WithPreStartFunc(fn ActionFunc) *CommandCompo {
|
||||
return w.WithPreStart(fn)
|
||||
}
|
||||
|
||||
// Set the usage action.
|
||||
func (w *CommandCompo) WithUsage(a Action) *CommandCompo {
|
||||
func (w CommandCompo) WithUsage(a Action) *CommandCompo {
|
||||
w.Usage = a
|
||||
return w
|
||||
}
|
||||
|
||||
// Set the usage action with function.
|
||||
func (w *CommandCompo) WithUsageFunc(fn ActionFunc) *CommandCompo {
|
||||
func (w CommandCompo) WithUsageFunc(fn ActionFunc) *CommandCompo {
|
||||
return w.WithUsage(fn)
|
||||
}
|
||||
|
||||
func (widget *CommandCompo) Filter(
|
||||
func (widget CommandCompo) Filter(
|
||||
u *Update,
|
||||
) bool {
|
||||
if u.Message == nil || !u.Message.IsCommand() {
|
||||
|
@ -132,13 +120,13 @@ func (widget *CommandCompo) Filter(
|
|||
}
|
||||
|
||||
// Implementing server.
|
||||
func (compo *CommandCompo) Serve(c *Context) {
|
||||
func (compo CommandCompo) Serve(c Context) {
|
||||
/*commanders := make(map[CommandName] BotCommander)
|
||||
for k, v := range compo.Commands {
|
||||
commanders[k] = v
|
||||
}*/
|
||||
c.Bot.DeleteCommands()
|
||||
err := c.Bot.SetCommands(
|
||||
c.bot.DeleteCommands()
|
||||
err := c.bot.SetCommands(
|
||||
tgbotapi.NewBotCommandScopeChat(c.Session.Id.ToApi()),
|
||||
compo.Commands,
|
||||
)
|
||||
|
|
266
context.go
266
context.go
|
@ -8,57 +8,17 @@ import (
|
|||
//"path"
|
||||
)
|
||||
|
||||
func Go(pth Path) UI {
|
||||
return UI{
|
||||
GoWidget(pth),
|
||||
}
|
||||
}
|
||||
|
||||
type GoWidget string
|
||||
// Implementing the Server interface.
|
||||
func (widget GoWidget) Serve(c *Context) {
|
||||
c.input.Close()
|
||||
c.Go(Path(widget))
|
||||
}
|
||||
|
||||
func (widget GoWidget) Render(c *Context) UI {
|
||||
return UI{widget}
|
||||
}
|
||||
|
||||
func (widget GoWidget) Filter(u *Update) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// General context for a specific user.
|
||||
// Is always the same and is not reached
|
||||
// inside end function-handlers.
|
||||
type context struct {
|
||||
Session *Session
|
||||
// To reach the bot abilities inside callbacks.
|
||||
Bot *Bot
|
||||
// Costum status for currently running context.
|
||||
Status any
|
||||
Type ContextType
|
||||
updates *UpdateChan
|
||||
skippedUpdates *UpdateChan
|
||||
// Current screen ID.
|
||||
pathHistory []Path
|
||||
//path, prevPath Path
|
||||
}
|
||||
|
||||
type Contexter interface {
|
||||
GetContext() *Context
|
||||
}
|
||||
|
||||
// Interface to interact with the user.
|
||||
type Context struct {
|
||||
*context
|
||||
session *Session
|
||||
// The update that called the Context usage.
|
||||
*Update
|
||||
update Update
|
||||
// Used as way to provide outer values redirection
|
||||
// into widgets and actions. It is like arguments
|
||||
// for REST API request etc.
|
||||
arg any
|
||||
typ ContextType
|
||||
// Instead of updates as argument.
|
||||
input *UpdateChan
|
||||
}
|
||||
|
@ -68,7 +28,7 @@ type Context struct {
|
|||
// maybe you will find another usage for this.
|
||||
// Returns users context by specified session ID
|
||||
// or nil if the user is not logged in.
|
||||
func (c *Context) As(sid SessionId) *Context {
|
||||
func (c Context) As(sid SessionId) Context {
|
||||
n, ok := c.Bot.contexts[sid]
|
||||
if !ok {
|
||||
return nil
|
||||
|
@ -78,17 +38,13 @@ func (c *Context) As(sid SessionId) *Context {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Context) GetContext() *Context {
|
||||
return c
|
||||
}
|
||||
|
||||
// General type function to define actions, single component widgets
|
||||
// and components themselves.
|
||||
type Func func(*Context)
|
||||
func (f Func) Act(c *Context) {
|
||||
type Func func(Context)
|
||||
func (f Func) Act(c Context) {
|
||||
f(c)
|
||||
}
|
||||
func (f Func) Serve(c *Context) {
|
||||
func (f Func) Serve(c Context) {
|
||||
f(c)
|
||||
}
|
||||
func(f Func) Filter(_ *Update) bool {
|
||||
|
@ -108,13 +64,13 @@ const (
|
|||
)
|
||||
|
||||
// Goroutie function to handle each user.
|
||||
func (c *Context) serve() {
|
||||
func (c Context) serve() {
|
||||
beh := c.Bot.behaviour
|
||||
c.Run(beh.Init)
|
||||
beh.Root.Serve(c)
|
||||
}
|
||||
|
||||
func (c *Context) Path() Path {
|
||||
func (c Context) Path() Path {
|
||||
ln := len(c.pathHistory)
|
||||
if ln == 0 {
|
||||
return ""
|
||||
|
@ -122,11 +78,11 @@ func (c *Context) Path() Path {
|
|||
return c.pathHistory[ln-1]
|
||||
}
|
||||
|
||||
func (c *Context) Arg() any {
|
||||
func (c Context) Arg() any {
|
||||
return c.arg
|
||||
}
|
||||
|
||||
func (c *Context) Run(a Action) {
|
||||
func (c Context) Run(a Action) {
|
||||
if a != nil {
|
||||
a.Act(c)
|
||||
}
|
||||
|
@ -135,12 +91,12 @@ func (c *Context) Run(a Action) {
|
|||
// Only for the root widget usage.
|
||||
// Skip the update sending it down to
|
||||
// the underlying widget.
|
||||
func (c *Context) Skip(u *Update) {
|
||||
func (c Context) Skip(u Update) {
|
||||
c.skippedUpdates.Send(u)
|
||||
}
|
||||
|
||||
// Sends to the Sendable object.
|
||||
func (c *Context) Send(v Sendable) (*Message, error) {
|
||||
func (c Context) Send(v Sendable) (Message, error) {
|
||||
config := v.SendConfig(c.Session.Id, c.Bot)
|
||||
if config.Error != nil {
|
||||
return nil, config.Error
|
||||
|
@ -155,219 +111,79 @@ func (c *Context) Send(v Sendable) (*Message, error) {
|
|||
|
||||
// Sends the formatted with fmt.Sprintf message to the user
|
||||
// using default Markdown parsing format.
|
||||
func (c *Context) Sendf(format string, v ...any) (*Message, error) {
|
||||
func (c Context) Sendf(format string, v ...any) (Message, error) {
|
||||
return c.Send(NewMessage(format, v...))
|
||||
}
|
||||
|
||||
// Same as Sendf but uses Markdown 2 format for parsing.
|
||||
func (c *Context) Sendf2(format string, v ...any) (*Message, error) {
|
||||
func (c Context) Sendf2(format string, v ...any) (Message, error) {
|
||||
return c.Send(NewMessage(fmt.Sprintf(format, v...)).MD2())
|
||||
}
|
||||
|
||||
// Same as Sendf but uses HTML format for parsing.
|
||||
func (c *Context) SendfHTML(format string, v ...any) (*Message, error) {
|
||||
func (c Context) SendfHTML(format string, v ...any) (Message, error) {
|
||||
return c.Send(NewMessage(fmt.Sprintf(format, v...)).HTML())
|
||||
}
|
||||
|
||||
func (c *Context) SendfR(format string, v ...any) (*Message, error) {
|
||||
// Send the message in raw format escaping all the special characters.
|
||||
func (c Context) SendfR(format string, v ...any) (Message, error) {
|
||||
return c.Send(NewMessage(Escape2(fmt.Sprintf(format, v...))).MD2())
|
||||
}
|
||||
|
||||
// Get the input for current widget.
|
||||
// Should be used inside handlers (aka "Serve").
|
||||
func (c *Context) Input() chan *Update {
|
||||
func (c Context) Input() chan Update {
|
||||
return c.input.Chan()
|
||||
}
|
||||
|
||||
// Returns copy of current context so
|
||||
// it will not affect the current one.
|
||||
// But be careful because
|
||||
// most of the insides uses pointers
|
||||
// which are not deeply copied.
|
||||
func (c *Context) Copy() *Context {
|
||||
ret := *c
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *Context) WithArg(v any) *Context {
|
||||
c = c.Copy()
|
||||
func (c Context) WithArg(v any) Context {
|
||||
c.arg = v
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) WithUpdate(u *Update) *Context {
|
||||
c = c.Copy()
|
||||
func (c Context) WithUpdate(u *Update) Context {
|
||||
c.Update = u
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) WithInput(input *UpdateChan) *Context {
|
||||
c = c.Copy()
|
||||
func (c Context) WithInput(input *UpdateChan) Context {
|
||||
c.input = input
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Context) Go(pth Path) error {
|
||||
return c.session.go_(pth, nil)
|
||||
}
|
||||
|
||||
func (c Context) GoWithArg(pth Path, arg any) error {
|
||||
return c.session.go_(pth, arg)
|
||||
}
|
||||
|
||||
// Customized actions for the bot.
|
||||
type Action interface {
|
||||
Act(*Context)
|
||||
Act(Context)
|
||||
}
|
||||
|
||||
type ActionFunc func(*Context)
|
||||
type ActionFunc func(Context)
|
||||
|
||||
func (af ActionFunc) Act(c *Context) {
|
||||
af(c)
|
||||
}
|
||||
|
||||
func (c *Context) History() []Path {
|
||||
return c.pathHistory
|
||||
func (c Context) History() []Path {
|
||||
return c.session.pathHistory
|
||||
}
|
||||
|
||||
// Changes screen of user to the Id one.
|
||||
func (c *Context) Go(pth Path, args ...any) error {
|
||||
var err error
|
||||
if pth == "" {
|
||||
c.pathHistory = []Path{}
|
||||
return nil
|
||||
}
|
||||
var back bool
|
||||
if pth == "-" {
|
||||
if len(c.pathHistory) < 2 {
|
||||
return c.Go("")
|
||||
}
|
||||
pth = c.pathHistory[len(c.pathHistory)-2]
|
||||
c.pathHistory = c.pathHistory[:len(c.pathHistory)-1]
|
||||
}
|
||||
// Getting the screen and changing to
|
||||
// then executing its widget.
|
||||
if !pth.IsAbs() {
|
||||
pth = (c.Path() + "/" + pth).Clean()
|
||||
}
|
||||
|
||||
if !c.PathExist(pth) {
|
||||
return ScreenNotExistErr
|
||||
}
|
||||
|
||||
if !back && c.Path() != pth {
|
||||
c.pathHistory = append(c.pathHistory, pth)
|
||||
}
|
||||
|
||||
// Stopping the current widget.
|
||||
screen := c.Bot.behaviour.Screens[pth]
|
||||
c.skippedUpdates.Close()
|
||||
if screen.Widget != nil {
|
||||
c.skippedUpdates, err = c.RunWidget(screen.Widget, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return NoWidgetForScreenErr
|
||||
}
|
||||
|
||||
return nil
|
||||
func (c Context) PathExist(pth Path) bool {
|
||||
return c.bot.behaviour.PathExist(pth)
|
||||
}
|
||||
|
||||
func (c *Context) PathExist(pth Path) bool {
|
||||
return c.Bot.behaviour.PathExist(pth)
|
||||
}
|
||||
|
||||
func (c *Context) makeArg(args []any) any {
|
||||
var arg any
|
||||
if len(args) == 1 {
|
||||
arg = args[0]
|
||||
} else if len(args) > 1 {
|
||||
arg = args
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func (c *Context) RunCompo(compo Component, args ...any) (*UpdateChan, error) {
|
||||
if compo == nil {
|
||||
return nil, nil
|
||||
}
|
||||
s, ok := compo.(Sendable)
|
||||
if ok {
|
||||
msg, err := c.Send(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.SetMessage(msg)
|
||||
}
|
||||
updates := NewUpdateChan()
|
||||
go func() {
|
||||
compo.Serve(
|
||||
c.WithInput(updates).
|
||||
WithArg(c.makeArg(args)),
|
||||
)
|
||||
// To let widgets finish themselves before
|
||||
// the channel is closed and close it by themselves.
|
||||
updates.Close()
|
||||
}()
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
// Run widget in background returning the new input channel for it.
|
||||
func (c *Context) RunWidget(widget Widget, args ...any) (*UpdateChan, error) {
|
||||
var err error
|
||||
if widget == nil {
|
||||
return nil, EmptyWidgetErr
|
||||
}
|
||||
|
||||
pth := c.Path()
|
||||
compos := widget.Render(c.WithArg(c.makeArg(args)))
|
||||
// Leave if changed path or components are empty.
|
||||
if compos == nil || pth != c.Path() {
|
||||
return nil, EmptyCompoErr
|
||||
}
|
||||
chns := make([]*UpdateChan, len(compos))
|
||||
for i, compo := range compos {
|
||||
chns[i], err = c.RunCompo(compo, args...)
|
||||
if err != nil {
|
||||
for _, chn := range chns {
|
||||
chn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ret := NewUpdateChan()
|
||||
go func() {
|
||||
ln := len(compos)
|
||||
UPDATE:
|
||||
for u := range ret.Chan() {
|
||||
if u == nil {
|
||||
break
|
||||
}
|
||||
cnt := 0
|
||||
for i, compo := range compos {
|
||||
chn := chns[i]
|
||||
if chn.Closed() {
|
||||
cnt++
|
||||
continue
|
||||
}
|
||||
if !compo.Filter(u) {
|
||||
chn.Send(u)
|
||||
continue UPDATE
|
||||
}
|
||||
}
|
||||
if cnt == ln {
|
||||
break
|
||||
}
|
||||
}
|
||||
ret.Close()
|
||||
for _, chn := range chns {
|
||||
chn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Simple way to read strings for widgets.
|
||||
func (c *Context) ReadString(pref string, args ...any) string {
|
||||
// Simple way to read strings for widgets with
|
||||
// the specified prompt.
|
||||
func (c Context) ReadString(promptf string, args ...any) string {
|
||||
var text string
|
||||
if pref != "" {
|
||||
c.Sendf(pref, args...)
|
||||
c.Sendf(promptf, args...)
|
||||
}
|
||||
for u := range c.Input() {
|
||||
if u == nil {
|
||||
|
@ -382,6 +198,10 @@ func (c *Context) ReadString(pref string, args ...any) string {
|
|||
return text
|
||||
}
|
||||
|
||||
func (c Context) Update() Update {
|
||||
return c.update
|
||||
}
|
||||
|
||||
// Returns the reader for specified file ID and path.
|
||||
func (c *Context) GetFile(fileId FileId) (io.ReadCloser, string, error) {
|
||||
file, err := c.Bot.Api.GetFile(tgbotapi.FileConfig{FileID:string(fileId)})
|
||||
|
|
6
file.go
6
file.go
|
@ -27,7 +27,7 @@ var (
|
|||
// The type implements the structure to easily send
|
||||
// files to the client.
|
||||
type File struct {
|
||||
*MessageCompo
|
||||
MessageCompo
|
||||
name string
|
||||
reader io.Reader
|
||||
upload bool
|
||||
|
@ -108,7 +108,7 @@ func (f *File) SendData() string {
|
|||
|
||||
func (f *File) SendConfig(
|
||||
sid SessionId, bot *Bot,
|
||||
) (*SendConfig) {
|
||||
) (SendConfig) {
|
||||
var config SendConfig
|
||||
cid := sid.ToApi()
|
||||
|
||||
|
@ -127,5 +127,5 @@ func (f *File) SendConfig(
|
|||
}
|
||||
|
||||
|
||||
return &config
|
||||
return config
|
||||
}
|
||||
|
|
22
go.go
Normal file
22
go.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package tg
|
||||
|
||||
func Go(pth Path) UI {
|
||||
return UI{
|
||||
GoWidget(pth),
|
||||
}
|
||||
}
|
||||
|
||||
type GoWidget string
|
||||
// Implementing the Server interface.
|
||||
func (widget GoWidget) Serve(c Context) {
|
||||
c.input.Close()
|
||||
c.Go(Path(widget))
|
||||
}
|
||||
|
||||
func (widget GoWidget) Render(c Context) UI {
|
||||
return UI{widget}
|
||||
}
|
||||
|
||||
func (widget GoWidget) Filter(u Update) bool {
|
||||
return true
|
||||
}
|
23
inline.go
23
inline.go
|
@ -4,13 +4,14 @@ 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).
|
||||
// The type represents keyboard to be emdedded
|
||||
// into the messages (inline in Telegram terms).
|
||||
type Inline struct {
|
||||
*Keyboard
|
||||
Keyboard
|
||||
}
|
||||
|
||||
// Convert the inline keyboard to markup for the tgbotapi.
|
||||
func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
|
||||
func (kbd Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
|
||||
rows := [][]tgbotapi.InlineKeyboardButton{}
|
||||
for _, row := range kbd.Rows {
|
||||
if row == nil {
|
||||
|
@ -31,14 +32,14 @@ func (kbd *Inline) ToApi() tgbotapi.InlineKeyboardMarkup {
|
|||
|
||||
// The type implements message with an inline keyboard.
|
||||
type InlineCompo struct {
|
||||
*MessageCompo
|
||||
*Inline
|
||||
MessageCompo
|
||||
Inline
|
||||
}
|
||||
|
||||
// Implementing the Sendable interface.
|
||||
func (compo *InlineCompo) SendConfig(
|
||||
sid SessionId, bot *Bot,
|
||||
) (*SendConfig) {
|
||||
) (SendConfig) {
|
||||
sendConfig := compo.MessageCompo.SendConfig(sid, bot)
|
||||
if len(compo.Inline.Rows) > 0 {
|
||||
sendConfig.Message.ReplyMarkup = compo.Inline.ToApi()
|
||||
|
@ -48,7 +49,9 @@ func (compo *InlineCompo) SendConfig(
|
|||
}
|
||||
|
||||
// Update the component on the client side.
|
||||
func (compo *InlineCompo) Update(c *Context) {
|
||||
// Requires exactly the pointer but not the value
|
||||
// cause it changes insides of the structure.
|
||||
func (compo *InlineCompo) Update(c Context) {
|
||||
if compo.Message != nil {
|
||||
var edit tgbotapi.Chattable
|
||||
markup := compo.Inline.ToApi()
|
||||
|
@ -74,7 +77,7 @@ func (compo *InlineCompo) Update(c *Context) {
|
|||
}
|
||||
|
||||
// Implementing the Filterer interface.
|
||||
func (compo *InlineCompo) Filter(u *Update) bool {
|
||||
func (compo InlineCompo) Filter(u Update) bool {
|
||||
if compo == nil || u.CallbackQuery == nil {
|
||||
return true
|
||||
}
|
||||
|
@ -88,13 +91,13 @@ func (compo *InlineCompo) Filter(u *Update) bool {
|
|||
}
|
||||
|
||||
// Implementing the Server interface.
|
||||
func (compo *InlineCompo) Serve(c *Context) {
|
||||
func (compo InlineCompo) Serve(c Context) {
|
||||
for u := range c.Input() {
|
||||
compo.OnOneUpdate(c, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (compo *InlineCompo) OnOneUpdate(c *Context, u *Update) {
|
||||
func (compo *InlineCompo) OnOneUpdate(c Context, u Update) {
|
||||
var act Action
|
||||
btns := compo.ButtonMap()
|
||||
cb := tgbotapi.NewCallback(
|
||||
|
|
25
keyboard.go
25
keyboard.go
|
@ -14,7 +14,7 @@ type Keyboard struct {
|
|||
}
|
||||
|
||||
// Returns the new keyboard with specified rows.
|
||||
func NewKeyboard(rows ...ButtonRow) *Keyboard {
|
||||
func NewKeyboard(rows ...ButtonRow) Keyboard {
|
||||
ret := &Keyboard{}
|
||||
for _, row := range rows {
|
||||
if row != nil && len(row) > 0 {
|
||||
|
@ -24,7 +24,7 @@ func NewKeyboard(rows ...ButtonRow) *Keyboard {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (kbd *Keyboard) RowNum() int {
|
||||
func (kbd Keyboard) RowNum() int {
|
||||
return len(kbd.Rows)
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ func (kbd *Keyboard) RemoveRow(i int) {
|
|||
}
|
||||
|
||||
// Adds a new button row to the current keyboard.
|
||||
func (kbd *Keyboard) Row(btns ...*Button) *Keyboard {
|
||||
func (kbd Keyboard) Row(btns ...Button) Keyboard {
|
||||
// For empty row. We do not need that.
|
||||
if len(btns) < 1 {
|
||||
return kbd
|
||||
|
@ -56,7 +56,7 @@ func (kbd *Keyboard) Row(btns ...*Button) *Keyboard {
|
|||
}
|
||||
|
||||
// Adds buttons as one column list.
|
||||
func (kbd *Keyboard) List(btns ...*Button) *Keyboard {
|
||||
func (kbd Keyboard) List(btns ...Button) Keyboard {
|
||||
for _, btn := range btns {
|
||||
if btn == nil {
|
||||
continue
|
||||
|
@ -68,18 +68,13 @@ func (kbd *Keyboard) List(btns ...*Button) *Keyboard {
|
|||
|
||||
// Set the default action when no button provides
|
||||
// key to the data we got.
|
||||
func (kbd *Keyboard) WithAction(a Action) *Keyboard {
|
||||
func (kbd Keyboard) WithAction(a Action) Keyboard {
|
||||
kbd.Action = a
|
||||
return kbd
|
||||
}
|
||||
|
||||
// Alias to WithAction but better typing when setting
|
||||
// a specific function
|
||||
func (kbd *Keyboard) ActionFunc(fn ActionFunc) *Keyboard {
|
||||
return kbd.WithAction(fn)
|
||||
}
|
||||
|
||||
// Returns the map of buttons.
|
||||
// Returns the map of buttons. Where the key
|
||||
// is button data and the value is Action.
|
||||
func (kbd Keyboard) ButtonMap() ButtonMap {
|
||||
if kbd.buttonMap == nil {
|
||||
kbd.buttonMap = kbd.MakeButtonMap()
|
||||
|
@ -101,14 +96,14 @@ func (kbd Keyboard) MakeButtonMap() ButtonMap {
|
|||
}
|
||||
|
||||
// Convert the keyboard to the more specific inline one.
|
||||
func (kbd *Keyboard) Inline() *Inline {
|
||||
ret := &Inline{}
|
||||
func (kbd Keyboard) Inline() Inline {
|
||||
ret := Inline{}
|
||||
ret.Keyboard = kbd
|
||||
return ret
|
||||
}
|
||||
|
||||
// Convert the keyboard to the more specific reply one.
|
||||
func (kbd *Keyboard) Reply() *Reply {
|
||||
func (kbd Keyboard) Reply() Reply {
|
||||
ret := &Reply{}
|
||||
ret.Keyboard = kbd
|
||||
// it is used more often than not once.
|
||||
|
|
55
message.go
55
message.go
|
@ -10,7 +10,7 @@ type Message = tgbotapi.Message
|
|||
|
||||
// Simple text message component type.
|
||||
type MessageCompo struct {
|
||||
Message *Message
|
||||
Message Message
|
||||
ParseMode string
|
||||
Text string
|
||||
}
|
||||
|
@ -26,14 +26,19 @@ func Escape2(str string) string {
|
|||
return string(escapeRe.ReplaceAll([]byte(str), []byte("\\$1")))
|
||||
}
|
||||
|
||||
func (compo *MessageCompo) Update(c *Context) {
|
||||
// Call the function after the message was sent.
|
||||
func (compo *MessageCompo) Update(c Context) error {
|
||||
edit := tgbotapi.NewEditMessageText(
|
||||
c.Session.Id.ToApi(),
|
||||
compo.Message.MessageID,
|
||||
compo.Text,
|
||||
)
|
||||
msg, _ := c.Bot.Api.Send(edit)
|
||||
compo.Message = &msg
|
||||
msg, err := c.bot.api.Send(edit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
compo.Message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (compo *MessageCompo) Delete(c *Context) {
|
||||
|
@ -44,59 +49,59 @@ func (compo *MessageCompo) Delete(c *Context) {
|
|||
|
||||
// Is only implemented to make it sendable and so we can put it
|
||||
// return of rendering functions.
|
||||
func (compo *MessageCompo) SetMessage(msg *Message) {
|
||||
func (compo *MessageCompo) SetMessage(msg Message) {
|
||||
compo.Message = msg
|
||||
}
|
||||
|
||||
// Return new message with the specified text.
|
||||
func NewMessage(format string, v ...any) *MessageCompo {
|
||||
ret := &MessageCompo{}
|
||||
func Messagef(format string, v ...any) MessageCompo {
|
||||
ret := MessageCompo{}
|
||||
ret.Text = fmt.Sprintf(format, v...)
|
||||
ret.ParseMode = tgbotapi.ModeMarkdown
|
||||
return ret
|
||||
}
|
||||
|
||||
// Return message with the specified parse mode.
|
||||
func (msg *MessageCompo) withParseMode(mode string) *MessageCompo {
|
||||
func (msg MessageCompo) withParseMode(mode string) MessageCompo {
|
||||
msg.ParseMode = mode
|
||||
return msg
|
||||
}
|
||||
|
||||
// Set the default Markdown parsing mode.
|
||||
func (msg *MessageCompo) MD() *MessageCompo {
|
||||
func (msg MessageCompo) MD() MessageCompo {
|
||||
return msg.withParseMode(tgbotapi.ModeMarkdown)
|
||||
}
|
||||
|
||||
// Set the Markdown 2 parsing mode.
|
||||
func (msg *MessageCompo) MD2() *MessageCompo {
|
||||
func (msg MessageCompo) MD2() MessageCompo {
|
||||
return msg.withParseMode(tgbotapi.ModeMarkdownV2)
|
||||
}
|
||||
|
||||
// Set the HTML parsing mode.
|
||||
func (msg *MessageCompo) HTML() *MessageCompo {
|
||||
func (msg MessageCompo) HTML() MessageCompo {
|
||||
return msg.withParseMode(tgbotapi.ModeHTML)
|
||||
}
|
||||
|
||||
// Transform the message component into one with reply keyboard.
|
||||
func (msg *MessageCompo) Inline(inline *Inline) *InlineCompo {
|
||||
return &InlineCompo{
|
||||
func (msg MessageCompo) Inline(inline Inline) InlineCompo {
|
||||
return InlineCompo{
|
||||
Inline: inline,
|
||||
MessageCompo: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the message component into one with reply keyboard.
|
||||
func (msg *MessageCompo) Reply(reply *Reply) *ReplyCompo {
|
||||
return &ReplyCompo{
|
||||
func (msg MessageCompo) Reply(reply Reply) ReplyCompo {
|
||||
return ReplyCompo{
|
||||
Reply: reply,
|
||||
MessageCompo: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the message component into the location one.
|
||||
func (msg *MessageCompo) Location(
|
||||
func (msg MessageCompo) Location(
|
||||
lat, long float64,
|
||||
) *LocationCompo {
|
||||
) LocationCompo {
|
||||
ret := &LocationCompo{
|
||||
MessageCompo: msg,
|
||||
Location: Location{
|
||||
|
@ -108,9 +113,9 @@ func (msg *MessageCompo) Location(
|
|||
}
|
||||
|
||||
// Implementing the Sendable interface.
|
||||
func (config *MessageCompo) SendConfig(
|
||||
func (config MessageCompo) SendConfig(
|
||||
sid SessionId, bot *Bot,
|
||||
) (*SendConfig) {
|
||||
) (SendConfig) {
|
||||
var (
|
||||
ret SendConfig
|
||||
text string
|
||||
|
@ -122,20 +127,18 @@ func (config *MessageCompo) SendConfig(
|
|||
text = config.Text
|
||||
}
|
||||
|
||||
//text = strings.ReplaceAll(text, "-", "\\-")
|
||||
|
||||
msg := tgbotapi.NewMessage(sid.ToApi(), text)
|
||||
ret.Message = &msg
|
||||
ret.Message.ParseMode = config.ParseMode
|
||||
msg.ParseMode = config.ParseMode
|
||||
ret.Chattable = msg
|
||||
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// Empty serving to use messages in rendering.
|
||||
func (compo *MessageCompo) Serve(c *Context) {}
|
||||
func (compo *MessageCompo) Serve(c Context) {}
|
||||
|
||||
// Filter that skips everything. Messages cannot do anything with updates.
|
||||
func (compo *MessageCompo) Filter(_ *Update) bool {
|
||||
func (compo *MessageCompo) Filter(_ Update) bool {
|
||||
// Skip everything
|
||||
return true
|
||||
}
|
||||
|
|
21
reply.go
21
reply.go
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
// The type represents reply keyboards.
|
||||
type Reply struct {
|
||||
*Keyboard
|
||||
Keyboard
|
||||
// If true will be removed after one press.
|
||||
OneTime bool
|
||||
// If true will remove the keyboard on send.
|
||||
|
@ -15,20 +15,20 @@ type Reply struct {
|
|||
|
||||
// Set if we should remove current keyboard on the user side
|
||||
// when sending the keyboard.
|
||||
func (kbd *Reply) WithRemove(remove bool) *Reply {
|
||||
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{
|
||||
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 {
|
||||
func (kbd Reply) ToApi() any {
|
||||
// Shades everything.
|
||||
if kbd.Remove {
|
||||
return tgbotapi.NewRemoveKeyboard(true)
|
||||
|
@ -58,20 +58,21 @@ func (kbd *Reply) ToApi() any {
|
|||
|
||||
// The type implements reply keyboard widget.
|
||||
type ReplyCompo struct {
|
||||
*MessageCompo
|
||||
*Reply
|
||||
MessageCompo
|
||||
Reply
|
||||
}
|
||||
|
||||
// Implementing the sendable interface.
|
||||
func (compo *ReplyCompo) SendConfig(
|
||||
func (compo ReplyCompo) SendConfig(
|
||||
sid SessionId, bot *Bot,
|
||||
) (*SendConfig) {
|
||||
) (SendConfig) {
|
||||
sendConfig := compo.MessageCompo.SendConfig(sid, bot)
|
||||
sendConfig.Message.ReplyMarkup = compo.Reply.ToApi()
|
||||
return sendConfig
|
||||
}
|
||||
|
||||
func (compo *ReplyCompo) Filter(
|
||||
// Implementing the Server interface.
|
||||
func (compo ReplyCompo) Filter(
|
||||
u *Update,
|
||||
) bool {
|
||||
if compo == nil || u.Message == nil {
|
||||
|
@ -93,7 +94,7 @@ func (compo *ReplyCompo) Filter(
|
|||
}
|
||||
|
||||
// Implementing the UI interface.
|
||||
func (compo *ReplyCompo) Serve(c *Context) {
|
||||
func (compo ReplyCompo) Serve(c *Context) {
|
||||
for u := range c.Input() {
|
||||
var btn *Button
|
||||
text := u.Message.Text
|
||||
|
|
30
send.go
30
send.go
|
@ -10,24 +10,14 @@ type MessageId int64
|
|||
// way to define what message will be
|
||||
// sent to the side of a user.
|
||||
type Sendable interface {
|
||||
SendConfig(SessionId, *Bot) (*SendConfig)
|
||||
SetMessage(*Message)
|
||||
}
|
||||
|
||||
type Errorer interface {
|
||||
Err() error
|
||||
SendConfig(SessionId, *Bot) (SendConfig)
|
||||
SetMessage(Message)
|
||||
}
|
||||
|
||||
// The type is used as an endpoint to send messages
|
||||
// via bot.Send .
|
||||
type SendConfig struct {
|
||||
// Message with text and keyboard.
|
||||
Message *tgbotapi.MessageConfig
|
||||
|
||||
// The image to be sent.
|
||||
Photo *tgbotapi.PhotoConfig
|
||||
Document *tgbotapi.DocumentConfig
|
||||
Location *tgbotapi.LocationConfig
|
||||
Chattable tgbotapi.Chattable
|
||||
Error error
|
||||
}
|
||||
|
||||
|
@ -35,17 +25,7 @@ type SendConfig struct {
|
|||
type MessageMap map[string] *Message
|
||||
|
||||
// Convert to the bot.Api.Send format.
|
||||
func (config *SendConfig) ToApi() tgbotapi.Chattable {
|
||||
switch {
|
||||
case config.Message != nil :
|
||||
return *(config.Message)
|
||||
case config.Photo != nil :
|
||||
return *(config.Photo)
|
||||
case config.Location != nil :
|
||||
return *(config.Location)
|
||||
case config.Document != nil :
|
||||
return *(config.Document)
|
||||
}
|
||||
return nil
|
||||
func (config SendConfig) ToApi() tgbotapi.Chattable {
|
||||
return config.Chattable
|
||||
}
|
||||
|
||||
|
|
147
session.go
147
session.go
|
@ -1,5 +1,16 @@
|
|||
package tg
|
||||
|
||||
// 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, scope SessionScope) *Session {
|
||||
ret := NewSession(sid, scope)
|
||||
sm[sid] = ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// The way to determine where the context is
|
||||
// related to.
|
||||
type SessionScope uint8
|
||||
|
@ -27,6 +38,11 @@ type Session struct {
|
|||
Scope SessionScope
|
||||
// Custom value for each user.
|
||||
Data any
|
||||
|
||||
bot *Bot
|
||||
pathHistory []Path
|
||||
skippedUpdates *UpdateChan
|
||||
updates *UpdateChan
|
||||
}
|
||||
|
||||
// Return new empty session with specified user ID.
|
||||
|
@ -37,14 +53,129 @@ func NewSession(id SessionId, scope SessionScope) *Session {
|
|||
}
|
||||
}
|
||||
|
||||
// The type represents map of sessions using
|
||||
// as key.
|
||||
type SessionMap map[SessionId]*Session
|
||||
// Changes screen of user to the Id one.
|
||||
func (s *Session) go_(pth Path, arg any) error {
|
||||
var err error
|
||||
if pth == "" {
|
||||
s.pathHistory = []Path{}
|
||||
return nil
|
||||
}
|
||||
var back bool
|
||||
if pth == "-" {
|
||||
if len(s.pathHistory) < 2 {
|
||||
return s.Go("")
|
||||
}
|
||||
pth = s.pathHistory[len(s.pathHistory)-2]
|
||||
s.pathHistory = s.pathHistory[:len(s.pathHistory)-1]
|
||||
}
|
||||
// Getting the screen and changing to
|
||||
// then executing its widget.
|
||||
if !pth.IsAbs() {
|
||||
pth = (s.Path() + "/" + pth).Clean()
|
||||
}
|
||||
|
||||
// Add new empty session by it's ID.
|
||||
func (sm SessionMap) Add(sid SessionId, scope SessionScope) *Session {
|
||||
ret := NewSession(sid, scope)
|
||||
sm[sid] = ret
|
||||
return ret
|
||||
if !s.PathExist(pth) {
|
||||
return ScreenNotExistErr
|
||||
}
|
||||
|
||||
if !back && s.Path() != pth {
|
||||
s.pathHistory = append(s.pathHistory, pth)
|
||||
}
|
||||
|
||||
// Stopping the current widget.
|
||||
screen := s.bot.behaviour.Screens[pth]
|
||||
s.skippedUpdates.Close()
|
||||
|
||||
if screen.Widget != nil {
|
||||
s.skippedUpdates, err = s.runWidget(screen.Widget, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return NoWidgetForScreenErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) runCompo(compo Component, arg any) (*UpdateChan, error) {
|
||||
if compo == nil {
|
||||
return nil, nil
|
||||
}
|
||||
s, ok := compo.(Sendable)
|
||||
if ok {
|
||||
msg, err := c.Send(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.SetMessage(msg)
|
||||
}
|
||||
updates := NewUpdateChan()
|
||||
go func() {
|
||||
compo.Serve(
|
||||
c.WithInput(updates).
|
||||
WithArg(arg),
|
||||
)
|
||||
// To let widgets finish themselves before
|
||||
// the channel is closed and close it by themselves.
|
||||
updates.Close()
|
||||
}()
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
// Run widget in background returning the new input channel for it.
|
||||
func (c *Context) runWidget(widget Widget, arg any) (*UpdateChan, error) {
|
||||
var err error
|
||||
if widget == nil {
|
||||
return nil, EmptyWidgetErr
|
||||
}
|
||||
|
||||
pth := c.Path()
|
||||
compos := widget.Render(c.WithArg(c.makeArg(args)))
|
||||
// Leave if changed path or components are empty.
|
||||
if compos == nil || pth != c.Path() {
|
||||
return nil, EmptyCompoErr
|
||||
}
|
||||
chns := make([]*UpdateChan, len(compos))
|
||||
for i, compo := range compos {
|
||||
chns[i], err = c.runCompo(compo, arg)
|
||||
if err != nil {
|
||||
for _, chn := range chns {
|
||||
chn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ret := NewUpdateChan()
|
||||
go func() {
|
||||
ln := len(compos)
|
||||
UPDATE:
|
||||
for u := range ret.Chan() {
|
||||
if u == nil {
|
||||
break
|
||||
}
|
||||
cnt := 0
|
||||
for i, compo := range compos {
|
||||
chn := chns[i]
|
||||
if chn.Closed() {
|
||||
cnt++
|
||||
continue
|
||||
}
|
||||
if !compo.Filter(u) {
|
||||
chn.Send(u)
|
||||
continue UPDATE
|
||||
}
|
||||
}
|
||||
if cnt == ln {
|
||||
break
|
||||
}
|
||||
}
|
||||
ret.Close()
|
||||
for _, chn := range chns {
|
||||
chn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
|
18
update.go
18
update.go
|
@ -5,13 +5,12 @@ import tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|||
type FileId string
|
||||
|
||||
type Update struct {
|
||||
*tgbotapi.Update
|
||||
c *Context
|
||||
tgbotapi.Update
|
||||
}
|
||||
|
||||
// The type represents general update channel.
|
||||
type UpdateChan struct {
|
||||
chn chan *Update
|
||||
chn chan Update
|
||||
}
|
||||
|
||||
// Return new update channel.
|
||||
|
@ -60,13 +59,13 @@ func (updates *UpdateChan) Close() {
|
|||
close(chn)
|
||||
}
|
||||
|
||||
func (u *Update) HasDocument() bool {
|
||||
func (u Update) HasDocument() bool {
|
||||
return u != nil &&
|
||||
u.Message != nil &&
|
||||
u.Message.Document != nil
|
||||
}
|
||||
|
||||
func (u *Update) DocumentId() FileId {
|
||||
func (u Update) DocumentId() FileId {
|
||||
return FileId(u.Update.Message.Document.FileID)
|
||||
}
|
||||
|
||||
|
@ -74,23 +73,24 @@ func (u *Update) DocumentName() string {
|
|||
return u.Message.Document.FileName
|
||||
}
|
||||
|
||||
func (u *Update) DocumentSize() int {
|
||||
func (u Update) DocumentSize() int {
|
||||
return u.Message.Document.FileSize
|
||||
}
|
||||
|
||||
func (u *Update) DocumentMimeType() string {
|
||||
func (u Update) DocumentMimeType() string {
|
||||
return u.Message.Document.MimeType
|
||||
}
|
||||
|
||||
func (u *Update) HasPhotos() bool {
|
||||
func (u Update) HasPhotos() bool {
|
||||
return u.Message != nil && u.Message.Photo != nil &&
|
||||
len(u.Message.Photo) != 0
|
||||
}
|
||||
|
||||
func (u *Update) PhotoIds() []FileId {
|
||||
func (u Update) PhotoIds() []FileId {
|
||||
ret := make([]FileId, len(u.Message.Photo))
|
||||
for i, photo := range u.Message.Photo {
|
||||
ret[i] = FileId(photo.FileID)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue