📜 ⬆️ ⬇️

Telegram-bot as a gift

Hi, Habr! Today I want to talk about how to write Telegram-bot, but not simple, but gift. I ask for those who find this story funny, as well as those who are trying to write their Telegram bots in Java. Perhaps my little experience will be somewhat useful.

Brief background


I work as a programmer for a little more than six years, I write mostly in Java and 1C, I don’t grab stars from the sky, but I perform the tasks I’ve set.

In the spring of 2017, I was interested in creating bots for various instant messengers. At first it seemed like a good idea to create a bot in Viber. In Siberia, it is the most popular, almost all acquaintances are sitting in it, corporate chats are also conducted in it. Also inspired by this article . However, the creation of a public account was not such a simple task - all requests received terse refusals.

Having suffered from Vayber for about a week, I noticed Telegram, it turned out that registering a bot there is very easy, and the life of a Java programmer is facilitated by the presence of the TelegramBots library. As a first attempt at writing, a bot was written that allows you to receive some reports from corporate systems, then there was a bot with reference information, and in the fall I already tried to make a bot for pre-recording to a beauty salon. All these were interesting crafts that were liked by others, and even the first two bots were actually used in their work. Due to this, thoughts constantly rotated in my head about how else one can apply bots in everyday life and the national economy.
')
Now it is necessary to make a digression to talk about who the gift was made for (those who are interested only in technical details can easily skip).
When I work as a programmer on 1C, I have a lot to communicate and work together with a very nice colleague, a colleague, besides being pretty, is also smart, and sometimes (when the mood is good), he goes deep enough into development issues (and, if he wishes, he can program anything) and generally has a systemic mindset. A system of thinking - the crown of female attractiveness, who would argue. So, with such an attractive colleague, we often discuss all sorts of whistlers for 1C (we discuss it on my head, as they later fall into the next requirements for the system being developed), various new technologies and near-topic topics. By the way, my first bot, I showed it to this colleague and then received a good feedback and impetus to the study of the topic.

It should be noted here that a colleague had a birthday planned at the very beginning of December, and of course it was impossible not to congratulate her. It is quite obvious that colleagues with systems thinking as gifts can count not only on sweets, flowers and other nishtyaki, but also on something that pretends to wit. So in 2016, as a gift, the main page of the system that we do together was registered as a gift, and on the day of birth instead of the standard screensaver, a colleague met with greetings. It seems to be a trifle, but in our organization no one was so congratulated and the gift, as they say, went in, and so went in that the colleague considered this congratulation the best that day (I hope it was so). After such achievements, it was clear that in 2017 it was necessary to develop the theme and again supplement the standard congratulatory set with something IT. In my head there were vague thoughts that “something IT-related” could be connected with bots, but there was no clear idea, and I almost resigned myself to the idea that nothing original could be invented. Time passed, until the day of birth remained 5 days ...

I was returning home from work, the traffic jam was sluggish, and I could go deep into my thoughts: development plans, technical solutions, automation with the help of bots, anticipation of Friday beer, in general, standard porridge. Suddenly, the thought flashed through my head that a colleague still needed to be given a bot, even if it was quite unpretentious, for example, issuing various memorable photos for a colleague. The thought seemed completely stale, but I began to figure out which photos could be used. At the same time there was a lot of photos, but the circumstances in which they were made were remembered with difficulty. It was then that the idea of ​​a gift bot appeared: a comic quiz in which each question would be accompanied by photos of a colleague and several answers, while the bot would give some funny comment to each answer. On the one hand, I considered this idea quite original, and on the other, feasible in the time remaining before the birthday.

Preparation and creation of a database of questions


There were only 4 days left to complete all the work, or more precisely, 4 pm, and even then incomplete. At hand, there were the sources of three other bots that can be used as “spare parts”, and it was clear that there was a fascinating rally on a bicycle with crutches ahead.
Java was chosen as the programming language, TelegramBots was used as a library for working with the Telegram API, and the H2 database was used to store the database of questions.

