Documentation
¶
Overview ¶
Package questionaire provides an interactive questionnaire system for Telegram bots.
This package allows you to create multi-step questionnaires with support for text input, single-choice (radio button), and multiple-choice (checkbox) questions. It includes features like answer validation, edit functionality, and clean ButtonGrid layouts.
Basic usage:
manager := questionaire.NewManager() q := questionaire.NewBuilder(chatID, manager). SetOnDoneHandler(handleResults). AddQuestion("name", "What's your name?", nil, validateName). AddQuestion("age", "Select age group:", ageChoices, nil) q.Show(ctx, bot, chatID)
The package integrates with the button package for creating organized choice layouts using the ButtonGrid builder pattern.
Index ¶
- Constants
- func GetResultByte(q *Questionaire) ([]byte, error)
- type Manager
- type Question
- func (q *Question) AddChoiceSelected(answer string)
- func (q *Question) GetDisplayAnswer() string
- func (q *Question) GetSelectedChoices() [][]button.Button
- func (q *Question) GetUnselectedChoices() [][]button.Button
- func (q *Question) IsSelected(choice string) bool
- func (q *Question) SetAnswer(answer string)
- func (q *Question) SetMsgID(msgID int)
- func (q *Question) Validate(answer string) error
- type QuestionFormat
- type Questionaire
- func (q *Questionaire) AddMultipleAnswerQuestion(key string, text string, choices [][]button.Button, ...) *Questionaire
- func (q *Questionaire) AddQuestion(key string, text string, choices [][]button.Button, ...) *Questionaire
- func (q *Questionaire) Answer(ctx context.Context, answer string, b *bot.Bot, chatID any) bool
- func (q *Questionaire) Done(ctx context.Context, b *bot.Bot, update *models.Update)
- func (q *Questionaire) GetAnswers() map[string]interface{}
- func (q *Questionaire) GetQuestionIndex(question *Question) int
- func (q *Questionaire) SetAllowEditAnswers(allow bool) *Questionaire
- func (q *Questionaire) SetContext(ctx context.Context) *Questionaire
- func (q *Questionaire) SetInitialData(data map[string]interface{}) *Questionaire
- func (q *Questionaire) SetManager(m *Manager) *Questionaire
- func (q *Questionaire) SetOnCancelHandler(handler func()) *Questionaire
- func (q *Questionaire) SetOnDoneHandler(handler onDoneHandlerFunc) *Questionaire
- func (q *Questionaire) Show(ctx context.Context, b *bot.Bot, chatID any)
Constants ¶
const ( // RadioUnselected is the symbol displayed for unselected radio button options. RadioUnselected = "⚪" // RadioSelected is the symbol displayed for the selected radio button option. RadioSelected = "🔘" // CheckUnselected is the symbol displayed for unselected checkbox options. CheckUnselected = "☑️" // CheckSelected is the symbol displayed for selected checkbox options. CheckSelected = "✅" // EditButtonText is the text displayed on edit buttons for answered questions. EditButtonText = "◀️ Edit" // DoneButtonText is the text displayed on the done button for checkbox questions. DoneButtonText = "✅ Done" // CancelButtonText is the text displayed on cancel buttons. CancelButtonText = "❌ Cancel" )
UI constants for consistent button symbols and text across the questionnaire interface. These can be customized by modifying the constants if different symbols are preferred.
const (
QUESTION_FORMAT = "✒️[%d/%d] %s"
)
Variables ¶
This section is empty.
Functions ¶
func GetResultByte ¶
func GetResultByte(q *Questionaire) ([]byte, error)
GetResultByte marshals the questionnaire answers to JSON bytes.
This utility function serializes the complete answers map (including InitialData) to JSON format, making it suitable for logging, storage, or API transmission.
Returns:
- []byte: JSON representation of the answers map
- error: JSON marshaling error, if any
The JSON structure matches the answers map returned by GetAnswers():
- Text/Radio answers: {"key": "value"}
- Checkbox answers: {"key": ["option1", "option2"]}
- Initial data: {"key": "value"}
Example usage:
jsonData, err := questionaire.GetResultByte(q) if err != nil { log.Printf("Failed to serialize answers: %v", err) return } log.Printf("Survey results: %s", string(jsonData))
Types ¶
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles thread-safe operations for questionnaire conversations. It manages active questionnaire sessions across multiple chats and routes incoming text messages to the appropriate questionnaire instance.
A Manager is required for questionnaires that include text input questions, as it handles the routing of user text replies to the correct questionnaire.
func NewManager ¶
func NewManager() *Manager
NewManager creates a new Manager instance for handling questionnaire sessions. Typically, you create one Manager when your bot starts and register its HandleMessage method with your bot to process text messages.
Example:
manager := questionaire.NewManager() bot.RegisterHandler(bot.HandlerTypeMessageText, "", bot.MatchTypePrefix, manager.HandleMessage)
func (*Manager) Add ¶
func (m *Manager) Add(chatID int64, q *Questionaire)
Add stores a questionnaire conversation for the given chat ID. This is called automatically when a questionnaire with a manager is shown. Multiple questionnaires can be active simultaneously in different chats.
func (*Manager) Exists ¶
Exists checks if a questionnaire conversation exists for the given chat ID. Returns true if an active questionnaire is running in the specified chat. This method is thread-safe and can be called concurrently.
func (*Manager) Get ¶
func (m *Manager) Get(chatID int64) *Questionaire
Get retrieves the questionnaire conversation for the given chat ID. Returns nil if no active questionnaire exists for the chat. This method is thread-safe and can be called concurrently.
func (*Manager) HandleMessage ¶
HandleMessage processes incoming text messages for active questionnaire conversations. This method should be registered with your bot to handle text message updates:
bot.RegisterHandler(bot.HandlerTypeMessageText, "", bot.MatchTypePrefix, manager.HandleMessage)
The method:
- Checks if an active questionnaire exists for the message's chat ID
- Routes the message text to the appropriate questionnaire's Answer method
- Handles questionnaire completion and cleanup automatically
- Ignores messages from chats without active questionnaires
This enables seamless text input handling for questionnaire text questions without requiring manual message routing in your application code.
type Question ¶
type Question struct { // Key is the unique identifier for this question used in the answers map Key string // Text is the question text displayed to the user Text string // Choices contains the button layout for radio/checkbox questions (created with ButtonGrid) Choices [][]button.Button // ChoicesSelected stores selected callback data for checkbox questions ChoicesSelected []string // Answer stores the user's response (text input or selected callback data) Answer string // QuestionFormat determines the type of question (text, radio, or checkbox) QuestionFormat QuestionFormat // MsgID stores the Telegram message ID of the question message for editing MsgID int // contains filtered or unexported fields }
Question represents a single question within a questionnaire. It contains the question text, possible choices (for radio/checkbox questions), user's answer, validation function, and formatting information.
func (*Question) AddChoiceSelected ¶
AddChoiceSelected appends a selected choice to the ChoicesSelected slice.
func (*Question) GetDisplayAnswer ¶
GetDisplayAnswer returns a user-friendly display string for the question's answer.
The returned string is suitable for display in the Telegram chat:
- Text questions: Returns the user's text input or "Not answered"
- Radio questions: Returns the display text of the selected button or "Not selected"
- Checkbox questions: Returns the first selection plus count (e.g., "Tech + 2 more") or "None selected"
This method converts internal callback data back to human-readable text, making it perfect for edit buttons and result summaries.
func (*Question) GetSelectedChoices ¶
func (*Question) GetUnselectedChoices ¶
func (*Question) IsSelected ¶
func (*Question) SetAnswer ¶
SetAnswer sets the answer for the question. This method is used internally during answer processing. For most use cases, answers are set automatically through user interaction.
type QuestionFormat ¶
type QuestionFormat int
QuestionFormat defines the type of input expected for a question.
const ( // QuestionFormatText indicates a free-text input question where users type their response. QuestionFormatText QuestionFormat = 0 // QuestionFormatRadio indicates a single-choice question with radio button style selection. QuestionFormatRadio QuestionFormat = 1 // QuestionFormatCheck indicates a multiple-choice question with checkbox style selection. QuestionFormatCheck QuestionFormat = 2 )
type Questionaire ¶
type Questionaire struct { // InitialData contains pre-filled data that will be included in final answers InitialData map[string]interface{} // contains filtered or unexported fields }
Questionaire represents an interactive questionnaire session for a specific chat. It manages a sequence of questions, tracks user progress, and handles answer collection.
Use NewBuilder() to create a new questionnaire instance rather than constructing this struct directly.
func NewBuilder ¶
func NewBuilder(chatID any, manager *Manager) *Questionaire
NewBuilder creates a new Questionaire instance for the specified chat.
Parameters:
- chatID: The Telegram chat ID where this questionnaire will run
- manager: Optional Manager instance to handle text message routing (can be nil)
Returns a new Questionaire instance that can be configured using the builder pattern.
Example:
manager := questionaire.NewManager() q := questionaire.NewBuilder(chatID, manager). SetOnDoneHandler(handleCompletion). AddQuestion("name", "What's your name?", nil, validateName)
func (*Questionaire) AddMultipleAnswerQuestion ¶
func (q *Questionaire) AddMultipleAnswerQuestion(key string, text string, choices [][]button.Button, validateFunc func(answer string) error) *Questionaire
AddMultipleAnswerQuestion adds a checkbox-style question that allows multiple selections.
Parameters:
- key: Unique identifier for this question (used in the final answers map)
- text: The question text shown to the user
- choices: Button layout created with ButtonGrid (e.g., button.NewBuilder().Row().Choice("Option1").Build())
- validateFunc: Optional validation function (usually nil for checkbox questions)
The user can select multiple options and click "Done" to proceed. The answer will be stored as a []string in the final results.
Example:
interests := button.NewBuilder(). Row().ChoiceWithData("Tech", "tech").ChoiceWithData("Sports", "sports"). Row().ChoiceWithData("Music", "music").ChoiceWithData("Travel", "travel").Build() q.AddMultipleAnswerQuestion("interests", "Select your interests:", interests, nil)
func (*Questionaire) AddQuestion ¶
func (q *Questionaire) AddQuestion(key string, text string, choices [][]button.Button, validateFunc func(answer string) error) *Questionaire
AddQuestion adds a text input or single-choice (radio) question to the questionnaire.
Parameters:
- key: Unique identifier for this question (used in the final answers map)
- text: The question text shown to the user
- choices: Button layout for radio questions (nil for text input)
- validateFunc: Optional validation function for user input
Question type is determined automatically:
- If choices is nil: Creates a text input question
- If choices is provided: Creates a radio button question (single selection)
Examples:
// Text input question q.AddQuestion("name", "What's your name?", nil, validateNonEmpty) // Radio button question ageChoices := button.NewBuilder(). SingleChoiceWithData("Under 18", "age_under_18"). SingleChoiceWithData("18-30", "age_18_30").Build() q.AddQuestion("age_group", "Select your age group:", ageChoices, nil)
func (*Questionaire) Answer ¶
Answer processes the user's answer for the current question and advances the questionnaire if appropriate. Returns true if all questions have been answered.
func (*Questionaire) Done ¶
Done is called internally when all questions have been answered. It marshals the answers to JSON and calls the onDoneHandler. This method is typically not called directly by user code.
func (*Questionaire) GetAnswers ¶
func (q *Questionaire) GetAnswers() map[string]interface{}
GetAnswers returns a map of question keys to their answers or selected choices. For text and radio questions, the value is a string. For checkbox questions, the value is a slice of strings ([]string). The returned map also includes any InitialData that was set on the questionnaire.
func (*Questionaire) GetQuestionIndex ¶
func (q *Questionaire) GetQuestionIndex(question *Question) int
func (*Questionaire) SetAllowEditAnswers ¶
func (q *Questionaire) SetAllowEditAnswers(allow bool) *Questionaire
SetAllowEditAnswers controls whether answered questions can be edited by the user. When set to false, answered questions will not show edit buttons and users cannot go back to modify their responses. When set to true (default), users can click edit buttons to modify previous answers.
Parameters:
- allow: true to enable editing (default), false to disable editing of answered questions
Example:
// Create a questionnaire without edit functionality q := questionaire.NewBuilder(chatID, manager). SetAllowEditAnswers(false). AddQuestion("name", "What's your name?", nil, nil)
func (*Questionaire) SetContext ¶
func (q *Questionaire) SetContext(ctx context.Context) *Questionaire
SetContext sets the context for the questionnaire and returns the updated instance. This context will be merged with contexts passed to handler functions. Useful for passing request-scoped data or implementing timeouts.
func (*Questionaire) SetInitialData ¶
func (q *Questionaire) SetInitialData(data map[string]interface{}) *Questionaire
SetInitialData sets pre-filled data for the questionnaire and returns the updated instance. This data will be included in the final answers map alongside user responses. Useful for including metadata like user ID, campaign source, etc.
func (*Questionaire) SetManager ¶
func (q *Questionaire) SetManager(m *Manager) *Questionaire
SetManager sets or updates the manager for this questionnaire and returns the updated instance. The manager is responsible for routing text messages to active questionnaires. If you're using text questions, you need a manager and must register its HandleMessage method with your bot.
func (*Questionaire) SetOnCancelHandler ¶
func (q *Questionaire) SetOnCancelHandler(handler func()) *Questionaire
SetOnCancelHandler sets the cancellation handler called when the user cancels the questionnaire. The handler typically sends a cancellation message to the user. Questionnaire cleanup (message deletion) is handled automatically.
func (*Questionaire) SetOnDoneHandler ¶
func (q *Questionaire) SetOnDoneHandler(handler onDoneHandlerFunc) *Questionaire
SetOnDoneHandler sets the completion handler called when all questions have been answered.
The handler receives:
- ctx: Context (merged with questionnaire context if set)
- b: Bot instance
- chatID: Chat ID where questionnaire is running
- answers: Map of question keys to user answers
Example:
q.SetOnDoneHandler(func(ctx context.Context, b *bot.Bot, chatID any, answers map[string]interface{}) error { fmt.Printf("Survey completed: %+v\n", answers) return b.SendMessage(ctx, &bot.SendMessageParams{ ChatID: chatID, Text: "Thank you for completing the survey!", }) })
func (*Questionaire) Show ¶
Show starts the questionnaire by displaying the first (or current) question to the user.
This method:
- Registers the questionnaire with the manager (if available) for text message handling
- Sends the current question with appropriate keyboard layout
- Sets up callback handlers for button interactions
- Handles error display if validation failed on previous attempts
Parameters:
- ctx: Context for the operation
- b: Bot instance for sending messages
- chatID: Telegram chat ID where to display the question
The method automatically determines the question type and creates the appropriate interface:
- Text questions: Simple message requesting text input
- Radio questions: Message with inline keyboard buttons
- Checkbox questions: Message with selectable buttons and "Done" option
Example:
q := questionaire.NewBuilder(chatID, manager). AddQuestion("name", "What's your name?", nil, nil). SetOnDoneHandler(handleResults) q.Show(ctx, bot, chatID)