Context
Object Context
(Referensi API grammY) merupakan komponen penting di grammY.
Setiap kali kamu menambahkan listener ke object bot, listener ini akan menerima sebuah object context.
// Ini adalah listener atau penyimak. Tugasnya menyimak pesan masuk.
bot.on("message", (ctx) => {
// `ctx` adalah object `Context`.
});
Kamu bisa menggunakan object context untuk:
Harap diketahui bahwa object context biasanya disebut ctx
.
Informasi yang Tersedia
Ketika pengguna mengirim pesan ke bot, kamu dapat mengakses pesan itu melalui ctx
. Sebagai contoh, untuk mendapatkan pesan teks, kamu dapat melakukan ini:
bot.on("message", (ctx) => {
// `txt` akan memiliki type `string` ketika memproses pesan berjenis teks.
// Atau bisa juga menjadi type `undefined` jika pesan tidak memiliki teks sama sekali,
// Misalnya foto, stiker, dan jenis pesan lainnya.
const txt = ctx.message.text;
});
Kamu juga dapat mengakses property lain dari object message, misal ctx
untuk memperoleh informasi dari suatu chat asal pesan tersebut dikirim. Lihat bagian Message
di Referensi API Bot Telegram untuk mengetahui data apa saja yang tersedia. Cara lainnya, kamu dapat dengan mudah menggunakan auto-complete di code editor untuk menelusuri pilihan-pilihan yang tersedia.
Kalau kamu memasang listener untuk jenis pesan lainnya, ctx
juga akan memberi informasi sesuai dengan jenis pesan tersebut. Contoh:
bot.on("edited_message", (ctx) => {
// Mendapatkan isi pesan baru yang diedit.
const teksPesan = ctx.editedMessage.text;
});
Bahkan, kamu bisa mengakses raw object dari sebuah Update
yang dikirimkan Telegram ke bot-mu (Referensi Bot API Telegram). Object update ini (ctx
) berisi data induk yang menjadi rujukan ctx
dan sejenisnya.
Object context selalu berisi informasi tentang bot-mu, yang dapat diakses melalui ctx
.
Shortcut
Ada sejumlah shortcut yang tersedia untuk object context.
Shortcut | Deskripsi |
---|---|
ctx | Mendapatkan object message, termasuk yang sudah diedit |
ctx | Mendapatkan object chat |
ctx | Mendapatkan object chat pengirim dari ctx (untuk pesan grup/channel anonim) |
ctx | Mendapatkan informasi penulis pesan, callback query, dan lainnya |
ctx | Mendapatkan id pesan inline dari callback query atau hasil inline yang dipilih |
Dengan kata lain, kamu juga bisa melakukan ini:
bot.on("message", (ctx) => {
// Mendapatkan isi teks pesan.
const teks = ctx.msg.text;
});
bot.on("edited_message", (ctx) => {
// Mendapatkan isi teks pesan yang diedit.
const teks = ctx.msg.text;
});
Bahkan, jika mau, kamu bisa mengabaikan ctx
, ctx
, ctx
dan seterusnya, cukup gunakan ctx
saja.
Pemeriksaan Melalui Has Checks
Context object memiliki beberapa method yang bisa kamu gunakan untuk memeriksa data yang ada di dalamnya. Contohnya, kamu bisa memanggil ctx
untuk memeriksa apakah context object tersebut terdapat sebuah command /start
. Itulah kenapa method ini dinamakan has checks.
Kapan Waktu yang Tepat untuk Menggunakan Has Checks?
Method ini menggunakan logika yang sama yang digunakan oleh bot
. Kami menyarankan kamu untuk selalu menggunakan filter queries dan method-method lain yang serupa. has checks sebaiknya digunakan di plugin conversations.
has checks secara tepat mengerucutkan type context terkait. Artinya, ia melakukan pengecekan apakah suatu context berisi data callback query. Jika ditemukan, TypeScript akan diberitahu bahwa context tersebut memiliki field ctx
di dalamnya.
if (ctx.hasCallbackQuery(/query-data-\d+/)) {
// `ctx.callbackQuery.data` ditemukan
const data: string = ctx.callbackQuery.data;
}
Hal yang sama juga berlaku untuk has checks lainnya. Lihat referensi API context object untuk mengetahui semua has checks yang tersedia. Selain itu, lihat juga referensi API untuk static property Context
yang bisa kamu gunakan untuk membuat predicate function memeriksa beberapa context object secara efisien.
Aksi yang Tersedia
Jika ingin menanggapi pesan pengguna, kamu bisa menuliskan ini:
bot.on("message", async (ctx) => {
// Mendapatkan id chat.
const idChat = ctx.msg.chat.id;
// Teks yang akan dikirim.
const teks = "Pesanmu sudah kuterima!";
// Kirim balasan.
await bot.api.sendMessage(idChat, teks);
});
Kalau diperhatikan, kamu bisa mencermati ada dua hal yang tidak optimal dari kode tersebut:
- Kita harus memiliki akses ke object
bot
. Berarti, untuk merespon pesan, kita harus meneruskan objectbot
ke seluruh bagian kode. Cukup merepotkan ketika kita memiliki lebih dari satu file source code dan memasang listener yang tersebar di berbagai tempat. - Kita perlu mengambil id chat dari context tersebut, lalu meneruskannya kembali ke
send
. Hal ini tentu merepotkan juga, karena kemungkinan besar kamu selalu ingin merespon ke pengguna yang sama yang telah mengirim pesan itu. Bayangkan betapa seringnya kamu mengetik hal yang sama berulang-ulang!Message
Mengenai poin 1, object context sudah menyediakan akses ke object API yang sama dengan yang kamu temukan di bot
, yang disebut ctx
. Kamu sekarang bisa menulis ctx
sebagai gantinya dan tidak perlu lagi meneruskannya ke objek bot
. Mudah, bukan?
Tetapi, kehebatan sesungguhnya adalah dalam mengatasi poin 2. Object context memungkinkan kamu mengirim balasan sesederhana ini:
bot.on("message", async (ctx) => {
await ctx.reply("Pesanmu sudah kuterima!");
});
// Atau bahkan lebih singkat lagi:
bot.on("message", (ctx) => ctx.reply("Ok. Diterima, Bos!"));
Mantap! 🎉
Di balik layar, context sudah tahu id chat pesan tersebut, yaitu ctx
. Jadi, ia hanya perlu menyediakan method reply
untuk mengirim pesan kembali ke chat yang sama. Untuk melakukannya, reply
memanggil kembali send
dengan id chat yang sudah terisi sebelumnya. Sehingga, kamu tidak perlu menuliskan id chat lagi.
Efeknya, semua method pada object context sekarang bisa menggunakan opsi-opsi dari object type Other
, seperti yang sudah dijelaskan sebelumnya. Opsi ini dapat digunakan untuk memasukkan konfigurasi lebih lanjut ke setiap pemanggilan API.
Fitur Reply Telegram
Meskipun method ini disebut ctx
di grammY (dan juga di kebanyakan framework lainnya), ia tidak menggunakan fitur reply dari Telegram dimana pesan sebelumnya terhubung satu sama lain. Lihat materi sebelumnya mengenai fitur reply.
Kalau kamu membaca Referensi API Bot Telegram, di situ terdapat sejumlah opsi, seperti parse
, disable
, dan reply
. Nah, yang opsi terakhir ini bisa digunakan untuk membuat pesan menjadi sebuah reply:
await ctx.reply("^ Aku me-reply pesan ini!", {
reply_to_message_id: ctx.msg.message_id,
});
Opsi object yang sama dapat juga digunakan di bot
dan ctx
. Gunakan auto-complete untuk melihat opsi yang tersedia langsung di code editor.
Umumnya, setiap method di ctx
memiliki shortcut dengan nilai yang sudah terisi sebelumnya, seperti ctx
untuk membalas menggunakan foto, atau ctx
untuk mendapatkan link undangan chat yang bersangkutan. Jika ingin tahu pintasan apa saja yang tersedia, auto-complete beserta Referensi API grammY adalah kawan baikmu.
Harap dicatat bahwa mungkin adakalanya kamu tidak ingin merespon ke chat yang sama. Untuk itu, kamu bisa kembali menggunakan method ctx
, lalu menentukan sendiri opsi-opsinya. Sebagai contoh, jika kamu menerima pesan dari Ani lalu hendak meresponnya dengan mengirim pesan ke Budi, maka kamu tidak dapat menggunakan ctx
karena method ini akan selalu mengirim pesan ke Ani. Sebagai gantinya, gunakan ctx
lalu tentukan id chat milik Budi.
Bagaimana Object Context Dibuat
Setiap kali bot menerima pesan baru dari Telegram, pesan tersebut dibungkus dalam sebuah object update. Bahkan, object update tidak hanya berisi pesan baru, tetapi juga hal-hal lain, seperti pengeditan pesan, jawaban polling, dan banyak lagi.
Untuk setiap update yang masuk, akan dibuatkan persis satu object context baru. Sehingga, context untuk update yang berbeda adalah object yang tidak saling berkaitan. Mereka hanya mereferensikan informasi bot yang sama melalui ctx
.
Object context yang sama untuk satu update akan didistribusikan ke semua middleware bot.
Memodifikasi Object Context
Jika kamu masih asing dengan object context, tak perlu risau memikirkan sisa dari halaman ini. Langsung di-skip saja.
Kamu dapat memasang property punyamu sendiri ke sebuah object context.
Melalui Middleware (Direkomendasikan)
Modifikasi bisa dilakukan dengan mudah melalui middleware.
Middleware? Tupperware jenis apa, tuh?
Materi ini memerlukan pemahaman yang baik mengenai middleware. Jika kamu belum membaca materi middleware, berikut ringkasan singkatnya.
Perlu kamu ketahui bahwa beberapa handler mampu memproses object context yang sama. Ada juga sebuah handler khusus yang berfungsi untuk memodifikasi ctx
sebelum handler-handler lain dijalankan. Hasil modifikasi tersebut akan digunakan oleh handler-handler berikutnya.
Idenya adalah kamu perlu memasang middleware terlebih dahulu sebelum listener-listener dijalankan. Dengan begitu, kamu bisa menentukan berbagai property yang diinginkan di dalam handler-handler tadi.
Sebagai ilustrasi, katakanlah kamu hendak mengatur property ctx
dari object context. Di contoh berikut, kamu akan menggunakannya untuk menyimpan beberapa konfigurasi, dengan tujuan agar semua handler bisa mengaksesnya. Konfigurasi tersebut akan mempermudah bot untuk mendeteksi apakah pesan dikirim oleh pengguna biasa atau developer bot itu sendiri.
Tepat sesudah membuat bot, lakukan hal ini:
const BOT_DEVELOPER = 123456; // Id chat developer si pembuat bot
bot.use(async (ctx, next) => {
// Modifikasi object context dengan mengatur config-nya.
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
// Jalankan handler-handler selanjutnya.
await next();
});
Setelah itu, kamu bisa menggunakan ctx
di handler lain.
bot.command("start", async (ctx) => {
// Gunakan context hasil modifikasi di sini!
if (ctx.config.isDeveloper) await ctx.reply("Selamat datang, Tuanku!");
else await ctx.reply("Halo, aku adalah bot!");
});
Sayangnya, TypeScript tidak mengetahui kalau ctx
telah dimodifikasi meski kamu sudah memasukkan property dengan benar. Akibatnya, meskipun kode akan bekerja di runtime, tetapi ia tidak bisa di-compile. Untuk mengatasinya kamu perlu menentukan type context beserta property-nya.
interface BotConfig {
botDeveloper: number;
isDeveloper: boolean;
}
type MyContext = Context & {
config: BotConfig;
};
Type baru My
sekarang secara akurat mendeskripsikan object context bot kamu.
Pastikan type yang dibuat sesuai dengan property-property yang kamu gunakan!
Kamu bisa menggunakan type baru dengan memasangnya ke constructor Bot
.
const bot = new Bot<MyContext>("<token_bot_kamu>");
Hasil akhirnya menjadi seperti ini:
const BOT_DEVELOPER = 123456; // Id chat developer
// Tentukan type context hasil modifikasi.
interface BotConfig {
botDeveloper: number;
isDeveloper: boolean;
}
type MyContext = Context & {
config: BotConfig;
};
const bot = new Bot<MyContext>("<token_bot_kamu>");
// Atur property yang diinginkan di object context.
bot.use(async (ctx, next) => {
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
await next();
});
// Gunakan object context modifikasi ke handler terkait.
bot.command("start", async (ctx) => {
if (ctx.config.isDeveloper) await ctx.reply("Selamat datang, Tuanku!");
else await ctx.reply("Halo, aku adalah bot!");
});
const BOT_DEVELOPER = 123456; // Id chat developer
const bot = new Bot("<token_bot_kamu>");
// Atur property yang diinginkan di object context.
bot.use(async (ctx, next) => {
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
await next();
});
// Gunakan object context modifikasi ke handler terkait.
bot.command("start", async (ctx) => {
if (ctx.config.isDeveloper) await ctx.reply("Selamat datang, Tuanku!");
else await ctx.reply("Halo, aku adalah bot!");
});
Type context modifikasi juga bisa diteruskan ke komponen lain yang menangani middleware, contohnya composer.
const composer = new Composer<MyContext>();
Beberapa plugin juga mengharuskan kamu menentukan type context modifikasi, contohnya plugin router dan plugin menu. Type semacam ini dinamakan dengan context flavor, seperti yang dijelaskan di bawah sini.
Melalui Inheritance
Cara lain untuk memodifikasi property object context adalah dengan membuat subclass dari class Context
.
class MyContext extends Context {
// ...
}
Meski bisa dilakukan, kami lebih merekomendasikan untuk memodifikasi object context melalui middleware, karena ia lebih fleksibel dan bekerja lebih baik ketika dipasang plugin.
Sekarang, kita akan lihat bagaimana caranya.
Ketika membuat bot, kamu bisa meneruskan constructor context hasil modifikasi yang nantinya akan digunakan untuk membuat object context.
Ingat! Class kamu harus meng-extend Context
.
import { Bot, Context } from "grammy";
import type { Update, UserFromGetMe } from "@grammyjs/types";
// Definisikan class context khusus.
class MyContext extends Context {
// Tentukan property yang diinginkan.
public readonly customProp: number;
constructor(update: Update, api: Api, me: UserFromGetMe) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Masukkan constructor class context modifikasi sebagai sebuah opsi.
const bot = new Bot("<token>", {
ContextConstructor: MyContext,
});
bot.on("message", (ctx) => {
// `ctx` sekarang mempunyai type `MyContext`!
const prop = ctx.customProp;
});
bot.start();
const { Bot, Context } = require("grammy");
// Definisikan class context khusus.
class MyContext extends Context {
// Tentukan property yang diinginkan.
public readonly customProp;
constructor(update, api, me) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Masukkan constructor class context modifikasi sebagai sebuah opsi.
const bot = new Bot("<token>", {
ContextConstructor: MyContext,
});
bot.on("message", (ctx) => {
// `ctx` sekarang mempunyai type `MyContext`!
const prop = ctx.customProp;
});
bot.start();
import { Bot, Context } from "https://deno.land/x/grammy@v1.11.2/mod.ts";
import type { Update, UserFromGetMe } from "https://esm.sh/@grammyjs/types";
// Definisikan class context khusus.
class MyContext extends Context {
// Tentukan property yang diinginkan.
public readonly customProp: number;
constructor(update: Update, api: Api, me: UserFromGetMe) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Masukkan constructor class context modifikasi sebagai sebuah opsi.
const bot = new Bot("<token>", {
ContextConstructor: MyContext,
});
bot.on("message", (ctx) => {
// `ctx` sekarang mempunyai type `MyContext`!
const prop = ctx.customProp;
});
bot.start();
Perhatikan bagaimana type context hasil modifikasi sudah ditentukan secara otomatis ketika kamu menggunakan subclass. Sehingga, kamu tidak perlu lagi menulis Bot<My
karena constructor subclass sudah ditentukan di dalam opsi object new Bot()
.
Akan tetapi, dengan menggunakan metode ini membuatnya sangat sulit—bahkan mungkin mustahil—untuk menginstal plugin, karena plugin-plugin tersebut sering kali mengharuskan kamu untuk menggunakan context flavor.
Context Flavor
Context flavor adalah suatu cara untuk memberitahu TypeScript mengenai adanya property baru di dalam object context-mu. Property-property ini dapat disertakan di dalam plugin atau module lain yang kemudian diinstal di bot kamu.
Context flavor juga mampu mengubah type property yang sudah ada menggunakan prosedur otomatis yang sudah ditentukan oleh plugin tersebut.
Additive Context Flavor
Context flavor terdiri atas dua jenis. Jenis yang paling sederhana disebut dengan additive context flavor. Kapanpun kita berbicara mengenai context flavor, yang kita maksud adalah jenis ini. Mari kita lihat bagaimana cara kerjanya.
Sebagai contoh, ketika kamu memiliki data session, maka kamu harus menambahkan ctx
ke dalam type context tersebut. Jika tidak dilakukan
- Kamu tidak bisa memasang plugin sessions bawaan; dan
- Kamu tidak memiliki akses ke
ctx
di listener kamu..session
Meski kami menggunakan session sebagai contoh, namun ini juga berlaku untuk berbagai hal lainnya. Bahkan, sebagian besar plugin menggunakan sebuah context flavor agar kamu bisa menggunakannya dengan baik.
Type context hanyalah sebuah type kecil yang mendefinisikan property-property apa saja yang harus ditambahkan ke dalam type context. Mari kita lihat contoh flavor berikut.
interface SessionFlavor<S> {
session: S;
}
Type Session
(referensi API) di atas cukup sederhana: ia hanya mendefinisikan property session
. Ia mengambil type parameter yang akan mendefinisikan struktur asli dari sebuah data session.
Lantas, manfaatnya apa? Berikut bagaimana kamu bisa memberi flavor ke context dengan data session:
import { Context, SessionFlavor } from "grammy";
// Deklarasikan `ctx.session` menjadi type `string`.
type MyContext = Context & SessionFlavor<string>;
Sekarang kamu dapat menggunakan plugin session serta memiliki akses ke ctx
:
bot.on("message", (ctx) => {
// Sekarang `str` memiliki type `string`.
const str = ctx.session;
});
Transformative Context Flavor
Jenis context flavor yang kedua lebih hebat lagi. Ketimbang dipasang menggunakan operator &
, ia cuma perlu dipasang seperti ini:
import { Context } from "grammy";
import { FlavorA } from "plugin-ku";
type ContextKu = FlavorA<Context>;
Selebihnya sama saja.
Setiap plugin—yang resmi—sudah tercantum di dalam dokumentasinya apakah harus menggunakan context flavor jenis additive atau transformative.
Mengombinasikan Context Flavor yang Berbeda
Jika kamu punya beberapa additive context flavor yang berbeda, tinggal dipasang seperti ini:
type ContextKu = Context & FlavorA & FlavorB & FlavorC;
Urutan context flavor tidak berpengaruh, kamu bisa mengurutkannya sesuai keinginan.
Beberapa transformative context flavor juga bisa dikombinasikan:
type ContextKu = FlavorX<FlavorY<FlavorZ<Context>>>;
Di sini, urutan context flavor akan berpengaruh. FlavorZ
mengubah Context
terlebih dahulu, lalu dilanjutkan oleh FlavorY
, dan hasilnya akan diubah kembali oleh FlavorX
. Dalam praktiknya, ini tidak perlu dikhawatirkan karena plugin biasanya tidak saling berbenturan satu sama lain.
Bahkan kamu bisa mencampur flavor additive dan flavor transformative sekaligus:
type ContextKu = FlavorX<
FlavorY<
FlavorZ<
Context & FlavorA & FlavorB & FlavorC
>
>
>;