The first task was to create a database of questions. For this, we had to do a lot of work on collecting photos from the phone, work and home computers and social networks. The photographs were structured in such a way that it turned out 26 questions, each of which included from 2 to 4 photos and 4 possible answers. At the same time, deliberately correct answers were not provided for, and the answer to each question was simply accompanied by a comment. I also wanted to save the history of the selected answer options, but at the last moment I just forgot to fasten this feature.

The layout of the photos and the invention of the questions turned out to be a very laborious process, and they spent one and a half evening.

Next was implemented the database storing the questions. Below are descriptions of the database tables and the DDL script.


Script
 CREATE SCHEMA IF NOT EXISTS QUE; SET SCHEMA QUE; CREATE TABLE QUE.CLS_QUEST( ID BIGINT IDENTITY, IS_DELETED INT DEFAULT 0, QUEST_TEXT CLOB ); CREATE TABLE QUE.CLS_QUEST_PHOTO( ID BIGINT IDENTITY, ID_QUEST BIGINT NOT NULL, IS_DELETED INT DEFAULT 0, REL_FILE_PATH CLOB, PHOTO_TEXT CLOB, FOREIGN KEY(ID_QUEST) REFERENCES CLS_QUEST(ID) ); CREATE TABLE QUE.CLS_ANSWER( ID BIGINT IDENTITY, ID_QUEST BIGINT NOT NULL, IS_DELETED INT DEFAULT 0, ANSWER_TEXT CLOB, ANSWER_COMMENT CLOB, FOREIGN KEY(ID_QUEST) REFERENCES CLS_QUEST(ID) ); 


After creation, the database was filled with data manually, the benefit of Netbeans, which I use as a development environment, is a fairly convenient SQL script editor.

After two days the base of questions and photos was ready, there was very little time left, it was time to move on to creating the bot itself.

Bot frame


Let me remind you that in order to create a bot in Telegram, you need to write @BotFather, using the / newbot command to enter the display name and user name for the bot. After completing these steps, a token will be received to access the Telegram API. It looks like this.


For beauty, add a profile photo using / setuserpic.


Now let's move on to creating the bot itself using TelegramBots. Let me remind you that Telegram allows you to create bots working with Webhooks and LongPolling bots. The second option was chosen. To create a LongPolling bot, you must implement your own class that inherits the org.telegram.telegrambots.bots.TelegramLongPollingBot class.

Class source code
 public class Bot extends TelegramLongPollingBot { private static final String TOKEN = "TOKEN"; private static final String USERNAME = "USERNAME"; public Bot() { } public Bot(DefaultBotOptions options) { super(options); } @Override public String getBotToken() { return TOKEN; } @Override public String getBotUsername() { return USERNAME; } @Override public void onUpdateReceived(Update update) { if (update.hasMessage() && update.getMessage().hasText()) { processCommand(update); } else if (update.hasCallbackQuery()) { processCallbackQuery(update); } } } 


TOKEN - a token for access to the Telegram API, obtained at the stage of bot registration.
USERNAME is the bot name obtained at the bot registration stage.
The onUpdateReceived method onUpdateReceived called when the bot onUpdateReceived “inbound updates” . In our bot, we are interested in processing text commands (to be honest, only the / start commands) and processing callbacks (callbacks) that occur when you click on the buttons of the inline keyboard (located in the message area).

The bot checks whether the incoming update is a text message update.hasMessage() && update.getMessage().hasText() or the callback update.hasCallbackQuery() , and then calls the appropriate methods for processing. We will talk about the contents of these methods a little later.

The created bot is a regular console application and its launch is as follows:

