使用Spring Boot创建Telegram机器人

2023/11/03

1. 简介

在本教程中,我们将使用Spring Boot创建一个Telegram机器人。

Telegram机器人是一种在Telegram消息平台内运行的自动化程序,它利用Telegram Bot API与用户交互并执行各种任务。我们将使用Java库,而不是直接与API交互。机器人帮助我们响应用户命令、提供信息并执行自动化操作。

我们将从设置一个全新的机器人开始,然后继续描述如何使用Java库来实现简单的操作。

2. 创建Telegram机器人

首先,我们需要在Telegram平台上创建一个新的机器人。我们直接使用Telegram消息应用程序并在搜索栏中搜索BotFather来完成此操作。打开后,我们将键入/newbot命令来创建机器人并按照BotFather的说明进行操作。它会询问我们要分配给机器人的用户名,根据Telegram的政策,该用户名需要以bot结尾:

上面,BotFather生成了一个令牌,我们必须将其保存在安全的地方,以便稍后用于配置我们的应用程序。

3. 设置应用程序

其次,我们必须有一个要集成TelegramBot的Spring Boot项目。我们将修改pom.xml文件并包含telegrambots-spring-boot-startertelegrambots-powered库:

<dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-spring-boot-starter</artifactId>
    <version>6.7.0</version>
</dependency>
<dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-abilities</artifactId>
    <version>6.7.0</version>
</dependency>

在底层,AbilityBot使用webhook与Telegrams API进行通信,但我们不需要担心这一点。事实上,该库实现了Telegram Bot API提供的所有接口。

现在,我们可以实现我们的机器人了。

4. 解释PizzaBot

我们将实现一个模拟披萨店的简单机器人,以演示如何使用Spring Boot库。此外,我们将与机器人进行一组预定义的交互:

简而言之,我们首先询问用户的姓名。然后,我们会提示他选择披萨或饮料。如果是饮料,我们会显示一条消息,说明我们不出售饮料。否则,我们会向他们询问披萨的配料。选择可用的配料后,我们将确认用户是否想再次订购。在这种情况下,我们将重复该流程。或者,我们会感谢他们并通过结束消息结束聊天。

5. 配置并注册PizzaBot

让我们首先为我们的新PizzaShop配置SkillBot:

@Component
public class PizzaBot extends AbilityBot {
    private final ResponseHandler responseHandler;
    
    @Autowired
    public PizzaBot(Environment env) {
        super(env.getProperty("botToken"), "tuyuchengbot");
        responseHandler = new ResponseHandler(silent, db);
    }
    
    @Override
    public long creatorId() {
        return 1L;
    }
}

我们在构造函数中读取作为环境变量注入的botToken属性。我们必须保证令牌的安全,不要将其推入代码库。在此示例中,我们在运行应用程序之前将其导出到我们的环境中。或者,我们可以在属性文件中定义它。此外,我们必须提供一个唯一的creatorId来描述我们的机器人。

此外,我们还扩展了SkillBot类,它减少了样板代码并通过ReplyFlow提供状态机等常用工具。但是,我们将仅使用嵌入式数据库并在ResponseHandler内显式管理状态:

public class ResponseHandler {
    private final SilentSender sender;
    private final Map<Long, UserState> chatStates;

    public ResponseHandler(SilentSender sender, DBContext db) {
        this.sender = sender;
        chatStates = db.getMap(Constants.CHAT_STATES);
    }
}

5.1 Spring Boot 3兼容性问题

使用Spring Boot版本3时,当声明为@Component时,库不会自动配置机器人。因此,我们必须在主应用程序类中手动初始化它:

TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
botsApi.registerBot(ctx.getBean("pizzaBot", TelegramLongPollingBot.class));

我们创建TelegramBotsApi的新实例,然后从Spring应用程序上下文提供PizzaBot组件实例。

6. 实现PizzaBot

Telegram API非常庞大,并且可能会重复执行新命令。因此,我们使用能力来简化开发新能力的过程。在设计交互流程时,我们应该考虑最终用户、必要条件和执行流程。

在我们的PizzaBot中,我们将仅使用一个功能/start来启动与机器人的对话:

public Ability startBot() {
    return Ability
        .builder()
        .name("start")
        .info(Constants.START_DESCRIPTION)
        .locality(USER)
        .privacy(PUBLIC)
        .action(ctx -> responseHandler.replyToStart(ctx.chatId()))
        .build();
}

我们使用构建器模式来构建能力。首先,我们定义能力的名称,该名称与能力的命令相同。其次,我们提供这个新能力的描述字符串。当我们运行/commands命令来获取机器人的能力时,这将很有帮助。第三,我们定义机器人的位置和隐私。最后,我们定义收到命令后必须采取的操作。在此示例中,我们将聊天的id转发到ResponseHandler类。按照上图的设计,我们将询问用户的姓名并将其以其初始状态保存在Map中:

