Алгоритм для чат бота с ветвлением
Коллеги, добрый день! Что-то впал в ступор. Не могу придумать алгоритм для чат бота телеграма. Суть бота, опрос с ветвлением. В зависимости от выбранного варианта ответа выдавать следующий вопрос. Сообщения от бота могут быть нескольких видов, а именно просто текстовые сообщения, сообщения с вариантами ответов и разные вариации на эту тему. Для сохранения стадии клиента, создал класс
public class Client
{
private int chatId;
private string phoneNumber;
private string name;
private int stage;
public int ChatId
{
get { return chatId; }
set {
chatId = value; }
}
public int Stage
{
get { return stage; }
set { stage = value; }
}
public string PhoneNumber
{
get { return phoneNumber; }
set => phoneNumber = value;
}
public string Name
{
get { return name; }
set { name = value; }
}
public void NextStage(int text)
{
Stage = text;
}
}
Создал интерфейс для сообщений:
public interface IMessage
{
string Message { get; set; }
int Stage { get; set; }
}
И унаследовал два типа сообщений:
Сообщения с вопросом:
public class Question : IMessage { public int Stage { get; set; } public string Message { get; set; } public List<Answer> Answers = new List<Answer>(); public Question() { Stage = -1; Message = null; } public Question(int stage, string message, Answer answer) { Stage = stage; Message = message; Answers.Add(answer); } public Question(int stage, string message) { Stage = stage; Message = message; } } public class Answer { public int IdQuestions { get; set; } public string RightAnswer { get; set; } public int NextStage { get; set; } public Answer(string rightAsnw, int nextStage) { RightAnswer = rightAsnw; NextStage = nextStage; } public Answer(int idQues, string rightAsnw, int nextStage) { IdQuestions = idQues; RightAnswer = rightAsnw; NextStage = nextStage; } } }Просто текст:
public class TextMessage : IMessage { public int Stage { get; set; } public string Message { get; set; } public int NextStage { get; set; } }
Засунул все это в БД с дополнительным полем TYPE, в зависимости от которого возвращается нужный тип IMessage.
Ну а далее делаю следующее:
public async Task<OkResult> Post([FromBody]Update update)
{
if (update == null) return Ok();
var message = update.Message;
client = new Client();
client.ChatId = (int)message.Chat.Id;
if (!Bot.Contains(client))
{
client.Stage = 0;
Bot.AddClient(client);
}
else
{
client = Bot.GetClient(client);
}
var botClient = await Bot.GetBotClientAsync();
Bot.AddCommand(client);
await ExecuteCommand( message, botClient);
return Ok();
}
А теперь про логику, как это должно работать. Если подряд идут несколько TextMessage, то они отправляются в чат до тех пор, пока не будет QuestionMessage, после чего выдается текст с вопросом и кнопками. Ну и по кругу может крутиться несколько раз. Проблема в том, что если выбираешь следующую на сообщение с выбором, то оно отправляется в чат повторно, чего быть не должно. Перепробовал уже всякие разные вариации метода ExecuteCommand. Последний вариант ниже.
private async Task ExecuteCommand( Message message, Telegram.Bot.TelegramBotClient botClient)
{
var command = Bot.Command;
if (command is TextMessageCommand)
{
await command.Execute(message, botClient, client);
client.Stage = command.NextStage(message, client);
Bot.UpdateClient(client);
Bot.AddCommand(client);
await ExecuteCommand( message, botClient);
}
else if(command is QuestionCommand)
{
await command.Execute(message, botClient, client);
if (command.Contains(message))
{
client.Stage = command.NextStage(message, client);
Bot.UpdateClient(client);
Bot.AddCommand(client);
if (command is TextMessageCommand)
{
message.Text = "12412512412"; //тут изменяю сообщение, чтоб не получился бесконечный цикл
await ExecuteCommand(message, botClient);
}
}
}
}
Помимо этих двух типов еще будут сообщения с картинками, карусели, а так же запрос контактных данных. И не хочется городить тьму IFELSEов. Понимаю, что где то свернул не туда.
upd: Пример: Имеется три обычных сообщения и 1 вопрос: Сообщения:
"Я первое сообщение", stage = 0; nextStage=1;
"Я второе сообщение", stage = 2; nextStage=3;
"Я третье сообщение", stage =3; nextStage=0;
Вопрос:
"К какому сообщению вы хотите отправиться?", stage=1;
Варианты ответов:
"К 1", nextStage=0;
"К 2", nextStage=2;
"к 3", nextStahe=3;
Что ожидается на выводе:
Я первое сообщение
К какому сообщению вы хотите отправиться? // ожидание выбора. Выбор к примеру 3;
Я третье сообщение
Я первое сообщение
К какому сообщению вы хотите отправиться? // если введен случайны символ, то повторный вопрос
К какому сообщению вы хотите отправиться? //
А у меня выходит следующее:
Я первое сообщение
К какому сообщению вы хотите отправиться? // ожидание выбора. Выбор к примеру 3;
К какому сообщению вы хотите отправиться? //Задваивается вопрос
Я третье сообщение
Я первое сообщение
К какому сообщению вы хотите отправиться? // если введен случайны символ, то повторный вопрос
К какому сообщению вы хотите отправиться? //
upd2: Переписал код для консоли, работает простым копипастом
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TestApp;
namespace TestApp
{
class Program
{
public static Client client;
static void Main(string[] args)
{
client = new Client();
client.Stage = 0;
Console.WriteLine("Input Message");
// MessageController1 controller = new MessageController1(); //Это два варианта, первый не рабочий,
MessageController2 controller = new MessageController2(); // второй рабочий, но странно.
while (true)
{
var message = Console.ReadLine();
controller.Start(message);
}
}
public class MessageController1
{
public void Start(string message)
{
Bot.AddCommand(client);
ExecuteCommand(message);
}
void ExecuteCommand(string message)
{
var command = Bot.Command;
if (command is TextMessageCommand)
{
command.Execute(message, client);
client.Stage = command.NextStage(message, client);
Bot.AddCommand(client);
ExecuteCommand(message);
}
else if (command is QuestionCommand)
{
command.Execute(message, client);
if (command.Contains(message))
{
client.Stage = command.NextStage(message, client);
Bot.AddCommand(client);
if (Bot.Command is TextMessageCommand)
{
message = "12412512412";
ExecuteCommand(message);
}
}
}
}
}
public class MessageController2
{
public void Start(string message)
{
Bot.AddCommand(client);
ExecuteCommand(message);
Bot.flag = true;
}
void ExecuteCommand(string message)
{
var command = Bot.Command;
if (command is TextMessageCommand)
{
command.Execute(message, client);
client.Stage = command.NextStage(message, client);
Bot.AddCommand(client);
ExecuteCommand(message);
}
else if (command is QuestionCommand)
{
if (Bot.flag == false || !command.Contains(message))
command.Execute(message, client);
if (command.Contains(message))
{
client.Stage = command.NextStage(message, client);
Bot.AddCommand(client);
if (Bot.Command is TextMessageCommand)
{
Bot.flag = true;
message = "12412512412";
ExecuteCommand(message);
}
}
}
}
}
public static class Bot
{
private static Dictionary<Type, int> typeDict = new Dictionary<Type, int>
{
{typeof(TextMessage),0},
{typeof(Question),1},
//{typeof(MyClass),2}
};
public static bool flag { get; set; }
public static Command Command { get; set; }
public static void AddCommand(Client person)
{
IMessage message = GetQuestion(person.Stage);
switch (typeDict[message.GetType()])
{
case 1: //Question - 1
{
Command = (new QuestionCommand((Question)message));
break;
}
case 0:
{
Command = new TextMessageCommand((TextMessage)message);
break;
}
default:
break;
}
}
static IMessage GetQuestion(int stage)
{
List<IMessage> IMessages = new List<IMessage> {
new TextMessage { Stage=0, Message="Stage 0", NextStage=1},
new TextMessage { Stage=1, Message="Stage 1", NextStage=2},
new Question {Stage=2,Message="Stage 2 Select stage: \n 0. \n 1. \n 3. \n 4. \n 5.", Answers= { new Answer("0",0), new Answer("1",1), new Answer ("3",3), new Answer("4", 4), new Answer("5", 5) } },
new TextMessage {Stage=3, Message="Stage 3", NextStage=4 },
new Question {Stage=4,Message="Stage 4 Select stage: \n 0. \n 2. \n 3. \n 5.", Answers= { new Answer("0",0), new Answer("2",2), new Answer ("3",3), new Answer("5", 5) } },
new TextMessage {Stage=5, Message="Stage 5", NextStage=0 },
};
var quest = from t in IMessages
where t.Stage == stage
select t;
return quest.ToList()[0];
}
}
public class Client
{
public int Stage { get; set; }
}
public abstract class Command
{
public abstract void Execute(string message, Client client);
public abstract bool Contains(string message);
public abstract int NextStage(string message, Client person);
public abstract string GetStageMessage(Client person);
}
public class QuestionCommand : Command
{
public QuestionCommand(Question q)
{
Quest = q;
}
public Question Quest { get; set; }
public override string GetStageMessage(Client person)
{
return Quest.Message;
}
public override int NextStage(string message, Client person)
{
var command = from t in Quest.Answers
where t.RightAnswer == message
select t;
return command.ToList()[0].NextStage;
}
public override bool Contains(string message)
{
var command = from t in Quest.Answers
where t.RightAnswer == message
select t;
if (command.ToList().Count != 0)
return message.Contains(command.ToList()[0].RightAnswer);
else return false;
}
public override void Execute(string message, Client client)
{
Console.WriteLine("Question message: {0}", Quest.Message);
}
}
public class TextMessageCommand : Command
{
public TextMessage TextMessage { get; set; }
public TextMessageCommand(TextMessage text)
{
TextMessage = text;
}
public override bool Contains(string message)
{
return true;
}
public override void Execute(string message, Client person)
{
Console.WriteLine("Simple TextMessage: {0}", TextMessage.Message);
}
public override string GetStageMessage(Client person)
{
return TextMessage.Message;
}
public override int NextStage(string message, Client person)
{
return TextMessage.NextStage;
}
}
public interface IMessage
{
string Message { get; set; }
int Stage { get; set; }
}
public class TextMessage : IMessage
{
public int Stage { get; set; }
public string Message { get; set; }
public int NextStage { get; set; }
}
public class Question : IMessage
{
public int Stage { get; set; }
public string Message { get; set; }
public List<Answer> Answers = new List<Answer>();
public Question()
{
Stage = -1;
Message = null;
}
public Question(int stage, string message, Answer answer)
{
Stage = stage;
Message = message;
Answers.Add(answer);
}
public Question(int stage, string message)
{
Stage = stage;
Message = message;
}
}
public class Answer
{
public int IdQuestions { get; set; }
public string RightAnswer { get; set; }
public int NextStage { get; set; }
public Answer(string rightAsnw, int nextStage)
{
RightAnswer = rightAsnw;
NextStage = nextStage;
}
public Answer(int idQues, string rightAsnw, int nextStage)
{
IdQuestions = idQues;
RightAnswer = rightAsnw;
NextStage = nextStage;
}
}
}
}
Ответы (1 шт):
В общем, сделал костыль, но вроде пока рабочий.
Завел переменную static bool flag в объявлении бота.
На самом деле не понял, почему так, но оно работает. Хотя понимаю, что какой-то странный костыль.
Дело было в проверке на !command.Contains(message).
И изменил код тут:
public async Task<OkResult> Post([FromBody]Update update)
{
if (update == null) return Ok();
var message = update.Message;
client = new Client();
client.ChatId = (int)message.Chat.Id;
if (!Bot.Contains(client))
{
client.Stage = 0;
Bot.AddClient(client);
}
else
{
client = Bot.GetClient(client);
}
var botClient = await Bot.GetBotClientAsync();
Bot.AddCommand(client);
await ExecuteCommand( message, botClient);
return Ok();
}
И тут:
private async Task ExecuteCommand( Message message, Telegram.Bot.TelegramBotClient botClient)
{
var command = Bot.Command;
if (command is TextMessageCommand)
{
await command.Execute(message, botClient, client);
client.Stage = command.NextStage(message, client);
Bot.UpdateClient(client);
Bot.AddCommand(client);
await ExecuteCommand( message, botClient);
}
else if(command is QuestionCommand)
{
if (!command.Contains(message))
await command.Execute(message, botClient, client);
if (command.Contains(message))
{
client.Stage = command.NextStage(message, client);
Bot.UpdateClient(client);
Bot.AddCommand(client);
if (command is TextMessageCommand)
{
message.Text = "12412512412"; //тут изменяю сообщение, чтоб не получился бесконечный цикл
await ExecuteCommand(message, botClient);
}
}
}
}