@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
yang saya berikan ke handler (kelas UpdatesReceiver sendiri):
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 adalah penangan pusat yang, bergantung pada jenis pembaruan, mentransfer kontrol ke penangan khusus lainnya: TextMessageHandler atau CallbackQueryHandler, kepada konstruktornya saya meneruskan pembaruan lebih jauh ke bawah rantai. Pembaruan adalah hal terpenting ketika bekerja dengan bot dan tidak dapat hilang, karena dengan bantuan informasi yang disimpan dalam pembaruan, kami mengetahui pengguna mana dan ke obrolan mana respons harus dikirim. Untuk menghasilkan tanggapan kepada pengguna, saya menulis kelas terpisah. Dapat mengirim pesan teks biasa, pesan dengan keyboard inline, pesan dengan gambar dan pesan dengan keyboard balasan. Keyboard sebaris terlihat seperti ini: Ini mendefinisikan tombol yang, dengan mengkliknya, pengguna mengirimkan panggilan balik ke server, yang dapat diproses dengan cara yang hampir sama seperti pesan biasa. Untuk “mempertahankannya” Anda memerlukan pengendali Anda sendiri. Kami menetapkan tindakan untuk setiap tombol, yang kemudian ditulis ke objek Update. Itu. untuk tombol "Biaya" kita mengatur deskripsi "/harga" untuk panggilan balik, yang nantinya bisa kita dapatkan dari pembaruan. Selanjutnya, di kelas terpisah, saya sudah bisa memproses panggilan balik ini:
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;
...
Papan ketik Balasan terlihat seperti ini: Dan intinya, ini menggantikan pengetikan pengguna. Mengklik tombol "Perpustakaan" akan dengan cepat mengirimkan pesan "Perpustakaan" ke bot. Untuk setiap jenis keyboard, saya menulis kelas saya sendiri, menerapkan pola Builder: inline dan reply . Hasilnya, pada dasarnya Anda dapat “menggambar” keyboard yang diinginkan tergantung pada kebutuhan Anda. Ini sangat nyaman, karena keyboardnya mungkin berbeda, tetapi prinsipnya tetap sama. Berikut adalah metode intuitif untuk mengirim pesan dengan keyboard inline:
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());
}
}
Untuk memberikan fungsionalitas yang ketat pada bot, perintah khusus menggunakan karakter garis miring diciptakan: /library, /help, /game, dll. Jika tidak, kami harus memproses sampah apa pun yang mungkin ditulis pengguna. Sebenarnya, untuk inilah MessageHandler ditulis:
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());
}
...
Jadi, bergantung pada perintah apa yang Anda kirimkan ke bot, penangan khusus akan disertakan dalam pekerjaan tersebut. Mari melangkah lebih jauh dan melihat pekerjaan parser dan perpustakaan. Jika Anda mengirimkan bot tautan ke game di Google Play Store, penangan khusus akan otomatis berfungsi . Sebagai tanggapan, pengguna akan menerima informasi tentang game dalam bentuk berikut: Pada saat yang sama, sebuah metode akan dipanggil yang akan mencoba menambahkan game ke perpustakaan bot (pertama ke peta lokal, lalu ke -> file json ). Jika game sudah ada di perpustakaan, maka akan dilakukan pengecekan (seperti pada hashmap biasa), dan jika data lapangan (misalnya nomor versi telah berubah), maka game di perpustakaan akan ditimpa. Jika tidak ada perubahan yang terdeteksi, maka tidak ada entri yang dibuat. Jika tidak ada permainan di perpustakaan sama sekali, maka pertama-tama ditulis ke peta lokal (objek seperti tyk ), dan kemudian ditulis ke file json, karena jika aplikasi di server ditutup secara tidak terduga, datanya akan menjadi hilang, tetapi selalu dapat dibaca menggunakan file tersebut. Sebenarnya, ketika program dimulai, perpustakaan selalu dimuat untuk pertama kalinya dari file dari blok statis:
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());
}
}
Di sini Anda juga harus membaca data dari file ke dalam peta sementara, yang kemudian "disalin" ke dalam peta lengkap untuk menjaga ketidakpekaan huruf besar-kecil saat mencari game di file tersebut (dengan menulis tITan QuEST, bot akan tetap menemukannya permainan Titan Quest di perpustakaan). Tidak mungkin menemukan solusi lain, ini adalah fitur deserialisasi menggunakan Jackson. Jadi, dengan setiap permintaan tautan, game tersebut ditambahkan ke perpustakaan, jika memungkinkan, dan perpustakaan tersebut diperluas. Informasi lebih lanjut tentang game tertentu dapat diperoleh dengan menggunakan perintah /libraryGame_Name. Anda dapat mengetahui parameter tertentu (misalnya, versi saat ini) dan semua parameter sekaligus. Ini diimplementasikan menggunakan keyboard inline, yang telah dibahas sebelumnya. Selama bekerja, saya juga menerapkan keterampilan yang diperoleh di sini saat memecahkan masalah. Misalnya, daftar nama permainan acak yang terletak di perpustakaan (opsi tersedia menggunakan perintah /library):
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);
}
Bagaimana cara bot memproses tautan? Ia memeriksanya untuk melihat apakah mereka milik Google Play (host, protokol, 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;
}
}
Jika semuanya beres, maka bot terhubung melalui tautan menggunakan perpustakaan Jsoup, yang memungkinkan Anda mendapatkan kode HTML halaman tersebut, yang akan dianalisis dan diurai lebih lanjut. Anda tidak akan bisa menipu bot dengan tautan yang salah atau berbahaya.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Di sini kami harus menyelesaikan masalah dengan pengaturan regional. Bot terhubung ke Google Play Store dari server yang berlokasi di Eropa, sehingga halaman di Google Play Store terbuka dalam bahasa yang sesuai. Saya harus menulis sebuah penopang yang akan memaksa “pengalihan” ke halaman versi Rusia (bagaimanapun juga, proyek ini ditujukan untuk audiens kami). Untuk melakukan ini, di akhir tautan, Anda perlu menambahkan parameter hl: &hl=ru dengan hati-hati ke server Google Play dalam permintaan GET .
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;
}
Setelah koneksi berhasil, kami menerima dokumen HTML yang siap untuk dianalisis dan diurai, tetapi ini di luar cakupan artikel ini. Kode parsernya ada di sini . Parser itu sendiri mengambil informasi yang diperlukan dan membuat objek dengan permainan, yang kemudian ditambahkan ke perpustakaan jika perlu. <h2>Ringkasannya</h2>Bot mendukung beberapa perintah yang berisi fungsi tertentu. Ia menerima pesan dari pengguna dan mencocokkannya dengan perintahnya. Jika berupa tautan atau perintah /game + link, ia akan memeriksa tautan tersebut untuk melihat apakah tautan tersebut milik Google Play. Jika tautannya benar, maka terhubung melalui Jsoup dan menerima dokumen HTML. Dokumen ini dianalisis berdasarkan parser tertulis. Informasi yang diperlukan tentang game tersebut diekstraksi dari dokumen, dan kemudian objek dengan game tersebut diisi dengan data ini. Selanjutnya objek yang berisi game tersebut ditempatkan di penyimpanan lokal (jika game tersebut belum ada) dan segera ditulis ke file untuk menghindari kehilangan data. Sebuah game yang direkam di perpustakaan (nama game adalah kunci peta, objek dengan game tersebut adalah nilai peta) dapat diperoleh dengan menggunakan perintah /library Game_name. Jika game yang ditentukan ditemukan di perpustakaan bot, pengguna akan dikembalikan keyboard inline, yang dengannya dia bisa mendapatkan informasi tentang game tersebut. Jika game tidak ditemukan, Anda harus memastikan namanya dieja dengan benar (harus benar-benar cocok dengan nama game di Google Play Store, kecuali untuk kasus ini), atau menambahkan game ke perpustakaan dengan mengirimkan bot tautan ke permainan. Saya menerapkan bot di heroku dan bagi mereka yang di masa depan berencana untuk menulis bot mereka sendiri dan menghostingnya secara gratis di heroku, saya akan memberikan beberapa rekomendasi untuk memecahkan kesulitan yang mungkin Anda temui (karena saya sendiri yang menemuinya). Sayangnya, karena sifat Heroku, perpustakaan bot terus-menerus “direset” setiap 24 jam sekali. Paket saya tidak mendukung penyimpanan file di server Heroku, jadi paket saya hanya mengambil file game saya dari Github. Ada beberapa solusi: gunakan database, atau cari server lain yang akan menyimpan file ini dengan game. Saya memutuskan untuk tidak melakukan apa pun untuk saat ini, karena pada dasarnya bot tersebut tidak begitu berguna. Saya membutuhkannya untuk mendapatkan pengalaman penuh, yang pada dasarnya adalah apa yang saya capai. Nah, rekomendasi Heroku:
-
Kemungkinan besar Anda harus mendaftar di heroku menggunakan VPN jika Anda tinggal di Rusia.
-
Di root proyek Anda perlu meletakkan file tanpa ekstensi bernama Procfile. Isinya harus seperti ini: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
Pada pom.xml, tambahkan baris berikut sesuai contoh , dimana pada tag mainClass menunjukkan path ke kelas yang berisi metode utama: bot.BotApplication (jika kelas BotApplication ada di folder bot).
-
Jangan membangun proyek apa pun menggunakan perintah paket mvn, dll., Heroku akan merakit semuanya untuk Anda.
-
Dianjurkan untuk menambahkan gitignore ke proyek, misalnya ini:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Sebenarnya upload projectnya ke github, lalu sambungkan repositori ke Heroku (atau gunakan cara lain, ada 3 kalau tidak salah).
-
Jika pengunduhan berhasil ("Pembangunan berhasil"), pastikan untuk membuka Konfigurasi Dynos:
dan alihkan penggesernya, lalu pastikan dalam posisi AKTIF (karena saya tidak melakukan ini, bot saya tidak berfungsi dan saya memutar otak selama beberapa hari dan melakukan banyak gerakan yang tidak perlu ).
-
Sembunyikan token bot di Github. Untuk melakukan ini, Anda perlu mendapatkan token dari variabel lingkungan:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Dan kemudian setelah menerapkan bot, atur variabel ini di dasbor Heroku di tab Pengaturan (di sebelah kanan TOKEN akan ada bidang VALUE, salin token bot Anda di sana):
- menerima proyek yang berfungsi penuh yang ditulis dalam Java;
- belajar bekerja dengan API pihak ketiga (Telegram Bot API);
- dalam praktiknya, saya mempelajari serialisasi lebih dalam, banyak bekerja dengan JSON dan perpustakaan Jackson (awalnya saya menggunakan GSON, tetapi ada masalah dengannya);
- memperkuat keterampilan saya saat bekerja dengan file, mengenal Java NIO;
- belajar bekerja dengan file konfigurasi .xml dan membiasakan diri melakukan logging;
- peningkatan kemahiran dalam lingkungan pembangunan (IDEA);
- belajar bekerja dengan git dan mempelajari nilai gitignore;
- memperoleh keterampilan dalam penguraian halaman web (perpustakaan Jsoup);
- mempelajari dan menggunakan beberapa pola desain;
- mengembangkan rasa dan keinginan untuk meningkatkan kode (refactoring);
- Saya belajar mencari solusi secara online dan tidak malu mengajukan pertanyaan yang tidak dapat saya temukan jawabannya.
GO TO FULL VERSION