tg/session.go

181 lines
3.5 KiB
Go

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
const (
NoSessionScope SessionScope = iota
PrivateSessionScope
GroupSessionScope
ChannelSessionScope
)
// Represents unique value to identify chats.
// In fact is simply ID of the chat.
type SessionId int64
// Convert the SessionId to Telegram API's type.
func (si SessionId) ToApi() int64 {
return int64(si)
}
// The type represents current state of
// user interaction per each of them.
type Session struct {
// Id of the chat of the user.
Id SessionId
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.
func NewSession(id SessionId, scope SessionScope) *Session {
return &Session{
Id: id,
Scope: scope,
}
}
// 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()
}
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
}