@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
yang saya berikan kepada pengendali (kelas Penerima Kemas Kini 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 ialah pengendali pusat yang, bergantung pada jenis kemas kini, memindahkan kawalan kepada pengendali khusus lain: TextMessageHandler atau CallbackQueryHandler, kepada pembina yang saya luluskan kemas kini lebih jauh ke bawah rantaian. Kemas kini adalah perkara yang paling penting apabila bekerja dengan bot dan tidak boleh hilang, kerana dengan bantuan maklumat yang disimpan dalam kemas kini, kami mengetahui pengguna mana dan kepada sembang mana respons harus dihantar. Untuk menjana respons kepada pengguna, saya menulis kelas yang berasingan. Ia boleh menghantar mesej teks biasa, mesej dengan papan kekunci sebaris, mesej dengan gambar dan mesej dengan papan kekunci balasan. Papan kekunci sebaris kelihatan seperti ini: Ia mentakrifkan butang yang, dengan mengklik padanya, pengguna menghantar panggilan balik ke pelayan, yang boleh diproses dengan cara yang hampir sama seperti mesej biasa. Untuk "mengekalkannya" anda memerlukan pengendali anda sendiri. Kami menetapkan tindakan untuk setiap butang, yang kemudiannya ditulis pada objek Kemas kini. Itu. untuk butang "Kos" kami menetapkan perihalan "/harga" untuk panggilan balik, yang boleh kami perolehi kemudian daripada kemas kini. Seterusnya, dalam kelas yang berasingan, saya sudah boleh 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 kekunci Balas kelihatan seperti ini: Dan pada dasarnya, ia menggantikan penaipan pengguna. Mengklik butang "Perpustakaan" akan menghantar mesej "Perpustakaan" dengan pantas kepada bot. Untuk setiap jenis papan kekunci, saya menulis kelas saya sendiri, melaksanakan corak Builder: inline dan reply . Akibatnya, anda boleh "melukis" papan kekunci yang dikehendaki bergantung pada keperluan anda. Ini sangat mudah, kerana papan kekunci mungkin berbeza, tetapi prinsipnya tetap sama. Berikut ialah kaedah intuitif untuk menghantar mesej dengan papan kekunci sebaris:
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 fungsi yang ketat kepada bot, arahan khas menggunakan aksara slash telah dicipta: /library, /help, /game, dsb. Jika tidak, kami perlu memproses sebarang sampah yang mungkin ditulis oleh pengguna. Sebenarnya, inilah MessageHandler ditulis untuk:
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());
}
...
Oleh itu, bergantung pada arahan yang anda hantar ke bot, pengendali khas akan disertakan dalam kerja. Mari kita pergi lebih jauh dan lihat kerja penghurai dan perpustakaan. Jika anda menghantar pautan kepada bot kepada permainan di gedung Google Play, pengendali khas akan berfungsi secara automatik . Sebagai tindak balas, pengguna akan menerima maklumat tentang permainan dalam bentuk berikut: Pada masa yang sama, kaedah akan dipanggil yang akan cuba menambahkan permainan ke pustaka bot (mula-mula ke peta setempat, kemudian ke -> fail json ). Jika permainan sudah ada dalam perpustakaan, maka semakan akan dijalankan (seperti dalam peta cincang biasa), dan jika data medan (contohnya, nombor versi telah berubah), maka permainan dalam perpustakaan akan ditimpa. Jika tiada perubahan dikesan, maka tiada entri akan dibuat. Jika tiada permainan dalam perpustakaan sama sekali, maka ia ditulis terlebih dahulu ke peta tempatan (objek seperti tyk ), dan kemudian ditulis ke fail json, kerana jika aplikasi pada pelayan ditutup secara tidak dijangka, data akan hilang, tetapi ia sentiasa boleh dibaca menggunakan fail. Sebenarnya, apabila program bermula, perpustakaan sentiasa dimuatkan buat kali pertama daripada fail daripada blok statik:
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 perlu membaca data daripada fail ke dalam peta sementara, yang kemudiannya "disalin" ke dalam peta penuh untuk mengekalkan ketidakpekaan kes semasa mencari permainan dalam fail (dengan menulis tITan QuEST, bot masih akan mencari permainan Titan Quest di perpustakaan). Tidak dapat mencari penyelesaian lain, ini adalah ciri penyahserialisasian menggunakan Jackson. Jadi, dengan setiap permintaan untuk pautan, permainan akan ditambahkan ke perpustakaan, jika boleh, dan perpustakaan itu berkembang. Maklumat lanjut tentang permainan tertentu boleh diperoleh menggunakan arahan /libraryGame_Name. Anda boleh mengetahui kedua-dua parameter tertentu (contohnya, versi semasa) dan semua parameter sekaligus. Ini dilaksanakan menggunakan papan kekunci sebaris, yang telah dibincangkan sebelum ini. Semasa bekerja, saya juga mengaplikasikan kemahiran yang diperolehi di sini semasa menyelesaikan masalah. Sebagai contoh, senarai nama permainan rawak yang terletak di perpustakaan (pilihan tersedia menggunakan arahan /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);
}
Bagaimanakah proses bot dipautkan? Ia menyemak mereka untuk melihat sama ada mereka tergolong dalam Google Play (hos, 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 teratur, maka bot menyambung melalui pautan menggunakan perpustakaan Jsoup, yang membolehkan anda mendapatkan kod HTML halaman, yang tertakluk kepada analisis dan penghuraian selanjutnya. Anda tidak akan dapat menipu bot dengan pautan 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 terpaksa menyelesaikan masalah dengan tetapan serantau. Bot bersambung ke gedung Google Play daripada pelayan yang terletak di Eropah, jadi halaman dalam gedung Google Play dibuka dalam bahasa yang sesuai. Saya terpaksa menulis tongkat yang akan memaksa "lencong" ke versi halaman Rusia (projek itu, bagaimanapun, ditujukan kepada penonton kami). Untuk melakukan ini, pada penghujung pautan anda perlu menambah parameter hl: &hl=ru dengan teliti dalam permintaan GET ke pelayan 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;
}
Selepas sambungan berjaya, kami menerima dokumen HTML yang sedia untuk analisis dan penghuraian, tetapi ini di luar skop artikel ini. Kod parser ada di sini . Penghurai itu sendiri mendapatkan semula maklumat yang diperlukan dan mencipta objek dengan permainan, yang kemudiannya ditambahkan ke perpustakaan jika perlu. <h2>Untuk meringkaskan</h2>Bot menyokong beberapa arahan yang mengandungi fungsi tertentu. Ia menerima mesej daripada pengguna dan memadankannya dengan arahannya. Jika ia adalah pautan atau perintah /game + link, ia menyemak pautan itu untuk melihat sama ada ia milik Google Play. Jika pautan itu betul, ia bersambung melalui Jsoup dan menerima dokumen HTML. Dokumen ini dianalisis berdasarkan penghurai bertulis. Maklumat yang diperlukan tentang permainan diekstrak daripada dokumen, dan kemudian objek dengan permainan diisi dengan data ini. Seterusnya, objek dengan permainan diletakkan dalam storan tempatan (jika permainan itu belum ada) dan segera ditulis ke fail untuk mengelakkan kehilangan data. Permainan yang direkodkan dalam perpustakaan (nama permainan adalah kunci untuk peta, objek dengan permainan adalah nilai untuk peta) boleh diperoleh menggunakan arahan /library Game_name. Jika permainan yang ditentukan ditemui dalam pustaka bot, pengguna akan dikembalikan papan kekunci sebaris, yang dengannya dia boleh mendapatkan maklumat tentang permainan itu. Jika permainan tidak ditemui, anda mesti sama ada memastikan nama itu dieja dengan betul (ia mesti sepadan sepenuhnya dengan nama permainan di gedung Google Play, kecuali untuk kes itu), atau menambah permainan ke pustaka dengan menghantar bot pautan kepada permainan. Saya menggunakan bot pada heroku dan bagi mereka yang pada masa hadapan merancang untuk menulis bot mereka sendiri dan menjadi tuan rumah secara percuma pada heroku, saya akan memberikan beberapa cadangan untuk menyelesaikan kesukaran yang mungkin anda hadapi (sejak saya sendiri yang menghadapinya). Malangnya, disebabkan sifat Heroku, perpustakaan bot sentiasa "set semula" sekali setiap 24 jam. Pelan saya tidak menyokong penyimpanan fail pada pelayan Heroku, jadi ia hanya menarik fail permainan saya dari Github. Terdapat beberapa penyelesaian: gunakan pangkalan data, atau cari pelayan lain yang akan menyimpan fail ini dengan permainan. Saya memutuskan untuk tidak melakukan apa-apa buat masa ini, kerana pada dasarnya bot tidak begitu berguna. Saya memerlukannya untuk mendapatkan pengalaman penuh, yang pada asasnya adalah apa yang saya capai. Jadi, cadangan untuk Heroku:
-
Anda berkemungkinan besar perlu mendaftar di heroku menggunakan VPN jika anda tinggal di Rusia.
-
Pada akar projek anda perlu meletakkan fail tanpa sambungan yang dipanggil Procfile. Kandungannya hendaklah seperti ini: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
Dalam pom.xml, tambah baris berikut mengikut contoh , di mana dalam teg mainClass menunjukkan laluan ke kelas yang mengandungi kaedah utama: bot.BotApplication (jika kelas BotApplication berada dalam folder bot).
-
Jangan bina sebarang projek menggunakan arahan pakej mvn, dsb., Heroku akan memasang segala-galanya untuk anda.
-
Adalah dinasihatkan untuk menambah gitignore pada projek, contohnya ini:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Sebenarnya muat naik projek ke github, dan kemudian sambungkan repositori ke Heroku (atau gunakan kaedah lain, terdapat 3 daripadanya, jika saya tidak silap).
-
Jika muat turun berjaya ("Bina berjaya"), pastikan anda pergi ke Konfigurasi Dynos:
dan tukar peluncur, dan kemudian pastikan ia berada dalam kedudukan ON (kerana fakta bahawa saya tidak melakukan ini, bot saya tidak berfungsi dan saya memerah otak saya selama beberapa hari dan membuat banyak pergerakan yang tidak perlu ).
-
Sembunyikan token bot pada Github. Untuk melakukan ini, anda perlu mendapatkan token daripada pembolehubah persekitaran:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Dan kemudian selepas menggunakan bot, tetapkan pembolehubah ini dalam papan pemuka Heroku dalam tab Tetapan (di sebelah kanan TOKEN akan terdapat medan VALUE, salin token bot anda di sana):
- menerima projek berfungsi sepenuhnya yang ditulis dalam Java;
- belajar untuk bekerja dengan API pihak ketiga (Telegram Bot API);
- dalam amalan, saya menyelidiki lebih mendalam ke dalam serialisasi, banyak bekerja dengan JSON dan perpustakaan Jackson (pada mulanya saya menggunakan GSON, tetapi terdapat masalah dengannya);
- mengukuhkan kemahiran saya apabila bekerja dengan fail, berkenalan dengan Java NIO;
- belajar bekerja dengan fail konfigurasi .xml dan membiasakan diri saya untuk mengelog;
- peningkatan kecekapan dalam persekitaran pembangunan (IDEA);
- belajar bekerja dengan git dan mempelajari nilai gitignore;
- memperoleh kemahiran dalam penghuraian halaman web (perpustakaan Jsoup);
- mempelajari dan menggunakan beberapa corak reka bentuk;
- mengembangkan rasa dan keinginan untuk menambah baik kod (pemfaktoran semula);
- Saya belajar untuk mencari penyelesaian dalam talian dan tidak malu bertanya soalan yang saya tidak dapat jawapannya.
GO TO FULL VERSION