Stabilize scene session skeleton
Hide internal scene runtime methods Refresh docs, tests, and changelog for scenes
This commit is contained in:
@@ -5,7 +5,10 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SceneHandler handles a scene step, scene command, or fallback message.
|
||||
type SceneHandler[T any] func(ctx *SceneContext, db T) (SceneResult, error)
|
||||
|
||||
// Scene defines a multi-step conversational flow.
|
||||
type Scene[T any] struct {
|
||||
Name string
|
||||
Scope SceneScope
|
||||
@@ -17,6 +20,7 @@ type Scene[T any] struct {
|
||||
message SceneHandler[T]
|
||||
}
|
||||
|
||||
// NewScene creates a new scene with user-chat scope by default.
|
||||
func NewScene[T any](name string) *Scene[T] {
|
||||
return &Scene[T]{
|
||||
Name: name,
|
||||
@@ -27,10 +31,14 @@ func NewScene[T any](name string) *Scene[T] {
|
||||
message: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// SetScope changes how scene sessions are keyed and shared.
|
||||
func (s *Scene[T]) SetScope(scope SceneScope) *Scene[T] {
|
||||
s.Scope = scope
|
||||
return s
|
||||
}
|
||||
|
||||
// SetEntry sets the initial step entered by MsgContext.EnterScene.
|
||||
func (s *Scene[T]) SetEntry(step string) *Scene[T] {
|
||||
s.Entry = step
|
||||
return s
|
||||
@@ -40,14 +48,19 @@ func (s *Scene[T]) setPluginName(name string) *Scene[T] {
|
||||
return s
|
||||
}
|
||||
|
||||
// OnStep registers a handler for a named scene step.
|
||||
func (s *Scene[T]) OnStep(step string, handler SceneHandler[T]) *Scene[T] {
|
||||
s.steps[step] = handler
|
||||
return s
|
||||
}
|
||||
|
||||
// OnCommand registers a command handler active while the scene is running.
|
||||
func (s *Scene[T]) OnCommand(cmd string, handler SceneHandler[T]) *Scene[T] {
|
||||
s.commands[cmd] = handler
|
||||
return s
|
||||
}
|
||||
|
||||
// OnMessage registers a fallback handler used when no scene command or step matches.
|
||||
func (s *Scene[T]) OnMessage(handler SceneHandler[T]) *Scene[T] {
|
||||
s.message = handler
|
||||
return s
|
||||
@@ -77,39 +90,42 @@ func (s *Scene[T]) executeMessage(ctx *SceneContext, db T) (SceneResult, bool, e
|
||||
return result, true, err
|
||||
}
|
||||
|
||||
// SceneSession stores the active scene state for one session key.
|
||||
type SceneSession struct {
|
||||
Scene string
|
||||
Step string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// SetData sets the session data. It is thread-safe and can be used to store any arbitrary data as a byte slice.
|
||||
// SetData stores arbitrary opaque session data.
|
||||
func (s *SceneSession) SetData(data []byte) {
|
||||
s.Data = data
|
||||
}
|
||||
|
||||
// GetData retrieves the session data. It is thread-safe and returns the data as a byte slice.
|
||||
// GetData returns the raw session data payload.
|
||||
func (s *SceneSession) GetData() []byte {
|
||||
return s.Data
|
||||
}
|
||||
|
||||
// HasData reports whether the session has a non-empty data payload.
|
||||
func (s *SceneSession) HasData() bool {
|
||||
return len(s.Data) > 0
|
||||
}
|
||||
|
||||
// ClearData clears the session data. It is thread-safe and sets the data to nil.
|
||||
// ClearData removes any stored session data.
|
||||
func (s *SceneSession) ClearData() {
|
||||
s.Data = nil
|
||||
}
|
||||
|
||||
// BindData binds the session data to the provided struct.
|
||||
// BindData unmarshals the stored JSON payload into v.
|
||||
func (s *SceneSession) BindData(v any) error {
|
||||
if len(s.Data) == 0 {
|
||||
return nil // No data to bind, return nil error
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(s.Data, v)
|
||||
}
|
||||
|
||||
// SaveData saves the provided struct as JSON in the session data.
|
||||
// SaveData marshals v as JSON and stores it in the session.
|
||||
func (s *SceneSession) SaveData(v any) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
@@ -119,22 +135,27 @@ func (s *SceneSession) SaveData(v any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SessionStore persists scene sessions by key.
|
||||
type SessionStore interface {
|
||||
Get(key string) (SceneSession, error)
|
||||
Set(key string, session SceneSession) error
|
||||
Delete(key string) error
|
||||
}
|
||||
|
||||
// MemorySessionStore stores scene sessions in memory.
|
||||
type MemorySessionStore struct {
|
||||
store map[string]SceneSession
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMemorySessionStore creates an empty in-memory session store.
|
||||
func NewMemorySessionStore() *MemorySessionStore {
|
||||
return &MemorySessionStore{
|
||||
store: make(map[string]SceneSession),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the session stored under key, or the zero session when absent.
|
||||
func (s *MemorySessionStore) Get(key string) (SceneSession, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@@ -143,12 +164,16 @@ func (s *MemorySessionStore) Get(key string) (SceneSession, error) {
|
||||
}
|
||||
return SceneSession{}, nil
|
||||
}
|
||||
|
||||
// Set stores session under key.
|
||||
func (s *MemorySessionStore) Set(key string, session SceneSession) error {
|
||||
s.mu.Lock()
|
||||
s.store[key] = session
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the session stored under key.
|
||||
func (s *MemorySessionStore) Delete(key string) error {
|
||||
s.mu.Lock()
|
||||
delete(s.store, key)
|
||||
@@ -156,35 +181,45 @@ func (s *MemorySessionStore) Delete(key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SceneResult describes how scene execution should proceed after a handler returns.
|
||||
type SceneResult struct {
|
||||
Action SceneAction
|
||||
Next string
|
||||
}
|
||||
|
||||
// SceneAction controls how the bot updates scene state after a handler returns.
|
||||
type SceneAction int
|
||||
|
||||
const (
|
||||
// SceneActionStay keeps the current scene and step active.
|
||||
SceneActionStay SceneAction = iota
|
||||
// SceneActionNext moves the session to another named step.
|
||||
SceneActionNext
|
||||
// SceneActionExit removes the current scene session.
|
||||
SceneActionExit
|
||||
// SceneActionPass lets normal bot routing continue after the scene handler.
|
||||
SceneActionPass
|
||||
)
|
||||
|
||||
// SceneScope defines how scene sessions are keyed.
|
||||
type SceneScope int
|
||||
|
||||
const (
|
||||
// SceneScopeUser shares a scene across all chats for one user.
|
||||
SceneScopeUser SceneScope = iota
|
||||
// SceneScopeChat shares a scene across all users in one chat.
|
||||
SceneScopeChat
|
||||
// SceneScopeUserChat isolates a scene per user-chat pair.
|
||||
SceneScopeUserChat
|
||||
)
|
||||
|
||||
type sceneRuntime interface {
|
||||
FindScene(name string) (*sceneMeta, bool)
|
||||
GetSession(key string) (SceneSession, error)
|
||||
SetSession(key string, session SceneSession) error
|
||||
DeleteSession(key string) error
|
||||
BuildSceneKey(scope SceneScope, ctx *MsgContext) (string, bool)
|
||||
FindSceneSession(ctx *MsgContext) (string, SceneSession, error)
|
||||
findScene(name string) (*sceneMeta, bool)
|
||||
getSession(key string) (SceneSession, error)
|
||||
setSession(key string, session SceneSession) error
|
||||
deleteSession(key string) error
|
||||
buildSceneKey(scope SceneScope, ctx *MsgContext) (string, bool)
|
||||
findSceneSession(ctx *MsgContext) (string, SceneSession, error)
|
||||
}
|
||||
|
||||
type sceneMeta struct {
|
||||
|
||||
Reference in New Issue
Block a user