{"id":2242,"date":"2018-06-18T10:00:43","date_gmt":"2018-06-18T08:00:43","guid":{"rendered":"https:\/\/billigeplaetze.com\/?p=46"},"modified":"2024-08-14T13:52:27","modified_gmt":"2024-08-14T11:52:27","slug":"getting-started-with-the-telegram-abilitybot","status":"publish","type":"post","link":"https:\/\/craftcoders.app\/getting-started-with-the-telegram-abilitybot\/","title":{"rendered":"Getting Started with Telegrams AbilityBot"},"content":{"rendered":"
This getting started is for developers who want to create a telegram chatbot with Java. There are many articles on the internet explaining how to create a chatbot based on the so-called In the course of the article we are going to build a simple chatbot that reminds its users every two days to work out. The result will look something like this:<\/p>\n <\/p>\n Even with this simple example, we can take a look at a bunch of great features provided by the Telegram API and more specific via If you don’t want to work yourself through the whole article, feel free to take a look at the finished code on github<\/a>.<\/p>\n Okay then, let’s start coding!<\/p>\n For the Ensure that you are using at least Java 8 by adding the following build plugin to your pom:<\/p>\n At first, we create our bot class called FitnessBot<\/strong>. It extends the class At the latest when you paste this code into your IDE, you will recognize the unresolved reference to an interface called You need to replace the information with the ones @BotFather<\/a> gave to you. If you don’t know what I’m talking about please refer to this tutorial<\/a>. It contains information on how to create a new Telegram bot.<\/p>\n Before you can run your bot you have to initialize it. Therefore we create a new class called Application<\/strong> and fill it with a Now you can run your bot but he won’t answer, yet. In the next step, we change this by developing the first ability: A response to the command In I don’t want to go into detail on the specific methods chained here because they are pretty well explained in the already linked documentation<\/a>.<\/p>\n But here are some information explaining State handling and properly saving that state to a database can get pretty complex. So it makes sense to create a separate class for that concern. Therefore we build a new class In it’s constructor, the The next step is to add a new entry to our This is the name of the table created internally by the embedded database. Pretty simple. You can read and even update the map <\/p>\n If you wondered about To make use of the whole new code we need to initialize our Now we need to replace the But our And our text is saved in If you run the application now and issue Chances are that you will use more than one inline keyboard. That’s why we create a new class called And in our Now we can just call the static method of our factory to make use of an inline keyboard. The most important code part is Now we need to use the keyboard in our response by adding another Give it a try! Our bot will now offer an inline keyboard to answer the training day question.<\/p>\n So far we aren’t able to recognize clicks on the two buttons. But we can give our Add the following lines to your bot class.<\/p>\n The tricky part here is the implementation of The actual logic resides in the referenced methods. First, it validates whether the bot is currently in a state where it is waiting for a response. Only if this is the case, the button is clicked. Depending on which button has been pressed, the bot is moved to another state.<\/p>\n The two referenced states are new thus have to be added to our enum:<\/p>\n Depending on the clicked button we want to answer something different. As usual, we need to add the answers to Now we need a way to remind our users to do sports on their training days. For that, we make use of DailyTask<\/strong> is an interface with an But first, let’s create an instance of Okay, now we need a new class for our Now you should have a compile error in your bot class because you try to pass your bot as a reference for Due to that interface, you need to override In the constants file:<\/p>\n For each chat, we will now send a reminder message if we are in And that’s it. Now your bot is able to remind you every second day to do sports. Feel free to leave a comment if you have any questions or suggestions. You can find the whole code of this getting started on our github profile<\/a>.<\/p>\n Greets, This getting started is for developers who want to create a telegram chatbot with Java. There are many articles on the internet explaining how to create a chatbot based on the so-called LongPollingBot. But development works much faster and easier with the AbilityBot, which will be our topic for today. In the course of the article we are going to … Read More<\/a><\/p>\n","protected":false},"author":3,"featured_media":2370,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[107,108,109],"tags":[],"acf":[],"yoast_head":"\nLongPollingBot<\/code>. But development works much faster and easier with the
AbilityBot<\/code>, which will be our topic for today.<\/p>\n
AbilityBot<\/code>. In a nutshell, those features are:<\/p>\n
\n
\/start<\/code><\/li>\n
Project setup<\/h2>\n
AbilityBot<\/code> to work, you need to set up a Maven project in a Java 8 environment. In case you have never done that before, head over to Maven’s getting started<\/a> and setup a new maven project. In your
pom.xml<\/code> add the following two dependencies:<\/p>\n
<dependencies>\r\n <dependency>\r\n <groupId>org.telegram<\/groupId>\r\n <artifactId>telegrambots<\/artifactId>\r\n <version>3.6.1<\/version>\r\n <\/dependency>\r\n\r\n <dependency>\r\n <groupId>org.telegram<\/groupId>\r\n <artifactId>telegrambots-abilities<\/artifactId>\r\n <version>3.6.1<\/version>\r\n <\/dependency>\r\n<\/dependencies>\r\n<\/code><\/pre>\n
<build>\r\n <plugins>\r\n <plugin>\r\n <groupId>org.apache.maven.plugins<\/groupId>\r\n <artifactId>maven-compiler-plugin<\/artifactId>\r\n <configuration>\r\n <source>8<\/source>\r\n <target>8<\/target>\r\n <\/configuration>\r\n <\/plugin>\r\n <\/plugins>\r\n<\/build>\r\n<\/code><\/pre>\n
Getting your bot to talk<\/h2>\n
AbilityBot<\/code> thus we need to call
super()<\/code> on the constructor and override the method
creatorId()<\/code>. Furthermore we added another constructor without any arguments, thus we can easily instantiate our bot.<\/p>\n
public class FitnessBot extends AbilityBot {\r\n\r\n public FitnessBot() {\r\n this(Constants.BOT_TOKEN, Constants.BOT_USERNAME);\r\n }\r\n\r\n private FitnessBot(String botToken, String botUsername) {\r\n super(botToken, botUsername);\r\n }\r\n\r\n @Override\r\n public int creatorId() {\r\n return Constants.CREATOR_ID;\r\n }\r\n}\r\n<\/code><\/pre>\n
Constants<\/code>. It is a common software pattern widely used in Android for example. The interface contains all the constant values needed by your application to function properly. Later we will use it for all our text responses, database identifiers, and similar stuff. In order to fix your code for now, create a new interface and paste the following lines.<\/p>\n
public interface Constants {\r\n \/\/ Initialization\r\n String BOT_USERNAME = \"FitnessBot\";\r\n String BOT_TOKEN = \"your-super-secret-token\";\r\n int CREATOR_ID = your-telegram-user-id;\r\n}\r\n<\/code><\/pre>\n
Running your bot<\/h3>\n
main()<\/code> method containing the code to initialize your bot. This block is copied from the Github documentation<\/a> of ability bot.<\/p>\n
public class Application {\r\n\r\n public static void main(String[] args) {\r\n \/\/ Initializes dependencies necessary for the base bot\r\n ApiContextInitializer.init();\r\n\r\n \/\/ Create the TelegramBotsApi object to register your bots\r\n TelegramBotsApi botsApi = new TelegramBotsApi();\r\n\r\n try {\r\n \/\/ Register your newly created AbilityBot\r\n FitnessBot bot = new FitnessBot();\r\n botsApi.registerBot(bot);\r\n\r\n } catch (TelegramApiException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n}\r\n<\/code><\/pre>\n
\/start<\/code>. Therefore, you have to add the following method to your
FitnessBot<\/code> class.<\/p>\n
public Ability replyToStart() {\r\n return Ability\r\n .builder()\r\n .name(\"start\")\r\n .info(Constants.START_DESCRIPTION)\r\n .locality(ALL)\r\n .privacy(PUBLIC)\r\n .action(ctx -> silent.send(\"Hello World!\", ctx.chatId()))\r\n .build();\r\n}\r\n<\/code><\/pre>\n
Constants<\/code> add:<\/p>\n
String START_DESCRIPTION = \"Start using the fitness bot to remind you doing sports\";\r\n<\/code><\/pre>\n
action()<\/code> more thoroughly.
ctx<\/code> is the message context and provides related information like the
chatId<\/code> of the message. To reply to a message you have to call a method on either object
silent<\/code> or
sender<\/code>. Both are provided by the parent class
AbilityBot<\/code>. As you can see in our code, currently we are using the
silent<\/code> object. The difference is that while
silent<\/code> is used to send plain text messages only,
sender<\/code> gives you more freedom on how to compose your reply. As you will learn later, inline keyboards, for example, need to use
sender<\/code>. The downside of
sender<\/code> is that more freedom leads to more responsibility. Exceptions might occour and you have to handle them by yourself.<\/p>\n
Database usage and state handling<\/h3>\n
ResponseHandler<\/code> which will be responsible for processing requests:<\/p>\n
public class ResponseHandler {\r\n private final MessageSender sender;\r\n private final Map<Long, State> chatStates;\r\n\r\n public ResponseHandler(MessageSender sender, DBContext db) {\r\n this.sender = sender;\r\n chatStates = db.getMap(Constants.CHAT_STATES);\r\n }\r\n}\r\n<\/code><\/pre>\n
ResponseHandler<\/code> receives the database context called
db<\/code>. The instance, which is going to be created using the constructor, will become a field in our FitnessBot<\/strong> class. Field
sender<\/code> will be used to send messages back to the user. As you can see, the state of the bot is saved separately for each chat in a Map called
chatStates<\/code>.<\/p>\n
Constants<\/code> interface:<\/p>\n
String CHAT_STATES = \"CHAT_STATES\"\r\n<\/code><\/pre>\n
chatStates<\/code> as you wish and it will be synced with the database automagically.<\/p>\n
State<\/code>-class: This is an enum containing our set of states for the bot. Currently, just a single one which says that we are waiting for the user to reply.<\/p>\n
public enum State {\r\n AWAITING_TRAINING_DAY\r\n}\r\n<\/code><\/pre>\n
ResponseHandler<\/code> in the bot class. We do that using a new field inside of
FitnessBot<\/code>.<\/p>\n
private final ResponseHandler responseHandler;\r\n\r\npublic FitnessBot(String botToken, String botUsername) {\r\n super(botToken, botUsername);\r\n responseHandler = new ResponseHandler(sender, db);\r\n }\r\n<\/code><\/pre>\n
action()<\/code> method in our
replyToStart()<\/code> ability with<\/p>\n
.action(ctx -> responseHandler.replyToStart(ctx.chatId()))\r\n<\/code><\/pre>\n
ResponseHandler<\/code> doesn’t have the referenced method, so far. You can find it below.<\/p>\n
public void replyToStart(long chatId) {\r\n try {\r\n sender.execute(new SendMessage()\r\n .setText(Constants.START_REPLY)\r\n .setChatId(chatId));\r\n chatStates.put(chatId, State.AWAITING_TRAINING_DAY);\r\n } catch (TelegramApiException e) {\r\n e.printStackTrace();\r\n }\r\n}\r\n<\/code><\/pre>\n
Constants<\/code>:<\/p>\n
String START_REPLY = \"Welcome I'm FitnessBot. I'm here to remind you doing sports every second day!\";\r\n<\/code><\/pre>\n
\/start<\/code> our newly added text will appear!<\/p>\n
Using inline keyboards<\/h3>\n
KeyboardFactory<\/code>. This class will create the needed keyboard instances for us. The code is pretty self-explanatory but see for yourself.<\/p>\n
public class KeyboardFactory {\r\n public static ReplyKeyboard withTodayTomorrowButtons() {\r\n InlineKeyboardMarkup inlineKeyboard = new InlineKeyboardMarkup();\r\n List<List<InlineKeyboardButton>> rowsInline = new ArrayList<>();\r\n List<InlineKeyboardButton> rowInline = new ArrayList<>();\r\n rowInline.add(new InlineKeyboardButton().setText(Constants.TRAINING_TODAY).setCallbackData(Constants.TRAINING_TODAY));\r\n rowInline.add(new InlineKeyboardButton().setText(Constants.TRAINING_TOMORROW).setCallbackData(Constants.TRAINING_TOMORROW));\r\n rowsInline.add(rowInline);\r\n inlineKeyboard.setKeyboard(rowsInline);\r\n return inlineKeyboard;\r\n }\r\n}\r\n<\/code><\/pre>\n
Constants<\/code> file we add:<\/p>\n
String TRAINING_TODAY = \"Today\";\r\nString TRAINING_TOMORROW = \"Tomorrow\";\r\nString FIND_TRAINING_DATE = \"Do you want to have a workout today or tomorrow?\";\r\n<\/code><\/pre>\n
setCallbackData()<\/code>. It defines an identifier to recognize which button has been clicked by the user. Hint: In a real world application it might not be too smart to use the button text as identifier for a callback but we use it here to simplify the code.<\/p>\n
sender.execute()<\/code> below the first one which we already defined.<\/p>\n
public void replyToStart(long chatId) {\r\n try {\r\n sender.execute(new SendMessage()\r\n .setText(Constants.START_REPLY)\r\n .setChatId(chatId));\r\n\r\n sender.execute(new SendMessage()\r\n .setText(Constants.FIND_TRAINING_DATE)\r\n .setChatId(chatId)\r\n .setReplyMarkup(KeyboardFactory.withTodayTomorrowButtons()));\r\n\r\n chatStates.put(chatId, State.AWAITING_TRAINING_DAY);\r\n\r\n } catch (TelegramApiException e) {\r\n e.printStackTrace();\r\n }\r\n}\r\n<\/code><\/pre>\n
Inline keyboard interactions<\/h3>\n
FitnessBot<\/code> a new ability to do so! Similar to the image reply example<\/a> given by Telegram we can filter for button responses via
Flag.CALLBACK_QUERY<\/code>. All the identifiers of clicked buttons will be sent inside of the update object
upd<\/code>. This object contains a lot of data but luckily there are some helper methods to extract the important information conveniently.
getChatId(upd)<\/code> will find the chat id of the update for you and via
AbilityUtils.getUser(upd)<\/code> you can get the author of the message.<\/p>\n
public Reply replyToButtons() {\r\n Consumer<Update> action = upd -> responseHandler.replyToButtons(getChatId(upd), upd.getCallbackQuery().getData());\r\n return Reply.of(action, Flag.CALLBACK_QUERY);\r\n}\r\n<\/code><\/pre>\n
replyToButtons()<\/code> inside of
responseHandler<\/code> because every single button click will be processed in this method. That’s why we use it for separation of concerns only.<\/p>\n
public void replyToButtons(long chatId, String buttonId) {\r\n try {\r\n switch (buttonId) {\r\n case Constants.TRAINING_TODAY:\r\n replyToTrainingToday(chatId);\r\n break;\r\n case Constants.TRAINING_TOMORROW:\r\n replyToTrainingTomorrow(chatId);\r\n break;\r\n }\r\n } catch (TelegramApiException e) {\r\n e.printStackTrace();\r\n }\r\n}\r\n<\/code><\/pre>\n
private void replyToTrainingToday(long chatId) throws TelegramApiException {\r\n if (chatStates.get(chatId).equals(State.AWAITING_TRAINING_DAY)) {\r\n sender.execute(new SendMessage()\r\n .setText(Constants.TRAINING_TODAY_REPLY)\r\n .setChatId(chatId));\r\n chatStates.put(chatId, State.TODAY_IS_TRAINING_DAY);\r\n }\r\n}\r\n\r\nprivate void replyToTrainingTomorrow(long chatId) throws TelegramApiException {\r\n if (chatStates.get(chatId).equals(State.AWAITING_TRAINING_DAY)) {\r\n sender.execute(new SendMessage()\r\n .setText(Constants.TRAINING_TOMORROW_REPLY)\r\n .setChatId(chatId));\r\n chatStates.put(chatId, State.TODAY_IS_RELAX_DAY);\r\n }\r\n}\r\n<\/code><\/pre>\n
public enum State {\r\n AWAITING_TRAINING_DAY, TODAY_IS_TRAINING_DAY, TODAY_IS_RELAX_DAY\r\n}\r\n<\/code><\/pre>\n
Constants<\/code>:<\/p>\n
String TRAINING_TODAY_REPLY = \"Okay then take this as a reminder ;)\";\r\nString TRAINING_TOMORROW_REPLY = \"Okay I'll remind you tomorrow at nine o'clock!\";\r\n<\/code><\/pre>\n
Extra: Scheduled task execution<\/h2>\n
ScheduledExecutorService<\/code> from Java’s concurrent<\/em> package. We’re not going to go into detail here, as it is not part of the bot framework. You just need to know that with the class below we have an easy way to execute a task at a specific time on a daily basis. E.g. to write a message at 9am in the morning. Create a new class called
DailyTaskExecutor<\/code>:<\/p>\n
public class DailyTaskExecutor {\r\n private final ScheduledExecutorService executorService;\r\n private final DailyTask dailyTask;\r\n\r\n public DailyTaskExecutor(DailyTask dailyTask) {\r\n this.executorService = Executors.newScheduledThreadPool(1);\r\n this.dailyTask = dailyTask;\r\n }\r\n\r\n public void startExecutionAt(int targetHour, int targetMin, int targetSec) {\r\n Runnable taskWrapper = () -> {\r\n dailyTask.execute();\r\n startExecutionAt(targetHour, targetMin, targetSec);\r\n };\r\n long delay = computeNextDelay(targetHour, targetMin, targetSec);\r\n executorService.schedule(taskWrapper, delay, TimeUnit.SECONDS);\r\n }\r\n\r\n private long computeNextDelay(int targetHour, int targetMin, int targetSec) {\r\n LocalDateTime localNow = LocalDateTime.now();\r\n ZoneId currentZone = ZoneId.systemDefault();\r\n ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);\r\n ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec);\r\n if(zonedNow.compareTo(zonedNextTarget) >= 0)\r\n zonedNextTarget = zonedNextTarget.plusDays(1);\r\n\r\n Duration duration = Duration.between(zonedNow, zonedNextTarget);\r\n return duration.getSeconds();\r\n }\r\n\r\n public void stop() {\r\n executorService.shutdown();\r\n try {\r\n executorService.awaitTermination(1, TimeUnit.DAYS);\r\n } catch (InterruptedException ex) {\r\n Logger.getLogger(DailyTaskExecutor.class.getName()).log(Level.SEVERE, null, ex);\r\n }\r\n }\r\n}\r\n<\/code><\/pre>\n
execute()<\/code> method. We have to create an implementation of this interface later.<\/p>\n
public interface DailyTask {\r\n void execute();\r\n}\r\n<\/code><\/pre>\n
DailyTaskExecutor<\/code> in our
FitnessBot<\/code> class as a new field. As soon as we have our instance we schedule a new task which will run every morning at 9 am using
startExecutionAt()<\/code>.<\/p>\n
private final DailyTaskExecutor dailyTaskExecutor;\r\n\r\n private FitnessBot(String botToken, String botUsername) {\r\n super(botToken, botUsername);\r\n responseHandler = new ResponseHandler(sender, db);\r\n dailyTaskExecutor = new DailyTaskExecutor(new MorningReminderTask(this));\r\n dailyTaskExecutor.startExecutionAt(9, 0, 0);\r\n }\r\n<\/code><\/pre>\n
DailyTask<\/code> interface. We call it
MorningReminderTask<\/code> and it takes a callback listener as a constructor parameter. This callback listener is part of the class itself and gets called as soon as
execute()<\/code> will be run.<\/p>\n
public class MorningReminderTask implements DailyTask {\r\n\r\n public interface Callback {\r\n void onTimeForMorningTask();\r\n }\r\n\r\n private final Callback callback;\r\n\r\n public MorningReminderTask(Callback callback) {\r\n this.callback = callback;\r\n }\r\n\r\n @Override\r\n public void execute() {\r\n callback.onTimeForMorningTask();\r\n }\r\n}\r\n<\/code><\/pre>\n
callback<\/code> in the constructor of
MorningReminderTask<\/code>. Before you can do that you need to implement
MorningReminderTask.Callback<\/code> in your bot.<\/p>\n
public class FitnessBot extends AbilityBot implements MorningReminderTask.Callback {\r\n\r\n @Override\r\n public void onTimeForMorningTask() {\r\n responseHandler.sayMorningMessages();\r\n }\r\n}\r\n<\/code><\/pre>\n
onTimeForMorningTask()<\/code> method. As our response handler class is taking care of writing messages we just call one of its methods. Which we have to create now:<\/p>\n
public void sayMorningMessages() {\r\n try {\r\n for (long chatId : chatStates.keySet()) {\r\n switch (chatStates.get(chatId)) {\r\n case TODAY_IS_TRAINING_DAY:\r\n processTrainingDay(chatId);\r\n break;\r\n case TODAY_IS_RELAX_DAY:\r\n processRelaxDay(chatId);\r\n break;\r\n }\r\n }\r\n } catch (TelegramApiException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n\r\n private void processTrainingDay(long chatId) throws TelegramApiException {\r\n sender.execute(new SendMessage()\r\n .setText(Constants.TRAINING_REMINDER)\r\n .setChatId(chatId));\r\n chatStates.put(chatId, State.TODAY_IS_RELAX_DAY);\r\n }\r\n\r\n private void processRelaxDay(long chatId) {\r\n chatStates.put(chatId, State.TODAY_IS_TRAINING_DAY);\r\n }\r\n<\/code><\/pre>\n
String TRAINING_REMINDER = \"Good Morning! Don't forget to do sports, today.\";\r\n<\/code><\/pre>\n
TODAY_IS_TRAINING_DAY<\/code> state. Furthermore, we are changing the state to the opposite, so every second day will be a training day.<\/p>\n
\nDomi<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"