节点电报机器人 API,在用户之间进行链对话

Node Telegram bot api, make chain conversation between user

本文关键字:之间 对话 用户 机器人 API 节点      更新时间:2023-09-26

我已经开始使用此模块为电报编写机器人逻辑

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

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

当用户键入/start时,他将收到此消息。

我想在机器人和用户之间创建类似链式对话的东西。就像当用户键入/buy机器人将显示购买选项一样,在该用户键入他想要购买的内容之后,机器人将显示所选产品的类型,依此类推。

如何在用户和机器人之间创建链式对话?如何让机器人记住以前选择的命令并在需要时重置它们?我是否需要保留在自己的数据库中才能执行此操作?

你可以用不同的方式做到这一点。

  1. 您可以自己存储用户的"状态"
  2. 您可以使用多个命令自行工作,但您只是给用户一种被引导的感觉
  3. 可以使用机器人 API 的ForceReply

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

对于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 种是我知道的方法。

我也有这个问题,我需要我的机器人根据他对用户的最后回答来回答问题,并且由于很难找到可以引导我找到解决方案的想法(在 Java 中),为了未来的 Java 谷歌用户,我将在这里分享我的。我正在使用电报机器人库和弹簧启动/数据。

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

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

保存与系统用户相关的电报聊天信息的实体。

@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 - 用户发送/身份验证。

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

3 - 用户发送电子邮件。

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

5 - 用户发送代码。

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

就是这样!希望它对某人有所帮助。