
Tentang Habré, ribuan artikel tentang cara membuat bot Telegram untuk berbagai bahasa dan platform pemrograman. Topiknya jauh dari baru.
Tapi Telegraff adalah kerangka kerja terbaik untuk menerapkan bot Telegram, dan saya akan membuktikannya di bawah potongan.
Pembukaan
Pada 2015, rubel Rusia mengalami demam. Saya memiliki tabungan dalam dolar dan saya memeriksa kurs secara harfiah setiap lima menit untuk menjual mata uang pada kurs yang saya butuhkan. Demam berlarut-larut, saya lelah dan menulis bot Telegram ( @TinkoffRatesBot ), yang memberi tahu Anda jika nilai tukar mencapai nilai ambang (yang diharapkan).
Saya sangat tersentuh dengan tugas ini. Botha menulis dengan cukup cepat, tetapi dia tidak menerima kepuasan.
Integrasi dengan Telegram tidak dan tidak ada masalah. Masalah ini teratasi dalam beberapa jam. Dan saya bahkan terkejut bahwa ada seluruh perpustakaan di Jawa (secara subyektif, dengan kode yang menjijikkan dalam kualitas) untuk integrasi dengan Telegrams, yang telah mendapatkan lebih dari seribu bintang di Github.
Tantangan utama bagi saya adalah sistem scripting: pengguna memanggil perintah, misalnya, "/ taksi", bot menanyakan serangkaian pertanyaan, setiap jawaban divalidasi dan dapat mempengaruhi urutan pertanyaan berikutnya, "bentuk" yang biasa terbentuk, diberikan kepada metode pemrosesan akhir untuk membentuk tanggapan.
Saya melakukan ini, tetapi struktur kelas, tingkat abstraksi, semuanya sangat heterogen sehingga pahit untuk dilihat. Saya tersiksa oleh pertanyaan: Bagaimana ini dapat secara ringkas dan organik ditransfer ke model berorientasi objek?
Saya ingin memiliki sesuatu yang sederhana, nyaman, dan yang paling penting - untuk dapat menggambarkan seluruh skrip dalam satu file yang terisolasi sehingga saya tidak perlu melihat setengah dari proyek untuk memahami rantai interaksi pengguna.
Bukan untuk mengatakan bahwa masalah itu sangat akut, karena tugasnya sudah diselesaikan. Sebaliknya, kadang-kadang saya memikirkannya. Pikirannya adalah Groovy DSL, tetapi ketika Kotlin tiba, pilihannya menjadi jelas. Jadi Telegraff muncul.
Ya, tentu saja, tidak ada persaingan yang akan dimenangkan Telegraff. Dan klaim bahwa Telegraff adalah yang terbaik seharusnya tidak diterima secara harfiah. Tapi Telegraff adalah pendekatan baru dan unik untuk tantangan ini. Mudah untuk menjadi yang terbaik, menjadi satu-satunya.
Bagaimana cara menggunakannya?
Ketergantungan
Langkah pertama adalah menentukan repositori tambahan untuk dependensi. Mungkin pada titik tertentu saya akan menerbitkan Telegraff di Maven Central atau di JCenter, tetapi untuk sekarang.
Gradlerepositories { maven { url "https://dl.bintray.com/ruslanys/maven" } }
Maven <repositories> <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>bintray-ruslanys-maven</id> <name>bintray</name> <url>https://dl.bintray.com/ruslanys/maven</url> </repository> </repositories>
Itu tetap berlaku untuk kecil. Untuk menggunakan Telegraff, Anda hanya perlu menentukan satu dependensi spring-boot-starter:
Gradle compile("me.ruslanys.telegraff:telegraff-starter:1.0.0")
Maven <dependency> <groupId>me.ruslanys.telegraff</groupId> <artifactId>telegraff-starter</artifactId> <version>1.0.0</version> </dependency>
Konfigurasi
Konfigurasi proyek sederhana dan dapat dibatasi pada dua atau tiga parameter pertama:
properti aplikasi telegram.access-key=123 # ① telegram.mode=webhook # ② telegram.webhook-base-url=https://ruslanys.me # ③ telegram.webhook-endpoint-url=/telegram # ④ telegram.handlers-path=handlers # ⑤ telegram.unresolved-filter.enabled=false # ⑥
- Kunci Anda ke API Telegram.
- Mode menerima pesan (pembaruan) dari Telegram. Ini bisa berupa polling atau webhook.
- Jika metode untuk menerima pembaruan ditunjukkan oleh "webhook", Anda harus menentukan jalur ke aplikasi Anda.
- Jika mau, Anda dapat menentukan jalur Anda sendiri ke titik akhir. Jika parameter ini tidak didefinisikan ulang, jalur bentuk berikut akan dihasilkan:
/telegram/${UUID}
. Sebelum memulai aplikasi, alamat yang ditentukan ditetapkan sebagai alamat kait web. Di akhir pekerjaan, alamat web hook ditimpa untuk dapat beralih ke polling saat berikutnya dimulai. - Jika diinginkan, Anda dapat mengubah folder tempat skrip penangan akan ditemukan. Secara default, ini adalah folder
handlers
. UnresolvedFilter
termasuk dalam "pengiriman" dan diaktifkan secara default. Jika tidak ada penangan yang ditemukan pada pesan pengguna, UnresolvedFilter
merespons dengan sesuatu seperti "Maaf, saya tidak mengerti Anda :(".
Saatnya menulis skrip!
Penangan
Penangan (skrip) adalah bagian penting dari Telegraff. Di sinilah rantai interaksi pengguna diatur. Intinya adalah bahwa setiap perintah, seperti "/ mulai", "/ taksi", "/ bantuan", adalah skrip / skrip / penangan / penangan yang terpisah.
Sebuah skrip dapat berisi serangkaian langkah (pertanyaan) yang harus dilalui pengguna untuk menjalankan perintah. Dengan kata lain, pengguna harus mengisi formulir. Dan karena messenger berasal dari antarmuka, Anda perlu berbicara dan bertanya kepada pengguna.
Apakah saya perlu menjelaskan bahwa respons pengguna perlu divalidasi? Hal pertama yang akan dilakukan pengguna adalah dia akan merespons secara berbeda dari yang Anda harapkan.
Nah, pada akhirnya, skrip bisa bercabang, mis. Setiap jawaban atas suatu pertanyaan dapat memengaruhi urutan jawaban berikutnya.
Sebagai contoh!
Untuk memulai, letakkan file dengan ekstensi .kts
di folder dengan handlers
sumber daya: src/main/resources/handlers/ExampleHandler.kts
.
Skenario panggilan taksi enum class PaymentMethod { CARD, CASH } handler("/taxi", "") {
Kunci-kunci stepa sengaja tidak dimasukkan ke dalam konstanta. Dalam produksi, tentu saja, ini sebaiknya dihindari.
Mari kita cari tahu:
- Kami mendeklarasikan skrip. Setidaknya diperlukan satu nama tim. Dalam hal ini, ada dua tim: "/ taksi", "taksi". Jika pesan pengguna dimulai dengan kata-kata ini, penangan yang sesuai akan dipanggil.
- Kami menentukan langkah-langkah (pertanyaan). Nama langkah unik diperlukan karena selanjutnya, respons pengguna dapat diakses dengan tepat dengan kunci ini ("locationFrom").
- Setiap langkah berisi tiga bagian, yang pertama adalah pertanyaan itu sendiri. Pertanyaannya adalah bagian wajib yang harus ada di setiap langkah. Tidak ada artinya dalam langkah tanpa pertanyaan.
- Anda dapat mengisi pertanyaan sesuai keinginan. Dalam hal ini, pengguna akan diminta melalui keyboard untuk memilih salah satu opsi: "Kartu" atau "Uang Tunai". Sebagai hasil dari memanggil blok ini, harus ada objek bertipe
TelegramSendRequest
. Maaf, saya tidak dapat menghasilkan sesuatu yang lebih baik daripada akhiran SendRequest
, yang menggambarkan struktur sebagai permintaan keluar di Telegram.

- Bagian langkah terpenting kedua adalah memeriksa respons pengguna. Jenis setiap langkah adalah parameter (generik), dan oleh karena itu, blok validasi harus mengembalikan dengan tepat tipe langkahnya parameter.
- Jika respons pengguna tidak memuaskan, Anda dapat melempar
ValidationException
dengan teks klarifikasi, tetapi keyboard yang sama, jika ditunjukkan dalam pertanyaan. - Bagian langkah terakhir adalah blok yang menunjukkan langkah selanjutnya. Secara default, langkah-langkah akan dieksekusi dalam urutan deklarasi mereka, dari atas ke bawah. Tetapi proses ini dapat dipengaruhi dengan mengganti blok yang sesuai. Entah kunci langkah berikutnya (
String
) atau "null" dapat dikembalikan sebagai hasil dari eksekusi blok ini, yang menunjukkan bahwa tidak ada langkah lagi dan sekarang saatnya untuk melanjutkan ke pelaksanaan perintah. - Ketika permintaan pengguna dihasilkan, pemrosesan diperlukan. Argumen dalam lambda adalah Negara (ini seperti sesi) dan respons pengguna.
- Perhatikan bahwa respons yang gagal bukan lagi string respons pengguna, tetapi objek yang sudah diproses dari tipe yang diinginkan.
- Respons terhadap perintah dapat berupa apa saja, mirip dengan paragraf 4. Jika respons terhadap perintah tidak diperlukan, Anda dapat mengembalikan "null".
Seorang pawang mungkin tidak memiliki langkah sama sekali. Dalam hal ini, Anda hanya perlu menentukan perilaku penangan untuk menjalankan perintah.
Skrip selamat datang handler("/start") { process { _, _ -> MarkdownMessage("!") } }
Coba
Untuk mencoba, garpu repositori , clone ke mesin lokal dan pergi ke folder telegraff-sample
. Konfigurasikan, luncurkan, sentuh!
Secara umum, telegraff-sample
adalah proyek yang sengaja independen yang tidak terkait dengan induk dan bahkan memiliki Gradle Wrapper sendiri. Anda hanya dapat meninggalkan folder ini. Ini adalah jenis pola dasar.
Bagaimana cara kerjanya?
Telegram
Integrasi dengan Telegram sangat sederhana dan diterapkan di TelegramApi
.
Setiap metode sengaja dilaksanakan secara individual karena sejumlah keadaan: mulai dari penggunaan Spring's RestTemplate (dan tes untuk itu), hingga kekhususan API Telegram.
Seperti yang Anda lihat dari konfigurasi, ada dua jenis klien dari API ini di Telegraff: PollingClient , WebhookClient . Bergantung pada konfigurasi, sebuah bin tertentu akan dideklarasikan.
Dan meskipun metode untuk menerima pembaruan (pesan baru) berbeda dari Telegram, esensinya tidak berubah dan bermuara pada satu hal - menerbitkan acara ( TelegramUpdateEvent
) tentang pesan baru melalui Spring's EventPublisher
(pola “Pengamat”). Jika mau, Anda dapat menerapkan pendengar Anda sendiri dengan berlangganan jenis acara ini. Menurut saya, lapisan abstraksi yang logis, karena sama sekali tidak masalah bagaimana pesan itu diterima.
Filter
Segera setelah pesan baru diterima, diperlukan untuk memprosesnya dan menanggapi pengguna. Untuk melakukan ini, pesan harus melalui rantai filter.
Ini mirip dengan filter Java EE yang akrab dengan programmer Java. Satu-satunya perbedaan adalah bahwa yang disebut Handler (jika kita menggambar paralel dengan Java EE, ini adalah Servlet) tidak terlepas dari filter, tetapi merupakan bagian dari mereka.

Jadi, filternya disederhanakan dan dapat membiarkan pesan masuk lebih jauh, mungkin tidak.
LoggingFilter
jelas merupakan filter prioritas pertama (pertama) yang akan dipanggil sebagai bagian dari pemrosesan pesan baru. Log informasi pada pesan masuk dan mengirimkannya lebih lanjut ke rantai. Saya sengaja menambahkan LoggingFilter
sebagai contoh. Bahkan, mungkin tidak masuk akal, karena Pesan yang masuk dicatat di tingkat klien.
Filter berikutnya adalah CancelFilter
. Ini pada dasarnya bekerja bersama dengan HandlersFilter
dan merupakan pelengkap untuk itu. Tugasnya sederhana: jika pengguna ingin meninggalkan skrip saat ini, ia dapat menulis "/ membatalkan", atau "membatalkan" dan Statusnya (sesi) harus dihapus. Dia dapat memulai skenario baru tanpa menyelesaikan yang sebelumnya. Untuk alasan ini, CancelFilter
"lebih tinggi" (prioritas).
HandlersFilter
adalah filter utama dalam proses saat ini. Filter inilah yang menyimpan status obrolan pengguna, menemukan dan memanggil penangan (skrip) yang diinginkan, menerapkan blok validasi, menentukan urutan langkah-langkah, dan merespons pengguna.
Jika HandlersFilter
tidak menemukan penangan yang cocok untuk pesan pengguna, baik dalam sesi atau dalam konten, pesan dikirim lebih jauh ke bawah rantai. Filter ekstrem adalah UnresolvedFilter
. Ini adalah filter yang tahu bahwa itu adalah yang terakhir, oleh karena itu fungsinya sederhana: jika mereka menghubungi saya, cara menanggapi pesan tidak jelas, saya akan mengatakan bahwa saya tidak mengerti apa-apa. Sepertinya saya lebih baik menerima setidaknya beberapa pesan dari bot jika tidak tahu bagaimana merespons, daripada tidak menerima apa pun.
Untuk menambahkan filter Anda, Anda perlu mendeklarasikan Bean dari kelas TelegramFilter
dan menentukan anotasi @TelegramFilterOrder(ORDER_NUMBER)
.
Contoh filter @Component @TelegramFilterOrder(Integer.MIN_VALUE) class LoggingFilter : TelegramFilter { override fun handleMessage(message: TelegramMessage, chain: TelegramFilterChain) { log.info("New message from #{}: {}", message.chat.id, message.text) chain.doFilter(message) } companion object { private val log = LoggerFactory.getLogger(LoggingFilter::class.java) } }
Inilah cara @TinkoffRatesBot mengimplementasikan "kalkulator". Tanpa memanggil skrip dan perintah apa pun, Anda dapat mengirim nomor, misalnya, "1000", atau bahkan seluruh ekspresi, misalnya, "4500 * 3 - 12000". Bot akan menghitung hasil dari ekspresi, menerapkan nilai tukar saat ini ke hasil dan menampilkan informasi tentang itu. Sebenarnya, hasil dari tindakan tersebut adalah eksekusi CalculationFilter
, yang berada dalam rantai di bawah HandlersFilter
, tetapi di UnresolvedFilter
.
Penangan
Sistem scripting Telegraff (penangan) dibangun di atas Kotlin DSL. Singkatnya, ini tentang lambda dan tentang pembangun.
Saya tidak melihat titik melihat secara terpisah Kotlin DSL, karena ini percakapan yang sangat berbeda. Ada dokumentasi hebat dari JetBrains dan laporan komprehensif dari i_osipov .
Nuansa
Bagian ini dikhususkan untuk fitur-fitur yang ada. Semua dari mereka, menurut saya, tidak kritis, beberapa dari mereka dapat diperbaiki, beberapa tidak. Tetapi Anda perlu tahu tentang aspek-aspek ini.
Jika Anda memiliki keinginan untuk berpartisipasi atau pengetahuan tentang cara memperbaiki satu atau lain hal dari bagian ini, saya akan sangat berterima kasih.
Telegram
Lapisan integrasi dengan Telegram mungkin tidak sepenuhnya dijelaskan. Hanya metode yang saya butuhkan yang diterapkan. Jika ada sesuatu yang tidak Anda miliki secara pribadi, perbaiki TelegramApi
dan kirim PR!
Salah satu bagian penting saat ini adalah kurangnya dukungan keyboard inline (ini adalah ketika keyboard langsung di bawah pesan di pita). Tugas ini diperparah oleh fakta bahwa inline-keyboards harus benar "dimasukkan" ke dalam struktur yang ada sehingga tetap sederhana, nyaman, terisolasi. Sudah ada ide bagus untuk mengimplementasikan fungsi ini, tetapi belum diimplementasikan dan diuji dalam bentuk apa pun.
Botol lemak
Sayangnya, beberapa perpustakaan, seperti JRuby
dan mungkin Kotlin Embedded Compiler
(diperlukan untuk menyusun skrip) dapat mengalami masalah sebagai bagian dari Fat JAR
. Fat JAR
adalah ketika kode Anda dan semua dependensi Anda dikemas dalam satu file ( *.jar
).
Untuk mengatasi masalah ini, Anda dapat membongkar dependensi dalam runtime. Yaitu, ketika aplikasi dimulai, JAR ketergantungan dari paket utama dikerahkan di suatu tempat pada disk dan classpath ditunjukkan sebelum itu. Ini cukup mudah dilakukan melalui konfigurasi bootJar
:
Konfigurasi plugin bootJar { requiresUnpack "**/**kotlin**.jar" requiresUnpack "**/**telegraff**.jar" }
Namun, untuk merujuk dari penangan (skrip) ke kacang Anda (layanan, misalnya), mereka juga harus dibongkar. Yang pada prinsipnya menghilangkan manfaat dari pendekatan ini.
Seperti yang saya lihat, menggunakan plugin application
Gradle tetap menjadi metode yang paling dapat diandalkan, sederhana dan nyaman. Selain itu, jika Anda menyimpan aplikasi Anda, tidak ada perbedaan dengan hasilnya.
Tentang semua ini saya tulis secara rinci di sini .
Urutan inisialisasi
Di sini saya ingin mencatat dua keadaan.
Pertama, jika Anda melihat skenario panggilan taksi, Anda dapat melihat bahwa kelas enum
didefinisikan di atas panggilan ke handler(...)
. Kebutuhan ini dipaksakan oleh fakta bahwa, pada kenyataannya, handler
adalah panggilan fungsi. Panggilan fungsi, yang hasilnya harus berupa struktur, yang akan digunakan Telegraff nanti. Jika, menurut hasil eksekusi skrip Anda, pabrik tidak dapat membawa hasil ke tipe yang diinginkan, kesalahan akan jatuh pada tahap inisialisasi.
Kedua, Anda harus ingat bahwa skrip Anda dapat diinisialisasi lebih awal dari seluruh aplikasi dan kacang. Jika, misalnya, kami meletakkan tautan ke konteks menjadi variabel statis dan mencoba mendapatkan layanan di baris pertama dalam file skrip, mungkin konteksnya tidak akan memilikinya, karena belum diinisialisasi. Untuk menghindari masalah seperti itu, gunakan metode Telegraff ini . Ini memastikan bahwa konteksnya diinisialisasi dan bahwa semua kacang yang diperlukan tersedia. Contohnya bisa dilihat di sini .
Kesimpulan
Saya ingin mencoba - garpu,
Saya ingin memperbaikinya - kirim PR,
Saya ingin berterima kasih - letakkan tanda bintang di Github, seperti pos dan beri tahu teman Anda!
Repositori proyek