Filter Query dan bot.on()
Filter query merupakan argument pertama dari bot
yang berbentuk string.
Pengenalan
Sebagian besar framework bot—atau bahkan semuanya?—hanya menyediakan pemfilteran update yang sederhana, misalnya cuma disediakan on("message")
dan sejenisnya. Pemfilteran untuk jenis pesan lainnya diserahkan kepada developer bot masing-masing untuk ditangani sendiri, yang mana sering kali mengarah ke penggunaan statemen if
yang tidak ada habisnya di dalam kode mereka.
Sebaliknya, grammY dilengkapi dengan bahasa query-nya sendiri yang dapat digunakan untuk memfilter pesan yang kamu inginkan.
grammY memiliki lebih dari 1150 filter berbeda yang siap dipakai, dan tidak menutup kemungkinan lebih banyak lagi filter yang akan ditambahkan seiring berjalannya waktu. Setiap filter yang valid dapat dilengkapi menggunakan auto-complete di code editor. Dengan demikian, kamu cukup mengetik bot
, lalu buka auto-complete, kemudian telusuri semua query yang tersedia dengan cara mengetik sesuatu.
Type inference bot
akan memahami filter query yang sedang kamu pilih. Dari situ, ia akan mengerucutkan beberapa type context yang ada.
bot.on("message", async (ctx) => {
// Bisa jadi undefined kalau pesan yang diterima tidak ada teksnya.
const text: string | undefined = ctx.msg.text;
});
bot.on("message:text", async (ctx) => {
// Text selalu tersedia karena handler ini dipanggil
// ketika pesan yang diterima hanya berupa teks.
const text: string = ctx.msg.text;
});
2
3
4
5
6
7
8
9
Filter query grammY tersedia di level runtime dan type.
Contoh Query
Berikut ini beberapa contoh query:
Query Biasa
Filter sederhana untuk update dan sub-filter:
bot.on("message"); // dipanggil untuk jenis pesan apapun
bot.on("message:text"); // hanya menerima pesan teks
bot.on("message:photo"); // hanya menerima pesan foto
2
3
Filter untuk Entity
Sub-filter yang masuk satu tingkat lebih dalam:
bot.on("message:entities:url"); // untuk pesan yang mengandung url
bot.on("message:entities:code"); // untuk pesan yang berisi cuplikan kode
bot.on("edited_message:entities"); // untuk pesan diedit yang mengandung entity apapun bentuknya
2
3
Mengosongkan Value
Kamu dapat mengosongkan value tertentu di filter query. grammY kemudian akan mencari value yang sesuai dengan query kamu.
bot.on(":text"); // Seluruh pesan dan postingan channel yang mengandung teks
bot.on("message::url"); // Pesan yang mengandung URL baik di teks maupun di caption (foto, dan sebagainya)
bot.on("::email"); // Seluruh pesan dan postingan channel yang mengandung email di teks maupun di caption-nya
2
3
Mengosongkan value pertama akan mencocokkan pesan serta postingan channel. Perlu diingat bahwa ctx
memberi kamu akses ke pesan dan postingan channel manapun yang cocok dengan query yang diberikan.
Mengosongkan value kedua akan mencocokkan entity, baik di pesan maupun di caption. Kamu bisa menghilangkan bagian pertama dan kedua secara bersamaan.
Shortcut
Query engine grammY memiliki shortcut yang dapat mengelompokkan query-query yang saling berkaitan.
msg
Shortcut msg
—bukan msg micin, loh ya 😬—mengelompokkan pesan dan postingan channel. Dengan menggunakan msg
sama halnya dengan menyimak aktivitas message
dan channel
.
bot.on("msg"); // Pesan atau postingan channel apapun bentuknya
bot.on("msg:text"); // Sama dengan menggunakan `:text`
2
edit
Shortcut edit
mengelompokkan pesan serta postingan channel yang diedit. Dengan kata lain, menggunakan edit
serupa dengan menyimak event "edited
dan "edited
.
bot.on("edit"); // Semua pesan dan postingan yang diedit
bot.on("edit:text"); // Pesan teks yang diedit
bot.on("edit::url"); // Pesan atau caption yang diedit dan mengandung URL di teksnya
bot.on("edit:location"); // Lokasi terkini (live location) yang diperbarui
2
3
4
:media
Shortcut :
mengelompokkan pesan foto dan video. Dengan kata lain, menggunakan :
serupa dengan menyimak event ":
dan ":
.
bot.on("message:media"); // pesan foto atau video
bot.on("edited_channel_post:media"); // postingan channel yang diedit dan berupa media
bot.on(":media"); // pesan atau postingan channel berupa media
2
3
:file
Shortcut :
mengelompokkan semua pesan yang mengandung file. Dengan kata lain, menggunakan :
serupa dengan menyimak event ":
, ":
, ":
, ":
, ":
, ":
, ":
, dan ":
. Dengan begitu, kamu bisa yakin kalau await ctx
pasti mengembalikan sebuah object file.
bot.on(":file"); // Pesan atau postingan channel yang mengandung file
bot.on("edit:file"); // Pesan atau postingan channel yang sudah diedit dan mengandung file
2
Syntactic Sugar
Catatan penerjemah: syntactic sugar merupakan sebuah sintaks yang dibuat sedemikian rupa supaya lebih mudah dibaca dan digunakan oleh programmer.
Terdapat dua potongan query khusus yang bisa membuat proses pemfilteran menjadi semakin mudah. Kamu bisa mendeteksi bot di query dengan menggunakan potongan query :
. Sedangkan syntactic sugar :
dapat digunakan untuk merujuk ke bot kamu di query, yang mana akan membandingkan id user bot kamu.
// Pesan service mengenai sebuah bot yang bergabung ke chat
bot.on("message:new_chat_members:is_bot");
// Pesan service mengenai bot kamu telah dikeluarkan dari chat
bot.on("message:left_chat_member:me");
2
3
4
Perhatikan bahwa meskipun syntactic sugar ini bisa digunakan untuk bekerja dengan pesan service, ia sebaiknya tidak digunakan untuk mendeteksi apakah seseorang benar-benar bergabung atau meninggalkan chat. Pesan service pada dasarnya adalah pesan untuk menginformasikan pengguna di dalam chat tersebut. Adakalanya dalam beberapa kasus, pesan itu tidak akan selalu terlihat. Misalnya, di grup besar atau supergroup tidak akan ada pesan service mengenai pengguna yang telah bergabung atau meninggalkan chat. Akibatnya, bot kamu bisa jadi tidak akan mendeteksinya. Oleh karena itu, kamu harus menyimak update chat member.
Mengombinasikan Beberapa Query
Kamu bisa mengombinasikan filter query menggunakan operator AND maupun OR.
Mengombinasikan Menggunakan OR
Kalau ingin memasang beberapa bagian middleware dibalik penggabungan OR dari dua buah query, kamu bisa memasang keduanya ke bot
di dalam sebuah array.
// Dijalankan jika pembaruan berupa pesan OR pesan yang diedit
bot.on(["message", "edited_message"] /* , ... */);
// Dijalankan jika berupa hashtag OR email OR terdapat entity mention di dalam teks atau caption tersebut
bot.on(["::hashtag", "::email", "::mention"] /* , ... */);
2
3
4
Middleware tersebut akan dijalankan ketika salah satu dari query yang disediakan terdapat kecocokan. Urutan kueri tidak menjadi masalah.
Mengombinasikan Menggunakan AND
Kalau ingin memasang beberapa bagian middleware dibalik penggabungan AND dari dua buah query, kamu bisa merangkai keduanya ke bot
.
// Mencocokkan URL yang di-forward
bot.on("::url").on(":forward_origin" /* , ... */);
// Mencocokkan foto yang mengandung hashtag di caption-nya
bot.on(":photo").on("::hashtag" /* , ... */);
2
3
4
Middleware tersebut akan dijalankan ketika seluruh query yang disediakan terdapat kecocokan. Urutan kueri tidak menjadi masalah.
Menyusun Query yang Kompleks
Secara teknis kamu bisa mengombinasikan filter query ke rangkaian yang lebih kompleks selama mereka termasuk CNF, meski sepertinya ini tidak terlalu bermanfaat juga.
bot
// Mencocokkan semua potingan channel atau pesan terusan ...
.on(["channel_post", ":forward_origin"])
// ... yang berupa teks ...
.on(":text")
// ... dan berisi sekurang-kurangnya satu url, hashtag, atau cashtag.
.on(["::url", "::hashtag", "::cashtag"] /* , ... */);
2
3
4
5
6
7
Type inference ctx
akan memindai seluruh rangkaian tersebut dan memeriksa setiap elemen dari ketiga panggilan .on
. Contohnya, dari potongan kode di atas, type inference dapat mendeteksi kalau ctx
adalah property yang dibutuhkan.
Tips Berguna
Berikut ini fitur-fitur filter query yang kurang begitu terkenal tetapi bisa sangat membantu. Beberapa diantaranya merupakan fitur tingkat lanjut, silahkan baca materi berikutnya.
Update Member Chat
Kamu bisa menggunakan filter query berikut untuk menerima status update mengenai bot-mu.
bot.on("my_chat_member"); // diblokir, blokir dibuka, bergabung, atau keluar
Filter di atas akan terpicu di chat pribadi, baik saat bot diblokir maupun saat blokirnya dibuka. Jika di grup, ia akan terpicu saat bot ditambahkan atau dikeluarkan. Kamu bisa memeriksa ctx
untuk mencari tahu apa yang sebenarnya terjadi.
Hati-hati! Filter ini tidak sama dengan
bot.on("chat_member");
yang digunakan untuk mendeteksi perubahan status member chat lainnya, misalnya ketika seseorang bergabung, dipromosikan, dan sebagainya.
Ingat! Update
chat
perlu diaktifkan secara eksplisit dengan cara menentukan_member allowed
saat memulai bot kamu._updates
Mengombinasikan Query dengan Method Lain
Kamu bisa mengombinasikan beberapa filter query dengan method-method lain di class Composer
(API Reference), misalnya command
atau filter
. Dengan begitu, kamu bisa membuat pola penanganan pesan menjadi lebih fleksibel.
bot.on(":forward_origin").command("help"); // Command /help yang di-forward
// Tangani command yang berasal dari private chat saja.
const pm = bot.chatType("private");
pm.command("start");
pm.command("help");
2
3
4
5
6
Memfilter Berdasarkan Jenis Pengirim Pesan
Terdapat lima jenis penulis pesan di Telegram, yaitu
- Penulis postingan channel;
- Forward otomatis dari channel ke grup diskusi terkait;
- Akun pengguna biasa, termasuk bot (misal, pesan yang “normal”);
- Admin yang mengirim atas nama grup (admin anonim);
- Pengguna yang mengirim pesan sebagai salah satu dari channel mereka.
Kamu dapat mengombinasikan filter query dengan mekanisme penanganan update lainnya untuk mengetahui jenis penulis pesan tersebut.
// Postingan channel yang dikirim oleh `ctx.senderChat`
bot.on("channel_post");
// Forward otomatis dari channel `ctx.senderChat`:
bot.on("message:is_automatic_forward");
// Pesan biasa yang dikirim oleh `ctx.from`
bot.on("message").filter((ctx) => ctx.senderChat === undefined);
// Admin anonim di `ctx.chat`
bot.on("message").filter((ctx) => ctx.senderChat?.id === ctx.chat.id);
// Pengguna yang mengirim pesan atas nama channel mereka `ctx.senderChat`
bot.on("message").filter((ctx) =>
ctx.senderChat !== undefined && ctx.senderChat.id !== ctx.chat.id
);
2
3
4
5
6
7
8
9
10
11
12
13
Memfilter Berdasarkan Property User
Kamu perlu melakukan request tambahan jika ingin memfilter berdasarkan kriteria user, misalnya await ctx
untuk memperoleh informasi penulis pesan tersebut. Filter query tidak akan secara otomatis melakukan request API lanjutan. Meski demikian, pemfilteran semacam itu masih cukup mudah dilakukan:
bot.on("message").filter(
async (ctx) => {
const user = await ctx.getAuthor();
return user.status === "creator" || user.status === "administrator";
},
(ctx) => {
// Menangani pesan dari creator dan para admin.
},
);
2
3
4
5
6
7
8
9
Memanfaatkan Kembali Logika Filter Query
Secara internal, bot
bergantung kepada sebuah function bernama match
. Function ini menerima sebuah filter query lalu meng-compile-nya menjadi predicate function. Predicate tersebut lalu diteruskan ke bot
untuk memfilter update.
Kamu bisa mengimpor match
secara langsung jika ingin menggunakannya di logika pemrogramanmu. Misalnya, kamu jadi bisa mengabaikan update yang cocok dengan query tertentu:
// Abaikan semua pesan atau postingan channel yang berupa teks.
bot.drop(matchFilter(":text"));
2
Dengan analogi yang sama, kamu bisa menggunakan type filter query yang digunakan di internal grammY:
Memanfaatkan Kembali Type Filter Query
Secara internal, match
menggunakan type predicates TypeScript untuk mengerucutkan type ctx
. Ia mengambil sebuah type C extends Context
dan Q extends Filter
yang kemudian menghasilkan ctx is Filter<C
. Dengan kata lain, type Filter
adalah hasil yang kamu terima untuk ctx
di middleware.
Kamu bisa meng-import Filter
secara langsung jika ingin menggunakannya di logika pemrogramanmu. Sebagai contoh, kamu bisa mendefinisikan sebuah function handler untuk menangani object context tertentu yang sudah difilter oleh filter query:
function handler(ctx: Filter<Context, ":text">) {
// Tangani object context yang sudah dikerucutkan
}
bot.on(":text", handler);
2
3
4
5
Lihat referensi API untuk
match
,Filter Filter
, danFilter
.Query
Bahasa Query
Bagian ini ditujukan untuk kamu yang ingin memahami filter query grammY secara lebih dalam. Informasi berikut tidak berisi pengetahuan yang diperlukan untuk membuat sebuah bot.
Struktur Query
Setiap query terdiri atas maksimal tiga komponen. Kami memisahkan antara query L1, L2, dan L3, seperti "message"
, "message:
, dan "message:
, secara berurutan tergantung dari seberapa banyak komponen yang dimiliki query.
Komponen-komponen query dipisahkan oleh titik dua (:
). Dari bagian awal hingga titik dua pertama atau string terakhir query tersebut, kami menyebutnya dengan komponen L1 query. Dari titik dua pertama hingga titik dua kedua atau string terakhir query tersebut, kami menyebutnya dengan komponen L2 query. Dari titik dua kedua hingga string terakhir query tersebut, kami menyebutnya dengan komponen L3 query.
Contoh:
Filter Query | Komponen L1 | Komponen L2 | Komponen L3 |
---|---|---|---|
"message" | "message" | undefined | undefined |
"message: | "message" | "entities" | undefined |
"message: | "message" | "entities" | "mention" |
Validasi Query
Meski type system bisa menangkap semua filter query yang tidak valid di compile time, namun grammY tetap memeriksa semua filter query di runtime selama proses penyusunan. Setiap filter query akan dicocokkan dengan struktur validasi untuk diperiksa apakah query tersebut memang valid. Dengan begitu, ia akan langsung gagal saat itu juga—alih-alih gagal di runtime—ketika hasilnya tidak valid, karena pernah terjadi sebelumnya, ketika bug di TypeScript menyebabkan masalah serius terhadap type inference system lanjutan yang menjadi penyokong filter query. Jika suatu saat bug tersebut muncul, kita bisa mencegah masalah serupa terjadi lagi. Selain itu, kamu juga akan diberikan pesan error yang lebih bermanfaat.
Performa
grammY mampu memeriksa setiap filter query dalam waktu konstan (amortized) per update, tidak terikat oleh struktur query ataupun update yang masuk.
Validasi filter query hanya dilakukan sekali, ketika bot diinisialisasi dan bot
dipanggil.
Ketika dimulai, grammY menurunkan function predicate dari filter query dengan cara memecahnya menjadi beberapa komponen query. Setiap komponen akan dipetakan ke sebuah function untuk diperiksa apakah properti objek tersebut cocok dengan filter terkait, atau bahkan dua kali pemeriksaan jika terdapat komponen yang dihilangkan sehingga dua nilai perlu diperiksa (misalnya, shortcut :
akan dijabarkan menjadi ["message:
sehingga perlu dilakukan dua kali pemeriksaan). Function-function ini kemudian disatukan untuk membentuk sebuah predicate yang akan memeriksa sebanyak mungkin value yang relevan untuk query, tanpa melakukan proses perulangan terhadap key object Update
.
Sistem ini menggunakan lebih sedikit operasi dibandingkan dengan beberapa library lainnya, dimana dibutuhkan beberapa pengecekan array ketika melakukan routing update. Ini membuat sistem filter query grammY selain lebih unggul juga jauh lebih cepat.
Type Safety
Sebagaimana yang disebutkan di atas, filter query secara otomatis menyeleksi properti tertentu di objek context. Penyeleksian satu atau lebih filter query tersebut berasal dari type predicate Type!
.
Meski terlihat jelas, dalam kasus tertentu, mungkin kamu tidak menemukan penyebab suatu property tidak terseleksi dengan benar. Jangan ragu untuk bertanya di obrolan grup jika kamu masih belum menemukan penyebabnya.
Mengelola berbagai macam type berikut bukanlah perkara mudah. Banyak sekali pengetahuan tentang API Bot yang masuk pada bagian grammY ini. Apabila kamu ingin memahami lebih dalam bagaimana type ini dibuat, berikut pembahasan lengkapnya yang bisa kamu tonton di You