【问题标题】:Node Telegram bot api, make chain conversation between userNode Telegram bot api,在用户之间进行链式对话
【发布时间】:2016-05-05 06:13:17
【问题描述】:

我已经开始使用这个module为电报编写机器人逻辑

我可以像这样创建简单的问答逻辑:

bot.onText(/\/start/, function(msg, match){
  bot.sendMessage(msg.chat.id, "Hello this is great bot");
});

当用户输入/start时,他会收到这条消息。

我想在机器人和用户之间创建类似链式对话的东西。就像当用户输入/buy 时,机器人会显示购买选项,然后用户输入他想购买的东西,然后机器人会显示所选产品的类型等等。

如何在用户和机器人之间创建链式对话?如何让机器人记住之前选择的命令并在需要时重置它们?我是否需要保存在自己的数据库中才能做到这一点?

【问题讨论】:

    标签: javascript node.js telegram telegram-bot


    【解决方案1】:

    我也遇到了这个问题,我需要我的机器人根据他对用户的最后回答来回答问题,因为很难找到可以引导我找到解决方案的想法(在 Java),为了未来的 Java googlers,我将在这里分享我的。我将 telegrambots library 与 Spring Boot/Data 一起使用。

    正如 Loki 所指出的,实现此流程的最佳方式是在数据库中保存状态。为此,使用消息的唯一聊天 ID 将聊天与另一个聊天区分开来。

    这是 Java 实现的相关部分(逻辑几乎适用于任何语言):

    持有与系统用户相关的 Telegram 聊天信息的实体。

    @Entity
    @Table(name = "user_bot")
    public class UserBot implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
    
        @Column(name = "chat_id", unique = true, nullable = false, length = 255)
        private String chatId;
    
        @Column(name = "bot_verification_code", length = 6)
        private String botVerificationCode;
    
        @Enumerated
        @Column(name = "last_bot_state", columnDefinition = "SMALLINT DEFAULT NULL")
        private BotState lastBotState;
    
        @Column(columnDefinition = "TINYINT(1)")
        private boolean verified;
    
        @JoinColumn(name = "user_id", referencedColumnName = "id")
        @ManyToOne(fetch = FetchType.EAGER)
        private User user;
    }
    

    代表所有可能的机器人响应(状态)的枚举。

    public enum BotState {
        // Respostas do bot que representam estados
        AUTH_STEP_1("Muito bem. Qual é o seu e-mail no sistema?"), AUTH_STEP_2("Enviei um código para o seu e-mail. Por favor, digite-o aqui."),
        NO_STATE("");
    
        private final String state;
    
        private BotState(String state) {
            this.state = state;
        }
    
        @Override
        public String toString() {
            return this.state;
        }
    }
    

    接收消息并做出相应响应的服务。

    @Service
    public class TelegramBotService extends TelegramLongPollingBot {
    
        @Autowired
        private CodeUtils codeUtils;
    
        @Autowired
        private UserBotRepository userBotRepository;
    
        @Autowired
        private UserRepository userRepository;
    
        @Value("${telegram.bot.username}")
        private String botUsername;
    
        @Value("${telegram.bot.token}")
        private String botToken;
    
        @PostConstruct
        public void registerBot() {
            TelegramBotsApi botsApi = new TelegramBotsApi();
            try {
                botsApi.registerBot(this);
            } catch (TelegramApiException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onUpdateReceived(Update update) {
            if (update.hasMessage() && update.getMessage().hasText()) {
                String receivedMessage = update.getMessage().getText();
                SendMessage sendMessage = null;
    
                // TODO: futuramente, tratar casos onde um usuário chama um comando sem ainda estar autenticado
                switch (receivedMessage) {
                    case "/autenticar":
                        sendMessage = handleAuthentication(update);
                        break;
                    default:
                        // Quando nenhum comando atender, será um texto a ser checado de acordo com o estado anterior
                        sendMessage = checkState(update);
                }
    
                try {
                    execute(sendMessage);
                } catch (TelegramApiException e) {
                    codeUtils.log(e.getMessage(), this);
                }
            }
        }
    
        private SendMessage handleAuthentication(Update update) {
            SendMessage sendMessage = new SendMessage()
                    .setChatId(update.getMessage().getChatId())
                    .setText(BotState.AUTH_STEP_1.toString());
    
            UserBot userBot = userBotRepository.findByChatId(update.getMessage().getChatId().toString());
    
            if (userBot == null) {
                userBot = new UserBot();
                userBot.setChatId(update.getMessage().getChatId().toString());
                userBot.setLastBotState(BotState.AUTH_STEP_1);
            } else if (userBot.isVerified()) {
                // Um texto simples enviado no sendMessage indica o fim de um fluxo
                sendMessage.setText("Este aparelho já está autenticado no sistema.");
                userBot.setLastBotState(null);
            }
    
            userBotRepository.save(userBot);
            return sendMessage;
        }
    
        // Checa o estado anterior do bot em relação ao chatId recebido
        private SendMessage checkState(Update update) {
            UserBot userBot = userBotRepository.findByChatId(update.getMessage().getChatId().toString());
            SendMessage sendMessage = null;
    
            if (userBot == null || userBot.getLastBotState() == null)
                return sendDefaultMessage(update);
    
            switch (Optional.ofNullable(userBot.getLastBotState()).orElse(BotState.NO_STATE)) {
                case AUTH_STEP_1:
                    sendMessage = sendCode(update);
                    break;
                case AUTH_STEP_2:
                    sendMessage = validateCode(update);
                    break;
                default:
                    sendMessage = sendDefaultMessage(update);
            }
    
            return sendMessage;
        }
    
        // Grava o código no banco e envia para o e-mail do usuário
        private SendMessage sendCode(Update update) {
            User user = userRepository.findByEmail(update.getMessage().getText().toLowerCase());
            SendMessage sendMessage = new SendMessage(update.getMessage().getChatId(), "");
    
            if (user == null)
                sendMessage.setText("Não encontrei nenhum usuário no sistema com este e-mail :(");
            else {
                UserBot userBot = userBotRepository.findByChatId(update.getMessage().getChatId().toString());
    
                String verificationCode = Integer.toString(new Random().nextInt(899999) + 100000);
                String text = "Este é um e-mail automático de verificação de identidade. Informe este código para o bot do Telegram: " + verificationCode;
                codeUtils.sendEmail(new String[]{user.getEmail()}, "CCR Laudos - Código de Verificação", text);
    
                // Associa a conversação ao usuário, mas a validade depende da flag verified
                userBot.setUser(user);
                userBot.setBotVerificationCode(verificationCode);
                userBot.setLastBotState(BotState.AUTH_STEP_2);
                userBotRepository.save(userBot);
    
                sendMessage.setText(BotState.AUTH_STEP_2.toString());
            }
    
            return sendMessage;
        }
    
        // Checa se o código informado foi o mesmo passado por e-mail para o usuário a fim de autenticá-lo
        private SendMessage validateCode(Update update) {
            UserBot userBot = userBotRepository.findByChatId(update.getMessage().getChatId().toString());
            SendMessage sendMessage = new SendMessage(update.getMessage().getChatId(), "");
    
            if (update.getMessage().getText().equals(userBot.getBotVerificationCode())) {
                userBot.setVerified(true);
                sendMessage.setText("O aparelho foi autenticado com sucesso. Você passará a receber notificações do sistema.");
            } else {
                userBot.setUser(null);
                sendMessage.setText("Código inválido.");
            }
    
            userBotRepository.save(userBot);
            return sendMessage;
        }
    
        private SendMessage sendDefaultMessage(Update update) {
            String markdownMessage = "Não entendi \ud83e\udd14 \n"
                    + "Que tal tentar um comando digitando */* ?";
            return new SendMessage(update.getMessage().getChatId(), markdownMessage).setParseMode(ParseMode.MARKDOWN);
        }
    
        @Override
        public String getBotUsername() {
            return this.botUsername;
        }
    
        @Override
        public String getBotToken() {
            return this.botToken;
        }
    }
    

    实现的流程是:

    1 - 用户发送 /authenticate。

    2 - 系统对设备一无所知,因此存储聊天 ID 和最后状态。 最后一个状态将是对用户的响应。系统要求用户的电子邮件。

    3 - 用户发送他的电子邮件。

    4 - 文本未被识别为命令,因此系统检查是否存在与此聊天 ID 相关的最后状态。如果存在先前的状态,则使用传入文本作为此状态方法的参数。系统向用户的电子邮件发送一个代码并要求它。

    5 - 用户发送代码。

    6 - 如果代码正确,系统会再次检查之前的状态并对用户进行身份验证。

    就是这样!希望它可以帮助某人。

    【讨论】:

      【解决方案2】:

      你可以用不同的方式来做。

      1. 您可以存储用户自己的“状态”
      2. 您可以使用多个单独运行的命令,但您只是给用户一种被引导的感觉
      3. 您可以使用 Bot API 的 ForceReply

      好的。所以对于1.,我想说你有一些好处。当用户处于不正确的状态时,您实际上可以引导用户并限制对某些命令的访问。因此,假设他想购买 Popcorn,但他在鞋店,您可以通过检查保存的用户状态来禁止该命令。

      对于2.,您将始终允许用户使用/buy 和/buy_popcorn 和/buy_shoe。但根据您的回答,您只需给他特定数量的可能选择。

      User: /buy
      
      What do you want to buy? /shoes or /food :Bot
      
      User: /food
      
      How about some Popcorn? Use /buy_popcorn :Bot
      
      User: /buy_shoe
      
      Alright. Shoes added to cart :Bot
      

      这是允许的,但用户必须手动写入/buy_shoe

      3. 可能的方式是使用ForceReply。用户将自动收到answer to 消息。因此,当他使用/buy_shoe 时,他将回复机器人发送的最后一条消息。您还将在来自 api 的消息中获得用户回答的消息。您可以检查用户回答的消息是否是该命令的适当前提条件/适当消息,然后限制或允许该命令。

      User: /buy
      
      What do you want to buy? /shoes or /food :Bot
      
      User: [Answer to: What do you...] /food
      
      How about some Popcorn? Use /buy_popcorn :Bot
      
      User: [Answer to: How about some...] /buy_shoe
      
      Sorry, but you're currently in the Food Store :Bot
      

      我想这取决于个人喜好。但所有这些都有利有弊,您必须决定是否要在没有先决条件的情况下允许特定命令。

      此列表可能不完整。可能还有其他方法,我没有想到。但是这 3 种是我所知道的。

      【讨论】:

      • 感谢您的回答。
      • @Loki 不错且解释清楚的答案!
      猜你喜欢
      • 2020-08-16
      • 1970-01-01
      • 1970-01-01
      • 2018-07-03
      • 1970-01-01
      • 2019-05-30
      • 2019-01-08
      • 1970-01-01
      • 2018-07-09
      相关资源
      最近更新 更多