@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
mən işləyiciyə ötürdüm (öz UpdatesReceiver sinfi):
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!");
}
}
UpdatesReceiver mərkəzi idarəedicidir ki, yeniləmənin növündən asılı olaraq idarəetməni başqa ixtisaslaşdırılmış işləyiciyə ötürür: TextMessageHandler və ya CallbackQueryHandler, mən onların konstruktorlarına yeniləməni zəncirin aşağısına ötürürəm. Yeniləmə botla işləyərkən ən vacib şeydir və itirilməməlidir, çünki yeniləmədə saxlanılan məlumatların köməyi ilə biz cavabın hansı istifadəçiyə və hansı çata göndərilməsi lazım olduğunu öyrənirik. İstifadəçiyə cavab yaratmaq üçün ayrıca sinif yazdım. O, müntəzəm mətn mesajı, daxili klaviatura ilə mesaj, şəkilli mesaj və cavab klaviaturası ilə mesaj göndərə bilər. Daxil olan klaviatura belə görünür: O, düymələri müəyyən edir ki, onların üzərinə klikləməklə istifadəçi serverə geri çağırış göndərir ki, bu da demək olar ki, adi mesajlarla eyni şəkildə işlənə bilər. Onu "saxlamaq" üçün sizə öz işləyiciniz lazımdır. Hər bir düymə üçün bir hərəkət təyin etdik, sonra bu, Update obyektinə yazılır. Bunlar. "Xərc" düyməsi üçün biz geri çağırış üçün "/qiymət" təsvirini təyin etdik, sonra yeniləmədən əldə edə bilərik. Sonra, ayrı bir sinifdə mən artıq bu geri çağırışı emal edə bilərəm:
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;
...
Cavab klaviaturası belə görünür: Və mahiyyət etibarilə o, istifadəçinin yazmasını əvəz edir. "Kitabxana" düyməsini sıxmaqla bota tez bir zamanda "Kitabxana" mesajı göndəriləcək. Hər bir klaviatura növü üçün Builder modelini tətbiq edərək öz sinfimi yazdım: inline and reply . Nəticədə, tələblərinizdən asılı olaraq istədiyiniz klaviaturanı mahiyyətcə “çəkə” bilərsiniz. Bu olduqca rahatdır, çünki klaviaturalar fərqli ola bilər, lakin prinsip eyni olaraq qalır. Budur, daxili klaviatura ilə mesaj göndərmək üçün intuitiv üsul:
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());
}
}
Bota ciddi funksionallıq vermək üçün kəsik işarəsindən istifadə edən xüsusi əmrlər icad edilmişdir: /library, /help, /oyun və s. Əks halda, istifadəçinin yaza biləcəyi zibilləri emal etməli olacağıq. Əslində, MessageHandler bunun üçün yazılmışdır:
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());
}
...
Beləliklə, bota hansı əmri göndərdiyinizdən asılı olaraq işə xüsusi işləyici daxil ediləcək. Gəlin daha da irəli gedək və təhlilçinin və kitabxananın işinə baxaq. Bota Google Play mağazasındakı oyuna keçid göndərsəniz, avtomatik olaraq xüsusi işləyici işləyəcək . Cavab olaraq, istifadəçi oyun haqqında məlumatı aşağıdakı formada alacaq: Eyni zamanda, oyunu botun kitabxanasına əlavə etməyə çalışacaq bir üsul çağırılacaq (əvvəlcə yerli xəritəyə, sonra -> json faylına ). Əgər oyun artıq kitabxanadadırsa, yoxlama aparılacaq (adi bir heşməpdə olduğu kimi) və sahə məlumatları (məsələn, versiya nömrəsi dəyişibsə), kitabxanadakı oyun üzərinə yazılacaq. Heç bir dəyişiklik aşkar edilmədikdə, heç bir giriş edilməyəcək. Kitabxanada ümumiyyətlə oyun yox idisə, o, əvvəlcə yerli xəritəyə ( tyk kimi bir obyekt ) yazılır və sonra json faylına yazılır, çünki serverdəki proqram gözlənilmədən bağlanarsa, məlumatlar itirilmiş, lakin həmişə fayldan istifadə edərək oxuna bilər. Əslində, proqram başlayanda kitabxana həmişə statik blokdan olan fayldan ilk dəfə yüklənir:
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());
}
}
Burada əlavə olaraq fayldakı məlumatları müvəqqəti xəritəyə oxumalısınız, sonra faylda oyun axtararkən hərflərə həssaslığı qorumaq üçün tam xəritəyə “kopyalanır” (tITan QuEST yazmaqla, bot yenə də tapacaq. Kitabxanada Titan Quest oyunu). Başqa bir həll tapmaq mümkün olmadı, bunlar Ceksondan istifadə edərək deserializasiyanın xüsusiyyətləridir. Beləliklə, hər bir keçid sorğusu ilə oyun, mümkünsə, kitabxanaya əlavə olunur və bununla da kitabxana genişlənir. Müəyyən bir oyun haqqında əlavə məlumatı /libraryGame_Name əmrindən istifadə etməklə əldə etmək olar . Həm müəyyən bir parametri (məsələn, cari versiya), həm də bütün parametrləri bir anda tapa bilərsiniz. Bu, əvvəllər müzakirə edilən daxili klaviaturadan istifadə etməklə həyata keçirilir. İş zamanı burada əldə etdiyim bacarıqları problemləri həll edərkən də tətbiq etdim. Məsələn, kitabxanada yerləşən təsadüfi oyunların adlarının siyahısı (seçim /library əmrindən istifadə etməklə mövcuddur):
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);
}
Bot keçidləri necə emal edir? O, onların Google Play-ə (host, protokol, port) aid olub-olmadığını yoxlayır:
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;
}
}
Əgər hər şey qaydasındadırsa, o zaman bot Jsoup kitabxanasından istifadə edərək link vasitəsilə qoşulur ki, bu da səhifənin əlavə təhlil və təhlilə məruz qalan HTML kodunu əldə etməyə imkan verir. Siz səhv və ya zərərli link ilə botu aldada bilməyəcəksiniz.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Burada regional parametrlərlə bağlı problemi həll etməli olduq. Bot Avropada yerləşən serverdən Google Play mağazasına qoşulur, buna görə də Google Play mağazasındakı səhifə müvafiq dildə açılır. Səhifənin rus versiyasına zorla “yönləndirən” qoltuqağacı yazmalı oldum (hər şeydən sonra layihə bizim auditoriyaya yönəlmişdi). Bunu etmək üçün, linkin sonunda hl parametrini diqqətlə əlavə etməlisiniz: &hl=ru Google Play serverinə GET sorğusunda .
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;
}
Uğurlu əlaqədən sonra təhlil və təhlil üçün hazır HTML sənədi alırıq, lakin bu, bu məqalənin əhatə dairəsindən kənardadır. Parser kodu buradadır . Təhlilçi özü lazımi məlumatları əldə edir və oyunla bir obyekt yaradır, lazım olduqda sonradan kitabxanaya əlavə olunur. <h2>Xülasə etmək</h2>Bot müəyyən funksiyaları ehtiva edən bir neçə əmri dəstəkləyir. İstifadəçidən mesajlar alır və onları öz əmrləri ilə uyğunlaşdırır. Əgər bu, keçid və ya /oyun + link əmridirsə, o, həmin linkin Google Play-ə aid olub-olmadığını yoxlayır. Əgər keçid düzgündürsə, o, Jsoup vasitəsilə qoşulur və HTML sənədini alır. Bu sənəd yazılı təhlil əsasında təhlil edilir. Oyun haqqında lazımi məlumatlar sənəddən çıxarılır və sonra oyun olan obyekt bu məlumatlarla doldurulur. Sonra, oyun olan obyekt yerli yaddaşa yerləşdirilir (əgər oyun hələ orada deyilsə) və məlumat itkisinin qarşısını almaq üçün dərhal fayla yazılır. Kitabxanada qeydə alınmış oyunu (oyunun adı xəritə üçün açardır, oyunu olan obyekt xəritə üçün dəyərdir) /library Game_name əmrindən istifadə etməklə əldə edilə bilər. Göstərilən oyun botun kitabxanasında tapılarsa, istifadəçiyə oyun haqqında məlumat ala biləcəyi daxili klaviatura qaytarılacaq. Oyun tapılmırsa, ya adın düzgün yazıldığından əmin olmalısınız (hal istisna olmaqla, Google Play mağazasındakı oyunun adı ilə tam uyğun olmalıdır), ya da botu göndərərək oyunu kitabxanaya əlavə etməlisiniz. oyuna keçid. Mən botu heroku-da yerləşdirdim və gələcəkdə öz botunu yazmağı və onu heroku-da pulsuz yerləşdirməyi planlaşdıranlar üçün qarşılaşa biləcəyiniz çətinlikləri həll etmək üçün bir neçə tövsiyə verəcəyəm (özüm onlarla qarşılaşdığım üçün). Təəssüf ki, Herokunun təbiətinə görə, bot kitabxanası hər 24 saatda bir dəfə daim “sıfırlanır”. Planım Heroku serverlərində faylların saxlanmasını dəstəkləmir, ona görə də o, sadəcə olaraq oyun faylımı Github-dan çıxarır. Bir neçə həll yolu var idi: verilənlər bazasından istifadə edin və ya bu faylı oyunla saxlayacaq başqa server axtarın. Hələlik heç nə etməməyə qərar verdim, çünki mahiyyətcə bot o qədər də faydalı deyil. Mənə daha çox tam təcrübə qazanmaq üçün lazım idi, bu, mənim əldə etdiyim şeydir. Beləliklə, Heroku üçün tövsiyələr:
-
Rusiyada yaşayırsınızsa, çox güman ki, VPN istifadə edərək heroku-da qeydiyyatdan keçməli olacaqsınız.
-
Layihənin kökündə Procfile adlı uzantısız bir fayl qoymaq lazımdır. Onun məzmunu belə olmalıdır: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
pom.xml-də nümunəyə uyğun olaraq aşağıdakı sətirləri əlavə edin , burada mainClass teqində əsas metodu ehtiva edən sinfə gedən yol göstərilir: bot.BotApplication (əgər BotApplication sinfi bot qovluğundadırsa).
-
Mvn paket əmrləri və s. istifadə edərək heç bir layihə qurmayın, Heroku sizin üçün hər şeyi yığacaq.
-
Layihəyə gitignore əlavə etmək məsləhətdir, məsələn:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Əslində layihəni github-a yükləyin və sonra deponu Heroku-ya qoşun (və ya başqa üsullardan istifadə edin, səhv etmirəmsə, onlardan 3-ü var).
-
Endirmə uğurlu olarsa ("Quraşdırma müvəffəqiyyətli"), Dynos'u konfiqurasiya edin:
və kaydırıcıyı dəyişdirin və sonra onun ON vəziyyətində olduğuna əmin olun (bunu etmədiyim üçün botum işləmədi və bir neçə gün beynimi sındırdım və çoxlu lazımsız hərəkətlər etdim. ).
-
Github-da bot nişanını gizlədin. Bunu etmək üçün mühit dəyişənindən nişan almalısınız:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Sonra botu yerləşdirdikdən sonra bu dəyişəni Parametrlər sekmesindəki Heroku idarə panelində təyin edin (TOKEN-in sağında DƏYƏR sahəsi olacaq, botun işarəsini ora köçürün):
- Java-da yazılmış tam işləyən bir layihə aldı;
- üçüncü tərəf API (Telegram Bot API) ilə işləməyi öyrəndi;
- praktikada seriallaşdırmanı daha dərindən öyrəndim, JSON və Cekson kitabxanası ilə çox işlədim (əvvəlcə mən GSON-dan istifadə etdim, lakin bununla bağlı problemlər var idi);
- fayllarla işləyərkən bacarıqlarımı gücləndirdim, Java NIO ilə tanış oldum;
- konfiqurasiya .xml faylları ilə işləməyi öyrəndim və özümü girişə öyrəşdim;
- inkişaf mühitində (IDEA) təkmilləşdirilmiş bacarıq;
- git ilə işləməyi öyrəndi və gitignore-un dəyərini öyrəndi;
- veb səhifənin təhlili (Jsoup kitabxanası) üzrə bacarıqlar əldə etdi;
- bir neçə dizayn nümunələrini öyrəndi və istifadə etdi;
- kodu təkmilləşdirmək hissi və istəyi inkişaf etdirdi (refaktorinq);
- Həll yollarını onlayn tapmağı və cavab tapa bilmədiyim sualları verməkdən çəkinməməyi öyrəndim.
GO TO FULL VERSION