diff --git a/conversation_handler.go b/conversation_handler.go new file mode 100644 index 0000000..0488218 --- /dev/null +++ b/conversation_handler.go @@ -0,0 +1,58 @@ +package bot + +import ( + "context" + "log" + + "github.com/go-telegram/bot/models" +) + +// ConversationHandler is a structure that manages conversation functions. +type ConversationHandler struct { + active bool // a flag indicating whether the conversation is active + currentStageId int // the identifier of the active conversation stage + stages map[int]HandlerFunc // a map of conversation stages +} + +// NewConversationHandler returns a new instance of ConversationHandler. +func NewConversationHandler() *ConversationHandler { + return &ConversationHandler{ + active: false, + currentStageId: 0, + stages: make(map[int]HandlerFunc), + } +} + +// AddStage adds a conversation stage to the ConversationHandler. +func (c *ConversationHandler) AddStage(stageId int, hf HandlerFunc) { + c.stages[stageId] = hf +} + +// SetActiveStage sets the active conversation stage. +// Invalid currentStageId is not checked because if the CallStage function encounters an invalid id, +// it will not process it, so the stageId is not checked. +// if stageId <= len(c.stages) +func (c *ConversationHandler) SetActiveStage(stageId int) { + if !c.active { + c.active = true + } + + c.currentStageId = stageId +} + +// CallStage calls the function of the active conversation stage. +func (c *ConversationHandler) CallStage(ctx context.Context, b *Bot, update *models.Update) { + if c.active { + // hf = HandlerFunction + if hf, ok := c.stages[c.currentStageId]; ok { + hf(ctx, b, update) + } else { + log.Println("Error: Invalid stage id. No matching function found for the current stage id.") + } + } +} + +// End ends the conversation. +func (c *ConversationHandler) End() { + c.active = false +} diff --git a/conversation_handler_test.go b/conversation_handler_test.go new file mode 100644 index 0000000..3f6f11e --- /dev/null +++ b/conversation_handler_test.go @@ -0,0 +1,88 @@ +package bot + +import ( + "context" + "testing" + + "github.com/go-telegram/bot/models" +) + +func Test_NewConversationHandler(t *testing.T) { + convHandler := NewConversationHandler() + + // Check if conversation handler is initialized properly + if convHandler == nil { + t.Error("Failed to create a new conversation handler") + return + } + + // Check if conversation handler's stages map is initialized properly + if convHandler.stages == nil { + t.Error("Conversation handler's stages map is not initialized") + } +} + +func Test_ConversationHandler_AddStage(t *testing.T) { + convHandler := NewConversationHandler() + + // Add a stage to the conversation handler + convHandler.AddStage(1, func(ctx context.Context, b *Bot, update *models.Update) {}) + + // Check if the stage is added correctly + if _, ok := convHandler.stages[1]; !ok { + t.Error("Failed to add stage to the conversation handler") + } +} + +func Test_ConversationHandler_SetActiveStage(t *testing.T) { + convHandler := NewConversationHandler() + + // Set active stage to 1 + convHandler.SetActiveStage(1) + + // Check if active stage is set correctly + if convHandler.currentStageId != 1 { + t.Error("Failed to set active stage") + } +} + +func Test_ConversationHandler_CallStage(t *testing.T) { + convHandler := NewConversationHandler() + + var state int + + handlerFunction := func(ctx context.Context, b *Bot, update *models.Update) { + state = 1 + } + + // Add a stage to the conversation handler + convHandler.AddStage(1, handlerFunction) + + // Set active stage to 1 + convHandler.SetActiveStage(1) + + // Create a dummy context, bot, and update + ctx := context.Background() + b := &Bot{} + update := &models.Update{} + + // Call the active stage function + convHandler.CallStage(ctx, b, update) + + // Check if the function of the active stage is called + if state != 1 { + t.Error("State is not updated as expected.") + } +} + +func Test_ConversationHandler_End(t *testing.T) { + convHandler := NewConversationHandler() + + // End the conversation + convHandler.End() + + // Check if conversation is ended + if convHandler.active { + t.Error("Conversation should be ended, but it's still active") + } +} diff --git a/examples/conversation_handler/main.go b/examples/conversation_handler/main.go new file mode 100644 index 0000000..dd05c2a --- /dev/null +++ b/examples/conversation_handler/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "os" + "os/signal" + + "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" +) + +const ( + firstNameStage = iota // Definition of the first name stage = 0 + lastNameStage // Definition of the last name stage = 1 +) + +var convHandler *bot.ConversationHandler + +var firstName string // Global variable to store user's first name + +func main() { + // Create a context to terminate the program when a signal is received + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + // Bot options + opts := []bot.Option{ + bot.WithDefaultHandler(defaultHandler), + } + + // Create the bot + b, err := bot.New("EXAMPLE_TELEGRAM_BOT_TOKEN", opts...) + if err != nil { + panic(err) + } + + // Register commands for the bot + b.RegisterHandler(bot.HandlerTypeMessageText, "/start", bot.MatchTypeExact, start) + b.RegisterHandler(bot.HandlerTypeMessageText, "/cancel", bot.MatchTypeExact, cancelConversation) + + // Create a conversation handler and add stages + convHandler = bot.NewConversationHandler() + convHandler.AddStage(firstNameStage, firstNameHandler) // Add first name stage + convHandler.AddStage(lastNameStage, lastNameHandler) // Add last name stage + + //start the bot + b.Start(ctx) +} + +// Default handler calls the CallStage function of conversation handler +func defaultHandler(ctx context.Context, b *bot.Bot, update *models.Update) { + convHandler.CallStage(ctx, b, update) +} + +// Handle /start command to start getting the user's name +func start(ctx context.Context, b *bot.Bot, update *models.Update) { + convHandler.SetActiveStage(firstNameStage) //start the first name stage + + // Ask user to enter their name + b.SendMessage(ctx, &bot.SendMessageParams{ + ChatID: update.Message.Chat.ID, + Text: "enter your name or use the /cancel command to cancel.", + }) +} + +// Handle the first name stage to get the user's first name +func firstNameHandler(ctx context.Context, b *bot.Bot, update *models.Update) { + firstName = update.Message.Text + + convHandler.SetActiveStage(lastNameStage) //change stage to last name stage + + // Ask user to enter their last name + b.SendMessage(ctx, &bot.SendMessageParams{ + ChatID: update.Message.Chat.ID, + Text: "enter your last name", + }) +} + +// Handle the last name stage to get the user's last name and send a hello message +func lastNameHandler(ctx context.Context, b *bot.Bot, update *models.Update) { + lastName := update.Message.Text + + convHandler.End() // end the conversation + + b.SendMessage(ctx, &bot.SendMessageParams{ + ChatID: update.Message.Chat.ID, + Text: "Hello, " + firstName + " " + lastName + " :)", + }) +} + +// Handle /cancel command to end the conversation +func cancelConversation(ctx context.Context, b *bot.Bot, update *models.Update) { + convHandler.End() // end the conversation + + // Send a message to indicate the conversation has been cancelled + b.SendMessage(ctx, &bot.SendMessageParams{ + ChatID: update.Message.Chat.ID, + Text: "conversation cancelled", + }) +}