Source code of main class
 public class Main { public static void main(String[] args) { ApiContextInitializer.init(); TelegramBotsApi botsApi = new TelegramBotsApi(); Runnable r = () -> { Bot bot = null; HttpHost proxy = AppEnv.getContext().getProxy(); if (proxy == null) { bot = new Bot(); } else { DefaultBotOptions instance = ApiContext .getInstance(DefaultBotOptions.class); RequestConfig rc = RequestConfig.custom() .setProxy(proxy).build(); instance.setRequestConfig(rc); bot = new Bot(instance); } try { botsApi.registerBot(bot); AppEnv.getContext().getMenuManager().setBot(bot); } catch (TelegramApiRequestException ex) { Logger.getLogger(Main.class.getName()) .log(Level.SEVERE, null, ex); } }; new Thread(r).start() while (true) { try { Thread.sleep(80000L); } catch (InterruptedException ex) { Logger.getLogger(Main.class.getName()) .log(Level.SEVERE, null, ex); } } } } 

There is nothing difficult in the bot initialization, but I want to note that it is important to provide an opportunity to specify the bot proxy. In our case, the proxy settings are stored in the usual properties file, from which they are read when the program starts. Also note that the application uses its own bad bike in the form of some semblance of the global context AppEnv.getContext() . At the time of writing this bot, there was no time to correct it, but in the new “crafts” it was possible to get rid of this bike and use Google Guice instead.

Welcome message


Bot operation naturally begins with processing the / start command. As it was written above, this command is processed by the processCommand method.
At the beginning of the method we will declare smiles that will be used in the text of the greeting message.

 final String smiling_face_with_heart_eyes = new String(Character.toChars(0x1F60D)); final String winking_face = new String(Character.toChars(0x1F609)); final String bouquet = new String(Character.toChars(0x1F490)); final String party_popper = new String(Character.toChars(0x1F389)); 

Next, it checks the entered command and if it is a / start command, then a response answerMessage . The message is set to the text setText() , it turns on support for some setParseMode("HTML") html tags, and the chat ID is set to which the message will be sent to setChatId(update.getMessage().getChatId()) . It remains only to add the "Start" button. To do this, create an inline keyboard and add it to the answer:

 SendMessage answerMessage = null; String text = update.getMessage().getText(); if ("/start".equalsIgnoreCase(text)) { answerMessage = new SendMessage(); answerMessage.setText("<b>!" + smiling_face_with_heart_eyes + "\n-   !" + bouquet + bouquet + bouquet + party_popper + "  -,      ?</b>"); answerMessage.setParseMode("HTML"); answerMessage.setChatId(update.getMessage().getChatId()); InlineKeyboardMarkup markup = keyboard(update); answerMessage.setReplyMarkup(markup); } 

The source code of the keyboard is shown below:
 private InlineKeyboardMarkup keyboard(Update update) { final InlineKeyboardMarkup markup = new InlineKeyboardMarkup(); List<List<InlineKeyboardButton>> keyboard = new ArrayList<>(); keyboard.add(Arrays.asList(buttonMain())); markup.setKeyboard(keyboard); return markup; } private InlineKeyboardButton buttonMain() { final String OPEN_MAIN = "OM"; final String winking_face = new String(Character.toChars(0x1F609)); InlineKeyboardButton button = new InlineKeyboardButtonBuilder() .setText("!" + winking_face) .setCallbackData(new ActionBuilder(marshaller) .setName(OPEN_MAIN) .asString()) .build(); return button; } 


InlineKeyboardButtonBuilder
 public class InlineKeyboardButtonBuilder { private final InlineKeyboardButton button; public InlineKeyboardButtonBuilder(){ this.button = new InlineKeyboardButton(); } public InlineKeyboardButtonBuilder setText(String text){ button.setText(text); return this; } public InlineKeyboardButtonBuilder setCallbackData(String callbackData){ button.setCallbackData(callbackData); return this; } public InlineKeyboardButton build(){ return button; } } 


An interesting point is the installation of callback data. This data can be used when processing the pressing of buttons. In our case, the object serialized into JSON is written to the callback data. This method is heavyweight for this task, but it allows you to work with the return data without unnecessary problems for the conversion. Return data is generated in a special ActionBuilder ActionBuilder .