public void replyToStart(long chatId) {
    SendMessage message = new SendMessage();
    message.setChatId(chatId);
    message.setText(START_TEXT);
    sender.execute(message);
    chatStates.put(chatId, AWAITING_NAME);
}

在此方法中,我们创建一个SendMessage命令并使用sender执行它。然后,我们将聊天状态设置为AWAITING_NAME,表示我们正在等待用户的名字:

private void replyToName(long chatId, Message message) {
    promptWithKeyboardForState(chatId, "Hello " + message.getText() + ". What would you like to have?",
        KeyboardFactory.getPizzaOrDrinkKeyboard(),
        UserState.FOOD_DRINK_SELECTION);
}

用户输入姓名后,我们向他们发送一个ReplyKeyboardMarkup,提示他们两个选项:

public static ReplyKeyboard getPizzaToppingsKeyboard() {
    KeyboardRow row = new KeyboardRow();
    row.add("Margherita");
    row.add("Pepperoni");
    return new ReplyKeyboardMarkup(List.of(row));
}

这将隐藏键盘并向用户显示带有两个按钮的界面:

现在,用户可以选择我们的披萨店不提供的披萨或饮料。当选择两个选项中的任何一个时,Telegram会发送一条带有响应的短信。

对于上图中的所有绿色菱形元素,我们遵循类似的过程。因此,我们这里不再重复。相反,让我们专注于处理对按钮的响应。

7. 处理用户回复

对于所有传入消息和聊天的当前状态,我们在PizzaBot类中以不同的方式处理响应:

public Reply replyToButtons() {
    BiConsumer<BaseAbilityBot, Update> action = (abilityBot, upd) -> responseHandler.replyToButtons(getChatId(upd), upd.getMessage());
    return Reply.of(action, Flag.TEXT,upd -> responseHandler.userIsActive(getChatId(upd)));
}

.replyToButtons()获取所有文本回复,并将它们与chatId和传入的Message对象一起转发到ResponseHandler。然后,在ResponseHandler内部,.replyToButtons()方法决定如何处理消息:

public void replyToButtons(long chatId, Message message) {
    if (message.getText().equalsIgnoreCase("/stop")) {
        stopChat(chatId);
    }

    switch (chatStates.get(chatId)) {
        case AWAITING_NAME -> replyToName(chatId, message);
        case FOOD_DRINK_SELECTION -> replyToFoodDrinkSelection(chatId, message);
        case PIZZA_TOPPINGS -> replyToPizzaToppings(chatId, message);
        case AWAITING_CONFIRMATION -> replyToOrder(chatId, message);
        default -> unexpectedMessage(chatId);
    }
}

在switch内,我们检查聊天的当前状态并相应地回复用户。例如,当用户的当前状态为“FOOD_DRINK_SELECTION”时,我们会处理响应并在用户点击选项Pizza时转到下一个状态:

private void replyToFoodDrinkSelection(long chatId, Message message) {
    SendMessage sendMessage = new SendMessage();
    sendMessage.setChatId(chatId);
    if ("drink".equalsIgnoreCase(message.getText())) {
        sendMessage.setText("We don't sell drinks.\nBring your own drink!! :)");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaOrDrinkKeyboard());
        sender.execute(sendMessage);
    } else if ("pizza".equalsIgnoreCase(message.getText())) {
        sendMessage.setText("We love Pizza in here.\nSelect the toppings!");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaToppingsKeyboard());
        sender.execute(sendMessage);
        chatStates.put(chatId, UserState.PIZZA_TOPPINGS);
    } else {
        sendMessage.setText("We don't sell " + message.getText() + ". Please select from the options below.");
        sendMessage.setReplyMarkup(KeyboardFactory.getPizzaOrDrinkKeyboard());
        sender.execute(sendMessage);
    }
}

另外,在.replyToButtons()中,我们立即检查用户是否发送了/stop命令。在这种情况下,我们停止聊天并从chatStates Map中删除chatId:

private void stopChat(long chatId) {
    SendMessage sendMessage = new SendMessage();
    sendMessage.setChatId(chatId);
    sendMessage.setText("Thank you for your order. See you soon!\nPress /start to order again");
    chatStates.remove(chatId);
    sendMessage.setReplyMarkup(new ReplyKeyboardRemove(true));
    sender.execute(sendMessage);
}

这将从数据库中删除用户的状态。要再次交互,用户必须编写/start命令。

8. 总结

在本教程中,我们讨论了使用Spring Boot实现Telegram机器人。

首先,我们使用BotFather在Telegram平台上创建了一个新的机器人。其次,我们设置了Spring Boot项目并解释了PizzaBot的功能以及它如何与用户交互。然后,我们使用简化新命令开发的功能来实现PizzaBot。最后,我们处理了用户的回复,并根据聊天状态提供了适当的响应。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章