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 }