ActionBuilder source code
 public class Action { protected String name = ""; protected String id = ""; protected String value = ""; public String getName() { return name; } public void setName(String value) { this.name = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class ActionBuilder { private final DocumentMarshaller marshaller; private Action action = new Action(); public ActionBuilder(DocumentMarshaller marshaller) { this. marshaller = marshaller; } public ActionBuilder setName(String name) { action.setName(name); return this; } public ActionBuilder setValue(String name) { action.setValue(name); return this; } public String asString() { return marshaller.<Action>marshal(action, "Action"); } public Action build() { return action; } public Action build(Update update) { String data = update.getCallbackQuery().getData(); if (data == null) { return null; } action = marshaller.<Action>unmarshal(data, "Action"); if (action == null) { return null; } return action; } } 


In order for ActionBuilder to return JSON, it needs to pass a marshaller to it. Hereinafter, when the marshaller variable is mentioned, it is assumed that it is an object of a class that implements the DocumentMarshaller interface.

DocumentMarshaller source code
 public interface DocumentMarshaller { <T> String marshal(T document); <T> T unmarshal(String str); <T> T unmarshal(String str, Class clazz); } 


Marshalller, which is used in ActionBuilder , is implemented using Jackson .

Finally, the message is sent:

 try { if (answerMessage != null) { execute(answerMessage); } } catch (TelegramApiException ex) { Logger.getLogger(Bot.class.getName()) .log(Level.SEVERE, null, ex); } 

As a result, the welcome message looks like this.


Asking questions


It remained to do the most interesting thing - to implement the logic of the bot.
JPA was used to work with the database of questions. I will give the code of the entity classes.

Source Code for Entity Classes
 public abstract class Classifier implements Serializable { private static final long serialVersionUID = 1L; public Classifier() { } public abstract Long getId(); public abstract Integer getIsDeleted(); public abstract void setIsDeleted(Integer isDeleted); } @Entity @Table(name = "CLS_ANSWER", catalog = "QUEB", schema = "QUE") @XmlRootElement public class ClsAnswer extends Classifier implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") private Long id; @Column(name = "IS_DELETED") private Integer isDeleted; @Lob @Column(name = "ANSWER_TEXT") private String answerText; @Lob @Column(name = "ANSWER_COMMENT") private String answerComment; @OneToMany(cascade = CascadeType.ALL, mappedBy = "idAnswer") private Collection<RegQuestAnswer> regQuestAnswerCollection; @JoinColumn(name = "ID_QUEST", referencedColumnName = "ID") @ManyToOne(optional = false) private ClsQuest idQuest; public ClsAnswer() { } public ClsAnswer(Long id) { this.id = id; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Integer getIsDeleted() { return isDeleted; } public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; } public String getAnswerText() { return answerText; } public void setAnswerText(String answerText) { this.answerText = answerText; } public String getAnswerComment() { return answerComment; } public void setAnswerComment(String answerComment) { this.answerComment = answerComment; } @XmlTransient public Collection<RegQuestAnswer> getRegQuestAnswerCollection() { return regQuestAnswerCollection; } public void setRegQuestAnswerCollection(Collection<RegQuestAnswer> regQuestAnswerCollection) { this.regQuestAnswerCollection = regQuestAnswerCollection; } public ClsQuest getIdQuest() { return idQuest; } public void setIdQuest(ClsQuest idQuest) { this.idQuest = idQuest; } } @Entity @Table(name = "CLS_QUEST", catalog = "QUEB", schema = "QUE") @XmlRootElement public class ClsQuest extends Classifier implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") private Long id; @Column(name = "IS_DELETED") private Integer isDeleted; @Lob @Column(name = "QUEST_TEXT") private String questText; @OneToMany(cascade = CascadeType.ALL, mappedBy = "idQuest") private Collection<RegQuestAnswer> regQuestAnswerCollection; @OneToMany(cascade = CascadeType.ALL, mappedBy = "idQuest") private Collection<ClsAnswer> clsAnswerCollection; @OneToMany(cascade = CascadeType.ALL, mappedBy = "idQuest") private Collection<ClsQuestPhoto> clsQuestPhotoCollection; public ClsQuest() { } public ClsQuest(Long id) { this.id = id; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Integer getIsDeleted() { return isDeleted; } public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; } public String getQuestText() { return questText; } public void setQuestText(String questText) { this.questText = questText; } @XmlTransient public Collection<RegQuestAnswer> getRegQuestAnswerCollection() { return regQuestAnswerCollection; } public void setRegQuestAnswerCollection(Collection<RegQuestAnswer> regQuestAnswerCollection) { this.regQuestAnswerCollection = regQuestAnswerCollection; } @XmlTransient public Collection<ClsAnswer> getClsAnswerCollection() { return clsAnswerCollection; } public void setClsAnswerCollection(Collection<ClsAnswer> clsAnswerCollection) { this.clsAnswerCollection = clsAnswerCollection; } @XmlTransient public Collection<ClsQuestPhoto> getClsQuestPhotoCollection() { return clsQuestPhotoCollection; } public void setClsQuestPhotoCollection(Collection<ClsQuestPhoto> clsQuestPhotoCollection) { this.clsQuestPhotoCollection = clsQuestPhotoCollection; } } @Entity @Table(name = "CLS_QUEST_PHOTO", catalog = "QUEB", schema = "QUE") @XmlRootElement public class ClsQuestPhoto extends Classifier implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") private Long id; @Column(name = "IS_DELETED") private Integer isDeleted; @Lob @Column(name = "REL_FILE_PATH") private String relFilePath; @Lob @Column(name = "PHOTO_TEXT") private String photoText; @JoinColumn(name = "ID_QUEST", referencedColumnName = "ID") @ManyToOne(optional = false) private ClsQuest idQuest; public ClsQuestPhoto() { } public ClsQuestPhoto(Long id) { this.id = id; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Integer getIsDeleted() { return isDeleted; } public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; } public String getRelFilePath() { return relFilePath; } public void setRelFilePath(String relFilePath) { this.relFilePath = relFilePath; } public String getPhotoText() { return photoText; } public void setPhotoText(String photoText) { this.photoText = photoText; } public ClsQuest getIdQuest() { return idQuest; } public void setIdQuest(ClsQuest idQuest) { this.idQuest = idQuest; } } 


