@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
na ipinapasa ko sa handler (sariling klase ng UpdatesReceiver):
public static void handleUpdates(Update update) {
...
if (update.hasMessage() && update.getMessage().hasText()){
log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
new TextMessageHandler(update, replyGenerator).handleTextMessage();
}
else if (update.hasCallbackQuery()) {
//логгирование
new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
}
else {
//логгирование
replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые messages!");
}
}
Ang UpdatesReceiver ay isang sentral na tagapangasiwa na, depende sa uri ng pag-update, ay naglilipat ng kontrol sa isa pang dalubhasang handler: TextMessageHandler o CallbackQueryHandler, kung saan ang mga konstruktor ay ipinapasa ko ang pag-update sa ibaba ng chain. Ang pag-update ay ang pinakamahalagang bagay kapag nagtatrabaho sa isang bot at hindi dapat mawala, dahil sa tulong ng impormasyong nakaimbak sa pag-update, nalaman namin kung sinong user at kung saang chat dapat ipadala ang tugon. Upang makabuo ng mga tugon sa gumagamit, nagsulat ako ng isang hiwalay na klase. Maaari itong magpadala ng regular na text message, mensahe na may inline na keyboard, mensahe na may larawan at mensahe na may reply keyboard. Ganito ang hitsura ng isang inline na keyboard: Tinutukoy nito ang mga button na, sa pamamagitan ng pag-click sa mga ito, nagpapadala ang user ng callback sa server, na maaaring iproseso sa halos parehong paraan tulad ng mga regular na mensahe. Upang "mapanatili" ito kailangan mo ng iyong sariling handler. Nagtakda kami ng aksyon para sa bawat button, na pagkatapos ay isusulat sa bagay na Update. Yung. para sa button na "Gastos" itinakda namin ang paglalarawan "/presyo" para sa callback, na makukuha namin sa ibang pagkakataon mula sa pag-update. Susunod, sa isang hiwalay na klase, maaari ko nang iproseso ang callback na ito:
public void handleCallBackQuery() {
String call_data = update.getCallbackQuery().getData();
long message_id = update.getCallbackQuery().getMessage().getMessageId();
long chat_id = update.getCallbackQuery().getMessage().getChatId();
switch (call_date){
case "/price" :
//тут что-то сделать
break;
...
Ang Reply keyboard ay ganito ang hitsura: At sa esensya, pinapalitan nito ang pag-type ng user. Ang pag-click sa button na "Library" ay mabilis na magpapadala ng mensaheng "Library" sa bot. Para sa bawat uri ng keyboard, isinulat ko ang sarili kong klase, na ipinapatupad ang pattern ng Builder: inline at reply . Bilang resulta, maaari mong mahalagang "iguhit" ang nais na keyboard depende sa iyong mga kinakailangan. Ito ay lubhang maginhawa, dahil ang mga keyboard ay maaaring magkakaiba, ngunit ang prinsipyo ay nananatiling pareho. Narito ang isang intuitive na paraan upang magpadala ng mensahe gamit ang isang inline na keyboard:
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
.setText("Вы может узнать следующую информацию об игре " + gameTitle)
.row()
.button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
.button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
.button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
.endRow()
.row()
.button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
.button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
.button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
.endRow()
.row()
.button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
.endRow()
.row()
.button("Скрыть клавиатуру", "close")
.endRow()
.build();
try {
execute(keyboard);
} catch (TelegramApiException e) {
log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
}
}
Upang bigyan ang bot ng mahigpit na pagpapagana, ang mga espesyal na command gamit ang slash character ay naimbento: /library, /help, /game, atbp. Kung hindi, kailangan naming iproseso ang anumang basura na maaaring isulat ng user. Sa totoo lang, ito ang isinulat ng MessageHandler:
if (message.equals(ChatCommands.START.getDescription())) {
replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
|| message.equalsIgnoreCase("Помощь")) {
replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
...
Kaya, depende sa kung anong utos ang ipinadala mo sa bot, isang espesyal na handler ang isasama sa trabaho. Pumunta pa tayo at tingnan ang gawain ng parser at library. Kung padadalhan mo ang bot ng link sa isang laro sa Google Play store, awtomatikong gagana ang isang espesyal na handler . Bilang tugon, makakatanggap ang user ng impormasyon tungkol sa laro sa sumusunod na anyo: Kasabay nito, tatawagin ang isang paraan na susubukang idagdag ang laro sa library ng bot (una sa lokal na mapa, pagkatapos ay sa -> json file ). Kung nasa library na ang laro, isasagawa ang tseke (tulad ng sa isang regular na hashmap), at kung ang data ng field (halimbawa, nagbago ang numero ng bersyon), ma-overwrite ang laro sa library. Kung walang mga pagbabagong nakita, walang mga entry na gagawin. Kung walang laro sa library, ito ay unang nakasulat sa lokal na mapa (isang bagay tulad ng tyk ), at pagkatapos ay isinulat sa isang json file, dahil kung ang application sa server ay hindi inaasahang sarado, ang data ay magiging nawala, ngunit maaari itong palaging basahin gamit ang file. Sa totoo lang, kapag nagsimula ang programa, ang library ay palaging na-load sa unang pagkakataon mula sa isang file mula sa isang static na bloke:
static {
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);
try {
Path path = Paths.get(LIBRARY_PATH);
if (!Files.exists(path)) {
Files.createDirectories(path.getParent());
Files.createFile(path);
log.info("[Файл библиотеки создан]");
}
else {
ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
games.putAll(temporary);
log.info("[Количество игр в загруженной библиотеке] = " + games.size());
}
}
catch (IOException e) {
log.error("[Ошибка при чтении/записи file] {}", e.getMessage());
}
}
Dito kailangan mong magbasa ng data mula sa file sa isang pansamantalang mapa, na pagkatapos ay "kopyahin" sa isang buong mapa upang mapanatili ang case insensitivity kapag naghahanap ng isang laro sa file (sa pamamagitan ng pagsulat ng tITan QuEST, mahahanap pa rin ng bot ang ang larong Titan Quest sa library). Hindi posible na makahanap ng isa pang solusyon, ito ang mga tampok ng deserialization gamit ang Jackson. Kaya, sa bawat kahilingan para sa isang link, ang laro ay idinagdag sa library, kung maaari, at sa gayon ay lumalawak ang library. Ang karagdagang impormasyon tungkol sa isang partikular na laro ay maaaring makuha gamit ang command na /libraryGame_Name. Maaari mong malaman ang parehong isang partikular na parameter (halimbawa, ang kasalukuyang bersyon) at lahat ng mga parameter nang sabay-sabay. Ito ay ipinatupad gamit ang inline na keyboard, na tinalakay kanina. Sa panahon ng trabaho, inilapat ko rin ang mga kasanayang nakuha dito habang nilulutas ang mga problema. Halimbawa, isang listahan ng mga pangalan ng mga random na laro na matatagpuan sa library (ang opsyon ay magagamit gamit ang /library command):
private String getRandomTitles(){
if (LibraryService.getLibrary().size() < 10){
return String.join("\n", LibraryService.getLibrary().keySet());
}
List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
Collections.shuffle(keys);
List<string> randomKeys = keys.subList(0, 10);
return String.join("\n", randomKeys);
}
Paano nagli-link ang proseso ng bot? Sinusuri sila nito upang makita kung kabilang sila sa Google Play (host, protocol, port):
private static class GooglePlayCorrectURL {
private static final String VALID_HOST = "play.google.com";
private static final String VALID_PROTOCOL = "https";
private static final int VALID_PORT = -1;
private static boolean isLinkValid(URI link) {
return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
}
private static boolean isProtocolExist(URI link) {
if (link.getScheme() != null) {
return link.getScheme().equals(VALID_PROTOCOL);
}
else {
return false;
}
}
private static boolean isHostExist(URI link) {
if (link.getHost() != null) {
return link.getHost().equals(VALID_HOST);
}
else {
return false;
}
}
Kung maayos ang lahat, kumokonekta ang bot sa pamamagitan ng isang link gamit ang library ng Jsoup, na nagbibigay-daan sa iyong makuha ang HTML code ng pahina, na napapailalim sa karagdagang pagsusuri at pag-parse. Hindi mo magagawang lokohin ang bot gamit ang isang hindi tama o nakakapinsalang link.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Dito kailangan naming lutasin ang isang problema sa mga setting ng rehiyon. Kumokonekta ang bot sa Google Play store mula sa isang server na matatagpuan sa Europe, kaya bubukas ang page sa Google Play store sa naaangkop na wika. Kinailangan kong magsulat ng saklay na pipilitin ang isang "pag-redirect" sa Russian na bersyon ng pahina (ang proyekto ay, pagkatapos ng lahat, ay naglalayong sa aming madla). Upang gawin ito, sa dulo ng link kailangan mong maingat na idagdag ang parameter na hl: &hl=ru sa kahilingan sa GET sa server ng Google Play .
private String forceToRusLocalization(String URL) {
if (URL.endsWith("&hl=ru")){
return URL;
}
else {
if (URL.contains("&hl=")){
URL = URL.replace(
URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
}
else {
URL += "&hl=ru";
}
}
return URL;
}
Pagkatapos ng matagumpay na koneksyon, nakatanggap kami ng HTML na dokumento na handa para sa pagsusuri at pag-parse, ngunit ito ay lampas sa saklaw ng artikulong ito. Ang parser code ay narito . Kinukuha mismo ng parser ang kinakailangang impormasyon at lumilikha ng isang bagay na may laro, na sa kalaunan ay idinagdag sa library kung kinakailangan. <h2>Upang buod</h2>Sinusuportahan ng bot ang ilang command na naglalaman ng ilang partikular na pagpapagana. Tumatanggap ito ng mga mensahe mula sa user at itinutugma ang mga ito sa mga utos nito. Kung ito ay isang link o ang command na /game + link, sinusuri nito ang link na iyon upang makita kung ito ay kabilang sa Google Play. Kung tama ang link, kumokonekta ito sa pamamagitan ng Jsoup at tinatanggap ang HTML na dokumento. Sinusuri ang dokumentong ito batay sa nakasulat na parser. Ang kinakailangang impormasyon tungkol sa laro ay nakuha mula sa dokumento, at pagkatapos ay ang bagay na may laro ay puno ng data na ito. Susunod, ang bagay na may laro ay inilalagay sa lokal na imbakan (kung ang laro ay wala pa doon) at agad na nakasulat sa isang file upang maiwasan ang pagkawala ng data. Ang isang laro na naitala sa library (ang pangalan ng laro ay ang susi para sa mapa, ang bagay na may laro ay ang halaga para sa mapa) ay maaaring makuha gamit ang command /library Game_name. Kung ang tinukoy na laro ay matatagpuan sa library ng bot, ibabalik sa user ang isang inline na keyboard, kung saan makakakuha siya ng impormasyon tungkol sa laro. Kung hindi natagpuan ang laro, dapat mong tiyakin na tama ang spelling ng pangalan (dapat itong ganap na tumugma sa pangalan ng laro sa Google Play store, maliban sa kaso), o idagdag ang laro sa library sa pamamagitan ng pagpapadala ng bot isang link sa laro. Na-deploy ko ang bot sa heroku at para sa mga nagplano sa hinaharap na magsulat ng kanilang sariling bot at i-host ito nang libre sa heroku, magbibigay ako ng ilang rekomendasyon para sa paglutas ng mga paghihirap na maaari mong makaharap (mula nang nakatagpo ko sila mismo). Sa kasamaang palad, dahil sa likas na katangian ng Heroku, ang bot library ay patuloy na "i-reset" isang beses bawat 24 na oras. Hindi sinusuportahan ng aking plano ang pag-iimbak ng mga file sa mga server ng Heroku, kaya hinila lang nito ang aking file ng laro mula sa Github. Mayroong ilang mga solusyon: gumamit ng database, o maghanap ng isa pang server na mag-iimbak ng file na ito kasama ng laro. Nagpasya akong huwag gumawa ng anuman sa ngayon, dahil sa esensya ang bot ay hindi gaanong kapaki-pakinabang. Kailangan ko ito sa halip upang makakuha ng isang buong karanasan, na karaniwang kung ano ang aking nakamit. Kaya, mga rekomendasyon para kay Heroku:
-
Malamang na kailangan mong magparehistro sa heroku gamit ang isang VPN kung nakatira ka sa Russia.
-
Sa ugat ng proyekto kailangan mong maglagay ng file na walang extension na tinatawag na Procfile. Ang nilalaman nito ay dapat na ganito: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
Sa pom.xml, idagdag ang mga sumusunod na linya ayon sa halimbawa , kung saan sa mainClass tag ay ipahiwatig ang path sa klase na naglalaman ng pangunahing paraan: bot.BotApplication (kung ang BotApplication class ay nasa bot folder).
-
Huwag bumuo ng anumang proyekto gamit ang mvn package commands, atbp., Heroku ay mag-assemble ng lahat para sa iyo.
-
Maipapayo na magdagdag ng gitignore sa proyekto, halimbawa ito:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Talagang i-upload ang proyekto sa github, at pagkatapos ay ikonekta ang repositoryo sa Heroku (o gumamit ng iba pang mga pamamaraan, mayroong 3 sa kanila, kung hindi ako nagkakamali).
-
Kung matagumpay ang pag-download ("Nagtagumpay ang pagbuo"), tiyaking pumunta sa I-configure ang Dynos:
at ilipat ang slider, at pagkatapos ay siguraduhin na ito ay nasa ON na posisyon (dahil sa ang katunayan na hindi ko ginawa ito, ang aking bot ay hindi gumana at ako ay sinarado ang aking utak sa loob ng ilang araw at gumawa ng maraming hindi kinakailangang paggalaw ).
-
Itago ang bot token sa Github. Upang gawin ito, kailangan mong makuha ang token mula sa variable ng kapaligiran:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
At pagkatapos na i-deploy ang bot, itakda ang variable na ito sa Heroku dashboard sa tab na Mga Setting (sa kanan ng TOKEN magkakaroon ng VALUE field, kopyahin ang token ng iyong bot doon):
- nakatanggap ng isang ganap na gumaganang proyekto na nakasulat sa Java;
- natutong magtrabaho sa third-party na API (Telegram Bot API);
- sa pagsasagawa, mas malalim akong nagsagawa ng serialization, nagtrabaho nang husto sa JSON at sa Jackson library (sa una ay ginamit ko ang GSON, ngunit may mga problema dito);
- pinalakas ang aking mga kasanayan kapag nagtatrabaho sa mga file, nakilala ang Java NIO;
- natutong gumawa ng configuration na .xml na mga file at sanay akong mag-log;
- pinahusay na kasanayan sa development environment (IDEA);
- natutong magtrabaho sa git at natutunan ang halaga ng gitignore;
- nakakuha ng mga kasanayan sa pag-parse ng web page (Jsoup library);
- natutunan at ginamit ang ilang mga pattern ng disenyo;
- bumuo ng isang pakiramdam at pagnanais na mapabuti ang code (refactoring);
- Natuto akong humanap ng mga solusyon online at huwag mahiyang magtanong ng mga tanong na hindi ko mahanap ang sagot.
GO TO FULL VERSION