CLS_QUEST
- a table containing the texts of the questionsCLS_QUEST_PHOTO
- a table containing relative paths to photos that are associated with the question being asked; the photos themselves are in the file system in folders corresponding to the issue.CLS_ANSWER
- a table containing answers to a question, as well as comments for each answer. 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) );
org.telegram.telegrambots.bots.TelegramLongPollingBot
class. 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.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).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. 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); } } } }
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.processCommand
method. 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));
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); }
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; }
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; } }
ActionBuilder
. 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; } }
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. public interface DocumentMarshaller { <T> String marshal(T document); <T> T unmarshal(String str); <T> T unmarshal(String str, Class clazz); }
ActionBuilder
, is implemented using Jackson . try { if (answerMessage != null) { execute(answerMessage); } } catch (TelegramApiException ex) { Logger.getLogger(Bot.class.getName()) .log(Level.SEVERE, null, ex); }
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; } }
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 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); }
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 (OPEN_MAIN.equals(action.getName())) { initQuests(update); sendQuest(update); }
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)); }
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. 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; } }
CallbackData
button that was clicked): Long answId = Long.parseLong(action.getValue()); ClsAnswer answ = classifierRepository.find(ClsAnswer.class, answId);
SendMessage comment = new SendMessage(); comment.setParseMode("HTML"); comment.setText("<b> :</b> " + answ.getAnswerText() + "\n<b> :</b> " + answ.getAnswerComment() + "\n"); comment.setChatId(chatId); execute(comment);
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();
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);
for (ClsQuestPhoto clsQuestPhoto : nextQuest.getClsQuestPhotoCollection()) { SendPhoto sendPhoto = new SendPhoto(); sendPhoto.setChatId(chatId); sendPhoto.setNewPhoto(new File("\\photo" + clsQuestPhoto.getRelFilePath())); sendPhoto(sendPhoto); }
SendMessage answers = new SendMessage(); answers.setParseMode("HTML"); answers.setText("<b> :</b>"); answers.setChatId(chatId); answers.setReplyMarkup(keyboardAnswer(update, nextQuest)); execute(answers);
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; }
SendMessage answers = new SendMessage(); answers.setParseMode("HTML"); answers.setText("<b> ! </b> \n " + " '' /start"); answers.setChatId(chatId); execute(answers);
SendSticker sticker = new SendSticker(); sticker.setChatId(chatId); File stikerFile = new File("\\photo\\stiker.png"); sticker.setNewSticker(stikerFile); sendSticker(sticker);
Source: https://habr.com/ru/post/346112/
All Articles