Also note that hereinafter, an object that implements the ClassifierRepository interface is used to access data, and when the classifierRepository variable is mentioned, it is implied that it is an object of the class that implements the ClassifierRepository interface

Source Code ClassifierRepository
 public interface ClassifierRepository { <T extends Classifier> void add(T classifier); <T extends Classifier> List<T> find(Class<T> clazz); <T extends Classifier> T find(Class<T> clazz, Long id); <T extends Classifier> List<T> find(Class<T> clazz, boolean isDeleted); <T extends Classifier> List<T> getAll(Class<T> clazz); <T extends Classifier> List<T> getAll(Class<T> clazz, boolean isDeleted); } 


Now let's move on to the moment when the “Start!” Button is pressed. At this very moment, the bot processes the next piece of incoming information and calls the previously mentioned method processCallbackQuery() . At the beginning of the method, the incoming update is processed, and the callback data is also retrieved. Based on the callback data, it is determined whether the button “Start!” OPEN_MAIN.equals(action.getName() , or the button to answer another question was pressed. GET_ANSWER.equals(action.getName()) .

 final String OPEN_MAIN = "OM"; final String GET_ANSWER = "GA"; Action action = new ActionBuilder(marshaller).buld(update); String data = update.getCallbackQuery().getData(); Long chatId = update.getCallbackQuery().getMessage().getChatId(); 

