13 β Webhook va deploy server¶
β¬ οΈ Oldingi: 12 β Maxsus xususiyatlar va plaginlar Β· π README Β· Keyingi: 14 β To'lovlar va Telegram Stars β‘οΈ
Bu bobda: botingiz Telegram'dan yangiliklarni (update'larni) qanday oladi β buning ikki yo'li bor: polling (bot Telegram'dan "yangilik bormi?" deb so'rab turadi) va webhook (Telegram sizning serveringizga o'zi POST so'rovi yuboradi). Biz bu ikkalasini chuqur taqqoslaymiz β afzallik/kamchilik jadvali bilan, qachon qaysini tanlash kerakligi bilan. So'ng grammY'ning
webhookCallbackyordamida Express, Hono va native Node "http" serverlarida webhook qabul qiluvchi endpoint quramiz;bot.api.setWebhook(url, ...)bilan webhook'ni ro'yxatdan o'tkazamiz,secret_tokenorqali soxta so'rovlardan himoyalanamiz,getWebhookInfovadeleteWebhookbilan boshqaramiz. Lokal sinov uchun ngrok/tunnel'ni, serverless (Cloudflare Workers / Vercel) eslatmasini, va eng muhim gotcha'ni β polling bilan webhook'ni bir vaqtda ishlatib bo'lmasligini (409 Conflict) ko'rib chiqamiz.Halollik eslatmasi: Bu bobdagi kod mantig'i β
webhookCallback(bot, "express"),(bot, "http"),(bot, "hono")funksiya qaytarishi; ularning ichidabot.handleUpdateorqali handler ishga tushishi; haqiqiy Express va native http server'ni 127.0.0.1 da bo'sh portda ko'tarib, soxta/startupdate'ini POST qilib 200 olish;secret_tokennoto'g'ri bo'lsa 401 rad etish β bularning hammasi offline ishga tushirib tasdiqlangan (node _verify_13.mjs, 6/6 o'tdi, token va internetsiz). JonlisetWebhook(tashqi HTTPS domeniga) haqiqiy domen, sertifikat va internet talab qiladi β u "illustrativ" deb belgilangan. ngrok va serverless misollari ham illustrativ.
Update qayerdan keladi? Polling vs webhook¶
Birinchi boblardan beri biz botni bot.start() bilan ishga tushirib keldik. U long polling rejimida ishlaydi: bot Telegram serveriga "menga yangi update bormi?" degan so'rov (getUpdates) yuboradi, javob kelmaguncha kutadi, kelgach qayta so'raydi β va shu zanjir cheksiz davom etadi. Bu rivojlanish (development) va kichik botlar uchun mukammal: hech narsa sozlash shart emas, faqat token va internet.
Lekin Telegram update'larni yetkazishning ikkinchi yo'li ham bor β webhook. Bunda siz Telegram'ga "menga update kelganda, mana shu HTTPS manzilimga o'zing POST qilib yubor" deb aytasiz. Endi bot Telegram'dan so'rab turmaydi β aksincha, Telegram update bo'lganda sizning serveringizga keladi. Bu production uchun tez va resurs-tejamkor yondashuv.
Tasavvur qiling: polling β bu pochta qutisiga har daqiqada borib "xat bormi?" deb tekshirish. Webhook β bu pochtachi xat kelganda eshigingizni o'zi taqillatishi. Birinchisi sizdan doimiy harakat talab qiladi; ikkinchisida siz xat kelguncha boshqa ishingiz bilan band bo'lasiz.
Chuqur taqqoslash¶
| Jihat | Polling (bot.start() / runner) |
Webhook (webhookCallback) |
|---|---|---|
| Update qanday keladi | Bot Telegram'dan so'rab turadi (getUpdates) |
Telegram serveringizga POST qiladi |
| HTTPS / domen | Shart emas β NAT/lokal orqasida ham ishlaydi | Shart β ochiq HTTPS domen kerak |
| Tezlik | Biroz kechikish bo'lishi mumkin | Deyarli darhol yetkaziladi |
| Resurs | Doimiy ulanish, ozroq band | Bo'sh turadi, update kelganda uyg'onadi |
| Sozlash | Juda oson (token yetarli) | Murakkabroq (server, sertifikat, setWebhook) |
| Serverless | Mos emas (doimiy jarayon kerak) | Juda mos (Cloudflare Workers, Vercel) |
| Lokal sinov | To'g'ridan-to'g'ri | Tunnel kerak (ngrok) |
| Bir nechta nusxa | 409 Conflict (faqat bitta polling) | Bir nechta nusxa yuk taqsimlay oladi |
Qachon qaysi?¶
- Polling tanlang: rivojlanishda (dev), kichik yoki shaxsiy bot, oddiy VPS/hosting, HTTPS domen sozlashni xohlamasangiz. 17-bobda
@grammyjs/runnerbilan polling'ni production darajasiga ham olib chiqamiz. - Webhook tanlang: production'da yuqori yuklamali bot, deyarli darhol javob kerak bo'lsa, serverless platformaga (Cloudflare Workers, Vercel, Deno Deploy) joylashtirsangiz, yoki bir nechta nusxa orasida yukni taqsimlamoqchi bo'lsangiz.
Eslatma: Ko'pchilik loyihalar dev'da polling, prod'da webhook ishlatadi. Bitta kod bazasida ikkalasini ham qo'llab-quvvatlash oson β handlerlaringiz (
bot.command,bot.on) bir xil qoladi, faqat botni qanday ishga tushirish o'zgaradi. Buni bobning oxirida ko'ramiz.
webhookCallback β grammY'ning webhook ko'prigi¶
grammY webhook'ni qo'lda yozishni talab qilmaydi. webhookCallback funksiyasi kelayotgan HTTP so'rovni o'qiydi, uning JSON tanasini Update obyektiga aylantiradi va bot.handleUpdate orqali sizning handlerlaringizga uzatadi β keyin Telegram'ga 200 OK javob qaytaradi.
import { webhookCallback } from "grammy";
const handler = webhookCallback(bot, "express"); // funksiya qaytaradi
webhookCallback(bot, framework) β ikkinchi argument qaysi freymvork bilan ishlayotganingizni bildiradi. Qo'llab-quvvatlanadigan qiymatlar:
"express"β Express.js (eng mashhur Node freymvorki)."http"β Node'ning ichki (native)http/httpsmoduli, hech qanday freymvorksiz."hono"β Hono (zamonaviy, yengil, serverless'ga mos freymvork)."std/http"β Deno yoki Web-standartRequest/Responsemuhitlari (Cloudflare Workers va h.k.).
Tasdiqlangan: Men
webhookCallback(bot, "express"),(bot, "http")va(bot, "hono")chaqiruvlarining uchalasi ham funksiya qaytarishini offline tekshirdim (typeof === "function", 6/6 test o'tdi). Bu funksiyalarni mos freymvork'ga ulaganingizda webhook qabul qiluvchi endpoint hosil bo'ladi.
Express server misoli¶
Express β Node'da eng keng tarqalgan veb-freymvork. Webhook endpoint quyidagicha quriladi:
import { Bot, webhookCallback } from "grammy";
import express from "express";
const bot = new Bot(process.env.BOT_TOKEN);
// Handlerlar β pollingdagi bilan AYNAN bir xil
bot.command("start", (ctx) => ctx.reply("Salom! Men webhook orqali ishlayapman."));
bot.on("message:text", (ctx) => ctx.reply(`Siz yozdingiz: ${ctx.message.text}`));
const app = express();
app.use(express.json()); // MUHIM: body'ni JSON sifatida o'qiydi
// Telegram shu manzilga POST qiladi
app.use("/telegraf-yoq-grammy", webhookCallback(bot, "express"));
app.listen(3000, () => console.log("Server 3000-portda ishlamoqda"));
Bu yerda nima bo'lyapti:
express.json()β bu middleware Telegram yuborgan JSON tanasini o'qibreq.bodyga qo'yadi. Buni unutsangizwebhookCallbackbody'ni topa olmaydi va bot jim qoladi (eng ko'p uchraydigan xato β pastdagi jadvalga qarang).app.use("/telegraf-yoq-grammy", webhookCallback(bot, "express"))β/telegraf-yoq-grammymanziliga kelgan POST so'rovniwebhookCallbackishlovchisiga topshiramiz. Manzil yo'lini (path) maxfiy va tasodifiy qiling β bu qo'shimcha himoya (lekinsecret_tokendan ko'ra kuchsizroq).app.listen(3000, ...)β server portni tinglay boshlaydi.
Tasdiqlangan: Men aynan shu naqsh bilan haqiqiy Express app'ni 127.0.0.1 da bo'sh portda ko'tarib, soxta
/startupdate'ini POST qildim. Server 200 qaytardi va ichidagibot.command("start", ...)handleri ishga tushibsendMessagechaqirdi (offline transformer bilan ushlandi). Bu "ishladi" deganim β haqiqatan ko'rilgan natija.Diqqat β Express 5: grammY bilan kelgan zamonaviy Express'da (v5)
express.json()standart o'rnatilgan. Agar eski qo'llanmalardabody-parserpaketini alohida o'rnatishni ko'rsangiz β endi shart emas,express.json()Express'ning o'zida bor.
Hono server misoli¶
Hono β yengil va tez freymvork, ayniqsa serverless (Cloudflare Workers, Vercel Edge, Deno) uchun mashhur. U Web-standart Request/Response bilan ishlaydi:
import { Bot, webhookCallback } from "grammy";
import { Hono } from "hono";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("start", (ctx) => ctx.reply("Salom Hono'dan!"));
const app = new Hono();
app.post("/webhook", webhookCallback(bot, "hono"));
// Node muhitida ishga tushirish (serve adapter bilan):
// import { serve } from "@hono/node-server";
// serve({ fetch: app.fetch, port: 3000 });
export default app; // Cloudflare Workers / Deno bevosita app'ni ishlatadi
Hono'da express.json() ga o'xshash narsa kerak emas β webhookCallback(bot, "hono") body'ni o'zi o'qiydi. Endpoint'ni app.post(...) bilan ro'yxatdan o'tkazasiz, chunki Telegram faqat POST qiladi.
Tasdiqlangan: Men Hono app'ini
app.fetch(new Request(..., { method: "POST", body: JSON.stringify(update) }))orqali soxta so'rov bilan chaqirdim βResponseobyektiningstatusmaydoni 200 bo'ldi va handler ishladi. Bu Node'da haqiqiy server ko'tarmasdan ham (Web-standart fetch interfeysi orqali) tasdiqlanadi.
Native "http" misoli (freymvorksiz)¶
Agar qo'shimcha bog'liqlik (dependency) o'rnatishni xohlamasangiz, Node'ning ichki http moduli yetarli:
import { Bot, webhookCallback } from "grammy";
import { createServer } from "node:http";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("start", (ctx) => ctx.reply("Salom native http'dan!"));
const handle = webhookCallback(bot, "http"); // (req, res) ishlovchisi
const server = createServer(handle);
server.listen(3000, () => console.log("Native http server 3000-portda"));
Bu yerda diqqat qiling: "http" adapteri body'ni o'zi o'qiydi, shuning uchun express.json() ga o'xshash narsa kerak emas. webhookCallback(bot, "http") to'g'ridan-to'g'ri (req, res) qabul qiluvchi funksiya qaytaradi va createServer ga beriladi.
Tasdiqlangan: Men native
httpserver'ni 127.0.0.1 da bo'sh portda ko'tarib, soxta/startPOST qildim β 200 qaytardi va handlersendMessagechaqirdi.Eslatma β HTTPS: Yuqoridagi misollarda biz oddiy HTTP (
httpmoduli) ishlatdik, chunki bu lokal sinov uchun. Telegram esa webhook manzili HTTPS bo'lishini talab qiladi. Production'da odatda HTTPS'ni Nginx, Caddy yoki bulut platformasi (reverse proxy) qatlami ta'minlaydi, sizning Node serveringiz uning orqasida oddiy HTTP'da turadi. Buni 17-bobda batafsil ko'ramiz.
setWebhook β Telegram'ga manzilingizni aytish¶
Server tayyor bo'ldi, lekin Telegram hali update'larni qayerga yuborishni bilmaydi. bot.api.setWebhook bilan unga aytasiz:
await bot.api.setWebhook("https://mening-domenim.uz/telegraf-yoq-grammy", {
secret_token: "juda_maxfiy_satr_2026",
drop_pending_updates: true,
allowed_updates: ["message", "callback_query"],
});
Illustrativ: Yuqoridagi
setWebhookchaqiruvi haqiqiy HTTPS domen, ochiq port va internet talab qiladi β uni token va serveringiz bilan o'zingiz ishga tushirasiz. Men buni offline tasdiqlay olmadim (tarmoq kerak), shuning uchun illustrativ deb belgilayman. LekinwebhookCallbackqabul qiluvchi tomon β server ichidagi mantiq β yuqorida haqiqatan ishga tushirib tasdiqlangan.
setWebhook(url, other) parametrlari (grammY tipi bilan tasdiqlangan):
url(birinchi argument) β HTTPS manzil. Bo'sh satr ("") yuborilsa webhook o'chiriladi.secret_tokenβ Telegram har POST so'rovidaX-Telegram-Bot-Api-Secret-Tokenheader'iga shu satrni qo'yib yuboradi. Sizning serveringiz uni tekshiradi (quyida). FaqatA-Z a-z 0-9 _ -belgilari, 1-256 ta uzunlik.drop_pending_updatesβtruebo'lsa, webhook o'rnatilguncha to'plangan eski update'larni tashlab yuboradi (bot o'chiq turganda kelganlarni qayta ishlamaslik uchun foydali).allowed_updatesβ qaysi turdagi update'larni olishni xohlaysiz (masalan["message", "callback_query"]). Bo'sh ro'yxat βchat_memberva reaksiyalardan tashqari hammasini oladi. Eslatma:chat_memberni olmoqchi bo'lsangiz uni bu ro'yxatda aniq ko'rsatishingiz kerak (19-21 boblardagi guruh boshqaruvida muhim).
Anti-eskirish: Telegram webhook uchun faqat 443, 80, 88, 8443 portlarini qo'llab-quvvatlaydi. Boshqa portni domeningiz reverse proxy orqali shu portlardan biriga yo'naltirishi kerak. Bu Telegram tomonidan belgilangan cheklov β vaqt o'tishi bilan kengayishi mumkin, shuning uchun shubha bo'lsa rasmiy hujjatni tekshiring.
secret_token bilan xavfsizlik¶
Webhook manzilingiz ochiq internetda β nazariy jihatdan har kim sizning endpoint'ingizga soxta update POST qilishi mumkin. secret_token aynan shuni oldini oladi: Telegram har so'rovga X-Telegram-Bot-Api-Secret-Token header'ini qo'shadi, sizning serveringiz uni webhookCallback ga bergan sir bilan solishtiradi. Mos kelmasa β so'rov rad etiladi.
app.use(
"/telegraf-yoq-grammy",
webhookCallback(bot, "express", { secretToken: "juda_maxfiy_satr_2026" })
);
Diqqat β nomlash:
setWebhookda parametrsecret_token(snake_case, Telegram API uslubi), lekinwebhookCallbackopsiyasidasecretToken(camelCase, grammY uslubi). Ikkalasidagi satr bir xil bo'lishi shart. Ikkala joyni ham qo'yishni unutmang β biri Telegram'ga aytadi, ikkinchisi serveringizda tekshiradi.Tasdiqlangan: Men
webhookCallback(bot, "express", { secretToken: "SUPER_MAXFIY" })bilan server ko'tarib ikki so'rov yubordim: noto'g'ri sir bilan β server 401 (rad), to'g'ri sir bilan β 200 (qabul). Demaksecret_tokenhimoyasi haqiqatan ishlaydi.
getWebhookInfo va deleteWebhook¶
Webhook holatini tekshirish va o'chirish uchun:
const info = await bot.api.getWebhookInfo();
console.log(info.url); // o'rnatilgan manzil ("" bo'lsa polling)
console.log(info.pending_update_count); // navbatdagi update soni
console.log(info.last_error_message); // oxirgi xato (agar bo'lsa)
await bot.api.deleteWebhook(); // webhook o'chadi, polling'ga qaytish mumkin
await bot.api.deleteWebhook({ drop_pending_updates: true }); // + eski update'larni tashlash
getWebhookInfo ayniqsa nosozliklarni topishda qimmatli: agar last_error_message da "Wrong response from the webhook: 401" ko'rsangiz β sir mos kelmayapti; "SSL error" ko'rsangiz β HTTPS sertifikati muammosi; pending_update_count katta bo'lsa β serveringiz update'larni qabul qila olmayapti.
Lokal sinov: ngrok va tunnellar¶
Webhook HTTPS domen talab qiladi β lekin kompyuteringizda hali domen yo'q. Yechim: tunnel xizmati lokal portingizni vaqtinchalik ommaviy HTTPS manzilga ulaydi. Eng mashhuri β ngrok.
# ngrok o'rnatilgan deb hisoblaymiz
ngrok http 3000
# Chiqishda: Forwarding https://abc123.ngrok-free.app -> http://localhost:3000
So'ng o'sha HTTPS manzilni setWebhook ga berasiz:
await bot.api.setWebhook("https://abc123.ngrok-free.app/telegraf-yoq-grammy", {
secret_token: "juda_maxfiy_satr_2026",
});
Illustrativ: ngrok haqiqiy internet ulanishi va ro'yxatdan o'tishni talab qiladi β bu blok illustrativ. Muqobillar: Cloudflare Tunnel (
cloudflared),localtunnel, yoki bulutdagi kichik VPS. ngrok bepul rejasida URL har ishga tushganda o'zgaradi, shuning uchunsetWebhookni qayta chaqirish kerak bo'ladi.Eslatma β bitta kod, ikki rejim: Lokalda tez sinash uchun webhook o'rnatish o'rniga ko'pincha polling (
bot.start()) ishlatish qulayroq β tunnel kerak emas. Webhook'ni faqat lokal webhook mantig'ini sinashda yoqing.
Serverless: Cloudflare Workers va Vercel¶
Webhook'ning eng kuchli tomoni β u serverless platformalarga mukammal mos keladi. Serverless'da doimiy ishlab turuvchi server yo'q; faqat so'rov kelganda funksiyangiz uyg'onadi. Polling bunday muhitda ishlamaydi (doimiy jarayon kerak), lekin webhook xuddi shu uchun yaratilgan.
webhookCallback ning "hono" va "std/http" adapterlari aynan shu muhitlarga mo'ljallangan:
// Cloudflare Workers / Deno Deploy uslubi (Web-standart Request/Response)
import { Bot, webhookCallback } from "grammy";
export default {
async fetch(request, env) {
const bot = new Bot(env.BOT_TOKEN); // sirlar env'dan
bot.command("start", (ctx) => ctx.reply("Serverless'dan salom!"));
return webhookCallback(bot, "std/http")(request);
},
};
Illustrativ: Serverless deploy haqiqiy platforma hisobini (Cloudflare/Vercel),
wrangler/vercelCLI va internetni talab qiladi β bu blok illustrativ. Lekin g'oya muhim:webhookCallbackadapteri shu muhitlarga mos, handlerlaringiz esa o'zgarmaydi. Botingizni 17-bobda production'ga chiqarishni batafsil ko'ramiz.Anti-eskirish: Serverless platformalar va ularning grammY hosting qo'llanmalari tez o'zgaradi. Aniq deploy qadamlari uchun har doim grammY'ning rasmiy "hosting" hujjatini (https://grammy.dev/hosting) tekshiring β bu yerdagi kod naqshi barqaror, lekin platforma sozlamalari yangilanib turadi.
Eng katta gotcha: polling va webhook BIR VAQTDA ishlamaydi (409)¶
Bu eng ko'p duch keladigan muammo. Telegram bitta bot uchun bir vaqtning o'zida faqat bitta update yetkazish usulini qo'llaydi:
- Agar webhook o'rnatilgan bo'lsa,
getUpdates(polling) ishlamaydi. - Agar siz
bot.start()(polling) ishlatib turib, yanasetWebhookqilsangiz β yoki bir nechta polling nusxasini ishga tushirsangiz β Telegram409: Conflictxatosini qaytaradi.
GrammyError: Call to 'getUpdates' failed! (409: Conflict: terminated by other getUpdates request;
make sure that only one bot instance is running)
Yechim β rejimni almashtirishda avval eskisini o'chirish:
// Webhook'dan polling'ga qaytmoqchi bo'lsangiz:
await bot.api.deleteWebhook();
bot.start();
// Polling'dan webhook'ga o'tmoqchi bo'lsangiz:
// bot.start() ni TO'XTATING (jarayonni o'chiring), keyin:
await bot.api.setWebhook("https://domen/path", { secret_token: "..." });
Diqqat:
409 Conflictko'rsangiz, birinchi gumon β bir xil bot tokeni bilan ikki marta ishga tushirgansiz (masalan eski terminal hali ochiq, yoki dev va prod bir tokendan foydalanmoqda). Har muhit uchun alohida token ishlating vagetWebhookInfobilan holatni tekshiring.
Bitta kodda ikki rejimni qo'llab-quvvatlash¶
Amaliy naqsh β muhit o'zgaruvchisiga qarab polling yoki webhook tanlash. Handlerlaringiz o'zgarmaydi:
import { Bot, webhookCallback } from "grammy";
import express from "express";
const bot = new Bot(process.env.BOT_TOKEN);
// --- Handlerlar (har ikki rejimda bir xil) ---
bot.command("start", (ctx) => ctx.reply("Salom!"));
bot.on("message:text", (ctx) => ctx.reply(`Aks-sado: ${ctx.message.text}`));
if (process.env.NODE_ENV === "production") {
// PROD: webhook
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express", { secretToken: process.env.WEBHOOK_SECRET }));
app.listen(3000, () => console.log("Webhook server tayyor"));
} else {
// DEV: polling
bot.start(); // illustrativ: jonli polling token talab qiladi
}
Cross-link: Botni serverga joylashtirish β VPS, Docker, pm2/systemd, HTTPS sozlash, sirlar (
.env), graceful shutdown β bularning hammasini 17-bob (Production va deploy) da batafsil ko'ramiz. Kodni Git bilan boshqarish va serverga yuborish bo'yicha Git va GitHub kitobi yordam beradi. Node.js server asoslari uchun Node.js kitobi ga qarang.aiogram bilan solishtirish: Python'dagi aiogram'da webhook
aiohttpyokiFastAPIbilanSimpleRequestHandlerorqali quriladi, polling esadp.start_polling()bilan. grammY'ningwebhookCallbackg'oyasi xuddi shu β faqat Node freymvorklariga (Express/Hono/native) moslangan. Taqqoslash uchun aiogram kitobi ga qarang.
Tez-tez uchraydigan xatolar¶
| Xato | Sabab | Yechim |
|---|---|---|
| Bot webhook'da jim, hech narsaga javob bermaydi | Express'da app.use(express.json()) UNUTILGAN |
webhookCallback dan oldin app.use(express.json()) qo'shing |
| Telegram "SSL error" / webhook o'rnatilmaydi | URL HTTPS emas yoki sertifikat yaroqsiz | HTTPS ishlating (Telegram HTTP'ni rad etadi); reverse proxy/sertifikat sozlang |
409: Conflict |
Polling va webhook bir vaqtda, yoki ikki nusxa ishlamoqda | Avval deleteWebhook() qiling; har muhitga alohida token; bitta nusxa qoldiring |
| Soxta so'rovlar / begona POST'lar | secret_token tekshirilmagan |
setWebhook'ga secret_token, webhookCallback'ga secretToken qo'shing |
getWebhookInfo'da pending_update_count o'sib bormoqda |
Server update'larni qabul qila olmayapti yoki sekin | Server loglarini tekshiring; last_error_message ni o'qing; handler tez javob qaytarsin |
Webhook ishlaydi, lekin chat_member kelmaydi |
allowed_updates da ko'rsatilmagan |
setWebhook'da allowed_updates: ["message", "chat_member", ...] qo'shing |
| Port ishlamaydi | Telegram faqat 443/80/88/8443 portlarini qabul qiladi | Reverse proxy bilan ruxsat etilgan portga yo'naltiring |
| Lokalda webhook test qilolmayapman | localhost ochiq HTTPS emas | ngrok/Cloudflare Tunnel ishlating, yoki dev'da polling'ga qayting |
Mashqlar¶
Quyidagi mashqlarning ko'pi offline tekshiriladi: soxta Update ni bot.handleUpdate ga (yoki haqiqiy mahalliy server orqali POST qilib) uzatib, chiqayotgan API chaqiruvlarini transformer bilan ushlaymiz. Mock muhit naqshi (bu bobda ishlatilgani):
import { Bot, webhookCallback } from "grammy";
import express from "express";
import http from "node:http";
import assert from "node:assert/strict";
function makeBot() {
const bot = new Bot("12345:FAKE");
bot.botInfo = { id: 12345, is_bot: true, first_name: "T", username: "t_bot",
can_join_groups: true, can_read_all_group_messages: false, supports_inline_queries: false,
can_connect_to_business: false, has_main_web_app: false };
const calls = [];
bot.api.config.use((prev, method, payload) => {
calls.push({ method, payload });
if (method === "sendMessage")
return Promise.resolve({ ok: true, result: { message_id: 1, date: 0,
chat: { id: payload.chat_id, type: "private" }, text: payload.text } });
return Promise.resolve({ ok: true, result: true });
});
return { bot, calls };
}
function mkUpdate(text, id = 1) {
const message = { message_id: id, date: 0, text,
chat: { id: 777, type: "private" }, from: { id: 777, is_bot: false, first_name: "Ali" } };
if (text.startsWith("/"))
message.entities = [{ type: "bot_command", offset: 0, length: text.split(/\s/)[0].length }];
return { update_id: id, message };
}
Oson¶
- webhookCallback turi.
webhookCallback(bot, "express"),(bot, "http")va(bot, "hono")ni chaqiring va uchalasi hamtypeof === "function"ekaniniassertbilan tasdiqlang. - handleUpdate bilan handler.
bot.command("start", (ctx) => ctx.reply("Salom!"))ni ro'yxatdan o'tkazing,mkUpdate("/start")nibot.handleUpdatega bering vacallsichidasendMessagematni"Salom!"ekanini tekshiring. - Express vs http β qaysisida express.json() kerak? Ikki misol yozing (
"express"va"http") va izohda har biridaexpress.json()kerakmi yoki yo'qligini yozing. Sababini bir jumlada tushuntiring. - deleteWebhook izohi.
getUpdatesva webhook bir vaqtda ishlamasligini hisobga olib, webhook'dan polling'ga to'g'ri o'tish ketma-ketligini (kod izohlari bilan) yozing.
O'rta¶
- Express server 200. Haqiqiy Express app ni
127.0.0.1da bo'sh portda ko'taring, soxta/startupdate'ini POST qiling va javob status200ekanini tasdiqlang. - http server 200. Xuddi shu narsani native
"http"adapteri bilan qiling (createServer(webhookCallback(bot, "http"))). - Handler webhook ichida ishladimi. 5-mashqdagi server ichida
bot.command("start", (ctx) => ctx.reply("Webhook!"))bo'lsin; POST'dan keyincallsichidasendMessagematni"Webhook!"ekanini tekshiring. - Hono fetch. Hono app yarating,
app.post("/webhook", webhookCallback(bot, "hono"))ulang,app.fetch(new Request(...))bilan soxta POST yuboring vaResponse.status === 200ekanini tasdiqlang.
Qiyin¶
- secret_token himoyasi.
webhookCallback(bot, "express", { secretToken: "MAXFIY" })bilan server ko'taring. Ikki POST yuboring: biri noto'g'ri (yoki yo'q)X-Telegram-Bot-Api-Secret-Tokenheader bilan, biri to'g'ri. Noto'g'risi200BO'LMASLIGINI (rad), to'g'risi200ekanini tasdiqlang. - Ikki rejim tanlovi.
process.env.NODE_ENVga qarab polling yoki webhook tanlovchi funksiya yozing (webhook tarmoqqa chiqmasin β faqatwebhookCallbackqaytishini tekshiring). Test'daNODE_ENVni almashtirib har ikki shoxni qoplang. - Bir nechta update ketma-ket. Express webhook server'ga
/startkeyin oddiy matn ("salom") update'larini ketma-ket POST qiling.callsichida ikkitasendMessageborligini va matnlari to'g'ri tartibda ekanini tasdiqlang. - getWebhookInfo'ni mock qilish. Transformer'da
method === "getWebhookInfo"bo'lsa soxtaWebhookInfo({ url: "https://x/y", pending_update_count: 0, has_custom_certificate: false }) qaytaring.await bot.api.getWebhookInfo()ni chaqiribinfo.urlto'g'ri kelishini tasdiqlang. - Maxfiy path + secret_token birga. Server'ni maxfiy path (
/sirli-1q2w3e) vasecretTokenbilan quring. To'g'ri path + to'g'ri sir β200; to'g'ri path + noto'g'ri sir β rad; noto'g'ri path β Express404. Uchchala holatni tasdiqlang.
Yechimlar
1-mashq yechimi¶
import { Bot, webhookCallback } from "grammy";
import assert from "node:assert/strict";
const { bot } = makeBot();
assert.equal(typeof webhookCallback(bot, "express"), "function");
assert.equal(typeof webhookCallback(bot, "http"), "function");
assert.equal(typeof webhookCallback(bot, "hono"), "function");
webhookCallback har bir freymvork uchun mos ishlovchi funksiya qaytaradi. Uni keyin server'ga (app.use, createServer, app.post) ulaysiz.
2-mashq yechimi¶
const { bot, calls } = makeBot();
bot.command("start", (ctx) => ctx.reply("Salom!"));
await bot.handleUpdate(mkUpdate("/start"));
const sent = calls.find((c) => c.method === "sendMessage");
assert.equal(sent.payload.text, "Salom!");
webhookCallback ichkarida aynan bot.handleUpdate ni chaqiradi. Shuning uchun handlerni to'g'ridan-to'g'ri handleUpdate orqali sinash webhook oqimining "yuragi"ni tekshiradi β server ko'tarmasdan ham. Buyruq mos kelishi uchun mkUpdate bot_command entity qo'shganiga e'tibor bering.
3-mashq yechimi¶
// Express: express.json() KERAK β Express body'ni o'zi o'qimaydi
const app = express();
app.use(express.json()); // <-- shart
app.use("/webhook", webhookCallback(bot, "express"));
// Native http: express.json() KERAK EMAS β "http" adapteri body'ni O'ZI o'qiydi
import { createServer } from "node:http";
const server = createServer(webhookCallback(bot, "http")); // <-- to'g'ridan-to'g'ri
Sabab: Express o'zi tana (body) ni o'qimaydi β buni express.json() middleware qiladi va req.body ga qo'yadi. Native "http" adapteri esa so'rov oqimini (stream) o'zi o'qib JSON'ga aylantiradi, shuning uchun qo'shimcha middleware shart emas.
4-mashq yechimi¶
// Webhook'dan polling'ga to'g'ri o'tish:
await bot.api.deleteWebhook(); // 1) Telegram'ga "endi webhook yo'q" deb aytamiz
// (ixtiyoriy) await bot.api.deleteWebhook({ drop_pending_updates: true });
bot.start(); // 2) endi getUpdates (polling) ishlaydi, 409 chiqmaydi
getUpdates va webhook bir vaqtda ishlay olmaydi. deleteWebhook ni chaqirmasdan bot.start() qilsangiz Telegram 409 Conflict qaytaradi, chunki webhook hali o'rnatilgan deb hisoblaydi.
5-mashq yechimi¶
import http from "node:http";
const { bot } = makeBot();
bot.command("start", (ctx) => ctx.reply("Salom!"));
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express"));
const server = http.createServer(app);
await new Promise((r) => server.listen(0, "127.0.0.1", r)); // 0 = bo'sh port
const { port } = server.address();
const body = JSON.stringify(mkUpdate("/start"));
const status = await new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path: "/webhook", method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
(res) => { res.resume(); res.on("end", () => resolve(res.statusCode)); });
req.on("error", reject); req.write(body); req.end();
});
await new Promise((r) => server.close(r));
assert.equal(status, 200);
listen(0, ...) operatsion tizimdan bo'sh portni so'raydi β sinov uchun ideal, hech narsa to'qnashmaydi. POST javobi 200 β webhook muvaffaqiyatli qabul qildi.
6-mashq yechimi¶
import { createServer } from "node:http";
const { bot } = makeBot();
bot.command("start", (ctx) => ctx.reply("Salom!"));
const server = createServer(webhookCallback(bot, "http")); // express.json() YO'Q
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
const body = JSON.stringify(mkUpdate("/start"));
const status = await new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path: "/", method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
(res) => { res.resume(); res.on("end", () => resolve(res.statusCode)); });
req.on("error", reject); req.write(body); req.end();
});
await new Promise((r) => server.close(r));
assert.equal(status, 200);
Native "http" adapteri butun (req, res) ni o'zi boshqaradi, shuning uchun Express ham, express.json() ham kerak emas. Path / β chunki butun server faqat webhook'ni xizmat qiladi.
7-mashq yechimi¶
const { bot, calls } = makeBot();
bot.command("start", (ctx) => ctx.reply("Webhook!"));
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express"));
const server = http.createServer(app);
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
const body = JSON.stringify(mkUpdate("/start"));
await new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path: "/webhook", method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
(res) => { res.resume(); res.on("end", resolve); });
req.on("error", reject); req.write(body); req.end();
});
await new Promise((r) => server.close(r));
assert.equal(calls.find((c) => c.method === "sendMessage").payload.text, "Webhook!");
POST 200 qaytarishidan tashqari, handler haqiqatan ishga tushganini calls orqali tasdiqlaymiz β sendMessage matni handler bergan "Webhook!" bilan mos.
8-mashq yechimi¶
import { Hono } from "hono";
const { bot, calls } = makeBot();
bot.command("start", (ctx) => ctx.reply("Hono!"));
const app = new Hono();
app.post("/webhook", webhookCallback(bot, "hono"));
const res = await app.fetch(new Request("http://localhost/webhook", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(mkUpdate("/start")),
}));
assert.equal(res.status, 200);
assert.equal(calls.find((c) => c.method === "sendMessage").payload.text, "Hono!");
Hono Web-standart Request/Response bilan ishlagani uchun haqiqiy port ochmasdan ham app.fetch(...) orqali to'liq sinash mumkin β bu serverless muhitidagi ishlashini ham aniq aks ettiradi.
9-mashq yechimi¶
const { bot } = makeBot();
bot.command("start", (ctx) => ctx.reply("Sir!"));
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express", { secretToken: "MAXFIY" }));
const server = http.createServer(app);
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
function post(secret) {
const body = JSON.stringify(mkUpdate("/start"));
const headers = { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) };
if (secret !== undefined) headers["X-Telegram-Bot-Api-Secret-Token"] = secret;
return new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path: "/webhook", method: "POST", headers },
(res) => { res.resume(); res.on("end", () => resolve(res.statusCode)); });
req.on("error", reject); req.write(body); req.end();
});
}
const wrong = await post("YOLGON");
const right = await post("MAXFIY");
await new Promise((r) => server.close(r));
assert.notEqual(wrong, 200); // rad etildi (401)
assert.equal(right, 200); // qabul qilindi
secretToken opsiyasi webhookCallback ga kelgan har so'rovning X-Telegram-Bot-Api-Secret-Token header'ini solishtiradi. Mos kelmasa 401 qaytaradi β soxta update'lardan himoya. (Men buni offline ishga tushirdim: noto'g'ri β 401, to'g'ri β 200.)
10-mashq yechimi¶
function ishgaTushir(bot, env) {
if (env === "production") {
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express", { secretToken: "S" }));
return { mode: "webhook", app };
} else {
// bot.start() β illustrativ (jonli polling token talab qiladi); testda chaqirmaymiz
return { mode: "polling" };
}
}
const { bot } = makeBot();
assert.equal(ishgaTushir(bot, "production").mode, "webhook");
assert.equal(ishgaTushir(bot, "development").mode, "polling");
assert.equal(typeof webhookCallback(bot, "express"), "function"); // webhook shoxi tarmoqqa chiqmaydi
Handlerlar ikki rejimda ham bir xil qoladi; faqat ishga tushirish o'zgaradi. Test'da bot.start() ni chaqirmaymiz (u tarmoq talab qiladi) β uning o'rniga shox tanlovini va webhookCallback funksiya qaytishini tekshiramiz.
11-mashq yechimi¶
const { bot, calls } = makeBot();
bot.command("start", (ctx) => ctx.reply("Boshladik!"));
bot.on("message:text", (ctx) => ctx.reply(`Aks-sado: ${ctx.message.text}`));
const app = express();
app.use(express.json());
app.use("/webhook", webhookCallback(bot, "express"));
const server = http.createServer(app);
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
function post(update) {
const body = JSON.stringify(update);
return new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path: "/webhook", method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
(res) => { res.resume(); res.on("end", resolve); });
req.on("error", reject); req.write(body); req.end();
});
}
await post(mkUpdate("/start", 1));
await post(mkUpdate("salom", 2));
await new Promise((r) => server.close(r));
const texts = calls.filter((c) => c.method === "sendMessage").map((c) => c.payload.text);
assert.deepEqual(texts, ["Boshladik!", "Aks-sado: salom"]);
Har bir POST mustaqil update β webhook ularni ketma-ket qabul qiladi. update_id ni har xil bering (1, 2), aks holda grammY takroriy update deb o'tkazib yuborishi mumkin.
12-mashq yechimi¶
const bot = new Bot("12345:FAKE");
bot.botInfo = { id: 12345, is_bot: true, first_name: "T", username: "t_bot",
can_join_groups: true, can_read_all_group_messages: false, supports_inline_queries: false,
can_connect_to_business: false, has_main_web_app: false };
bot.api.config.use((prev, method, payload) => {
if (method === "getWebhookInfo")
return Promise.resolve({ ok: true, result: {
url: "https://x/y", pending_update_count: 0, has_custom_certificate: false } });
return Promise.resolve({ ok: true, result: true });
});
const info = await bot.api.getWebhookInfo();
assert.equal(info.url, "https://x/y");
assert.equal(info.pending_update_count, 0);
getWebhookInfo jonli holatda Telegram'dan WebhookInfo oladi (URL, navbatdagi update soni, oxirgi xato). Offline'da transformer orqali soxta javob qaytarib mantiqni tekshiramiz. Real botda last_error_message nosozliklarni topishda eng foydali maydon.
13-mashq yechimi¶
const { bot } = makeBot();
bot.command("start", (ctx) => ctx.reply("Sirli yo'l!"));
const app = express();
app.use(express.json());
app.use("/sirli-1q2w3e", webhookCallback(bot, "express", { secretToken: "MAXFIY" }));
const server = http.createServer(app);
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
function post(path, secret) {
const body = JSON.stringify(mkUpdate("/start"));
const headers = { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) };
if (secret !== undefined) headers["X-Telegram-Bot-Api-Secret-Token"] = secret;
return new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, path, method: "POST", headers },
(res) => { res.resume(); res.on("end", () => resolve(res.statusCode)); });
req.on("error", reject); req.write(body); req.end();
});
}
const okOk = await post("/sirli-1q2w3e", "MAXFIY"); // to'g'ri yo'l + to'g'ri sir
const okBad = await post("/sirli-1q2w3e", "YOLGON"); // to'g'ri yo'l + noto'g'ri sir
const badPath = await post("/notogri", "MAXFIY"); // noto'g'ri yo'l
await new Promise((r) => server.close(r));
assert.equal(okOk, 200); // qabul
assert.notEqual(okBad, 200); // sir rad etdi (401)
assert.equal(badPath, 404); // Express bu yo'lni topmadi
Bu ikki qatlamli himoya: maxfiy path begona POST'larni Express darajasida 404 bilan to'sadi, secret_token esa to'g'ri yo'lga kelgan soxta so'rovlarni 401 bilan rad etadi. Ikkalasi birga eng ishonchli β secret_token asosiy himoya, maxfiy path qo'shimcha.
β¬ οΈ Oldingi: 12 β Maxsus xususiyatlar va plaginlar Β· π README Β· Keyingi: 14 β To'lovlar va Telegram Stars β‘οΈ