If the quiz has just begun, then you need to initialize the list of questions and ask the first question.

  if (OPEN_MAIN.equals(action.getName())) { initQuests(update); sendQuest(update); } 

Now consider initialization of the initQuests() list of questions:

 private void initQuests(Update update) { QuestStateHolder questStateHolder = new QuestStateHolder(); List<ClsQuest> q = classifierRepository.find(ClsQuest.class, false); Collections.shuffle(q); questStateHolder.put(update, new QuestEnumeration(q)); } 

In the initQuests method, initQuests first get all 26 questions, and then mix in random order. After that, we put the questions in QuestEnumeration , from where we will receive them one by one, until all 26 questions are received. QuestEnumeration we add to the object a special class QuestStateHolder , which stores the correspondence of the user and his current session of questions. The code for the QuestStateHolder and QuestEnumeration below.

QuestStateHolder and QuestEnumeration Source Code
 public class QuestStateHolder{ private Map<Integer, QuestEnumeration> questStates = new HashMap<>(); public QuestEnumeration get(User user) { return questStates.get(user.getId()) == null ? null : questStates.get(user.getId()); } public QuestEnumeration get(Update update) { User u = getUserFromUpdate(update); return get(u); } public void put(Update update, QuestEnumeration questEnumeration) { User u = getUserFromUpdate(update); put(u, questEnumeration); } public void put(User user, QuestEnumeration questEnumeration) { questStates.put(user.getId(), questEnumeration); } static User getUserFromUpdate(Update update) { return update.getMessage() != null ? update.getMessage().getFrom() : update.getCallbackQuery().getFrom(); } } public class QuestEnumeration implements Enumeration<ClsQuest>{ private List<ClsQuest> quests = new ArrayList<>(); private Integer currentQuest = 0; public QuestEnumeration(List<ClsQuest> quests){ this.quests.addAll(quests); } @Override public boolean hasMoreElements() { return currentQuest < quests.size(); } @Override public ClsQuest nextElement() { ClsQuest q = null; if (hasMoreElements()){ q = quests.get(currentQuest); currentQuest++; } return q; } public Integer getCurrentQuest(){ return currentQuest; } } 


After initialization, the first question is asked. But let's talk about this a little later. In the meantime, consider the situation when an answer came to an already asked question and the bot needs to send a comment relating to this answer. Everything is quite simple here, first we look for the answer in the database (the unique identifier of the answer option is stored in the CallbackData button that was clicked):

 Long answId = Long.parseLong(action.getValue()); ClsAnswer answ = classifierRepository.find(ClsAnswer.class, answId); 

Then we prepare a message based on the answer found and send it:
 SendMessage comment = new SendMessage(); comment.setParseMode("HTML"); comment.setText("<b> :</b> " + answ.getAnswerText() + "\n<b>  :</b> " + answ.getAnswerComment() + "\n"); comment.setChatId(chatId); execute(comment); 

Now consider the sendQuest method, which sends the next question. It all starts with the receipt of the next question:

 QuestEnumeration qe = questStateHolder.get(update); ClsQuest nextQuest = qe.nextElement(); 

If the Enumeration still contains elements, then we are preparing a question for sending, otherwise it is time to display a message about the end of the quiz. We send the question itself:
 Long chatId = update.getCallbackQuery().getMessage().getChatId(); SendMessage quest = new SendMessage(); quest.setParseMode("HTML"); quest.setText("<b> " + qe.getCurrentQuest() + ":</b> " + nextQuest.getQuestText()); quest.setChatId(chatId); execute(quest); 

Now send photos related to this issue:

 for (ClsQuestPhoto clsQuestPhoto : nextQuest.getClsQuestPhotoCollection()) { SendPhoto sendPhoto = new SendPhoto(); sendPhoto.setChatId(chatId); sendPhoto.setNewPhoto(new File("\\photo" + clsQuestPhoto.getRelFilePath())); sendPhoto(sendPhoto); } 

And finally, the answer options:

 SendMessage answers = new SendMessage(); answers.setParseMode("HTML"); answers.setText("<b> :</b>"); answers.setChatId(chatId); answers.setReplyMarkup(keyboardAnswer(update, nextQuest)); execute(answers); 

Keyboard with answer options is formed as follows

Source
 private InlineKeyboardMarkup keyboardAnswer(Update update, ClsQuest quest) { final InlineKeyboardMarkup markup = new InlineKeyboardMarkup(); List<List<InlineKeyboardButton>> keyboard = new ArrayList<>(); for (ClsAnswer clsAnswer : quest.getClsAnswerCollection()) { keyboard.add(Arrays.asList(buttonAnswer(clsAnswer))); } markup.setKeyboard(keyboard); return markup; } private InlineKeyboardButton buttonAnswer(ClsAnswer clsAnswer) { InlineKeyboardButton button = new InlineKeyboardButtonBuilder() .setText(clsAnswer.getAnswerText()) .setCallbackData(new ActionBuilder(marshaller) .setName(GET_ANSWER) .setValue(clsAnswer.getId().toString()) .asString()) .build(); return button; } 

The question has been sent. It will look something like this (the photos are a bit blurred so as not to embarrass anyone).


At that moment, when the questions are over, a message about the end of the quiz will be generated:

 SendMessage answers = new SendMessage(); answers.setParseMode("HTML"); answers.setText("<b>   !    </b> \n " + "      ''   /start"); answers.setChatId(chatId); execute(answers); 

And at the very end we will send a funny sticker:

 SendSticker sticker = new SendSticker(); sticker.setChatId(chatId); File stikerFile = new File("\\photo\\stiker.png"); sticker.setNewSticker(stikerFile); sendSticker(sticker); 


This completes the bot's work.

The work on all the described functionality was completed somewhere at 11 o'clock in the evening of the day preceding day X. I note that with some peculiarities in mind, I understood that the bot must be started at exactly 12 nights. In this regard, I experienced some time trouble (why I forgot about the history of the answers). In addition, a colleague needed to somehow inform about this quiz. For several reasons, I couldn’t just drop the link, so I trusted the alert to another bot (since during testing, the user ID was saved and the bot was free to write). At this the story of writing the bot ends.

Conclusion


There will be no deep conclusions from this story. The bot itself was accepted with interest and liked, so I tried for a reason. If we proceed to the technical part of the question, then the work on the bot allowed us to think about the fact that it is time to get rid of some crutches and bicycles, which I tried to do in the next bot.

I also want to note that the idea of ​​bots is very promising. There are many everyday tasks from ordering pizza to calling a taxi, which are proposed to be solved through mobile applications, websites and their mobile versions or phone calls to the operator. On the one hand, all these methods have proven their effectiveness, are convenient and are unlikely to change in the near future. On the other hand, applications for mobile devices, although rich in functionality, require installation, updating and studying their interface, and still tend to eat battery. Sites and their mobile versions require the user to at least switch to the browser and work with the new interface, which is not always convenient, especially on mobile devices. Telephone interaction is convenient for many, but does not imply any visualization in principle, and besides, the operator will always be a bottleneck of the system. When solving the same tasks, bots do not require installation of anything other than the messenger, do not require studying new interfaces, and allow the user to work asynchronously (as opposed to a phone call) in a relatively familiar messenger interface. The messenger in this case provides some kind of environment for client-server interaction, where the bot acts as the server, and the client part is implemented by means of the messenger. Of course, when working with bots, the user also faces various difficulties, somewhat similar to the difficulties of working with text interfaces, and in some ways due to the limitations of the instant messengers themselves. But still, bots that are focused on solving small everyday tasks (or on entertainment, like the bot described in this article) seem promising.

This is my first post, I would appreciate constructive feedback!

UPD: Filled the project on GitHub github.com/altmf/questbot

Source: https://habr.com/ru/post/346112/


All Articles