IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime25/69-dars18 daqiqa
Mundarija (29)

Bot prodakshnda: webhook, 429, ommaviy yuborish va graceful shutdown

Qisqacha: Prodakshnda bot ko'pincha webhook bilan ishlaydi: Telegram update'ni serveringizga POST qiladi, siz X-Telegram-Bot-Api-Secret-Token sarlavhasini tekshirasiz. Telegram limitlari bor: bitta chatga ~1 xabar/s, hammaga ~30 xabar/s; oshsa — 429 va retry_after. Kanonda 100 kishiga Promise.all bilan yuborish 111 ta qo'shimcha (takroriy) so'rov berdi, 25 xabar/s navbat bilan — 0. Botni bloklagan foydalanuvchi — 403: unga qayta yubormang. To'xtashda ishlov tugashini kuting: kanonda runner.stop() 8 ms da qaytdi, ishlov esa hali tugamagan edi.

Bu darsda

  • Long polling va webhook'ni prodakshn nuqtai nazaridan tanlaysiz.
  • Webhook'ni secret_token bilan himoyalaysiz va 409 nima ekanini bilasiz.
  • Webhook javobi kechiksa nima bo'lishini o'zimiz sinagan tajribada ko'rasiz.
  • Telegram limitlari, 429 va retry_after bilan ishlaysiz; autoRetry plaginini tushunasiz.
  • Ommaviy yuborishni navbat bilan qilasiz va 403 ni to'g'ri ishlaysiz.
  • grammY runner, sequentialize va graceful shutdown'ni kanon topilmasi bilan o'rganasiz.

Oldin bilishingiz kerak: Guruh, kanal botlari va inline rejim, Reverse proxy orqasida Node, Server timeout'lari va keep-alive (graceful shutdown), Telegram Bot API asoslari (webhook).

Shu paytgacha bot bitta kompyuterda, bitta foydalanuvchi bilan ishladi. Endi uni minglab foydalanuvchiga tayyorlaymiz.

1. Nega bu kerak?

Bot ishlab turibdi. Bir kuni uchta narsa sodir bo'ladi:

  1. Siz 5 000 foydalanuvchiga "yangi versiya chiqdi" deb yozasiz. Yuzlab xabar 429 Too Many Requests bilan qaytadi.
  2. Ularning bir qismi botni allaqachon bloklagan — har kuni ularga yozishga urinib, loglarni xatoga to'ldirasiz.
  3. Yangi versiyani deploy qilasiz. Shu soniyada ishlanayotgan update'lar yo'qoladi — foydalanuvchining "Bajarildi" bosishi saqlanmay qoladi.

Bu dars shu uch muammo haqida. O'xshatish: pochta bo'limi. Bitta pochtachi bir soatda cheklangan xat tashiydi (limit). Ko'chib ketganlarga xat tashishni to'xtatish kerak (403). Kun oxirida bo'limni yopishdan oldin qo'lingizdagi xatlarni tarqatib bo'lish kerak (graceful shutdown).

2. Update olish: polling yoki webhook

2.1 Taqqoslash

Telegram Bot API asoslari darsida ikkalasini ko'rgansiz. Kanon o'lchovi (IlmHamroh, 2026-10, lokal mock, 5 xabar):

text
short (0 s, har 500 ms)  kechikish mediana 214 ms, so'rovlar 10 (5 xabar uchun)
long (timeout 10 s)      kechikish mediana 14 ms, so'rovlar 5 (5 xabar uchun)

Prodakshn uchun tanlov:

Long polling (bot.start()) Webhook
Ulanish bot Telegram'ga boradi Telegram botga keladi
Ochiq HTTPS manzil kerak emas kerak (port 443, 80, 88, 8443)
Bir nechta nusxa yo'q — bittasi getUpdates qiladi yuk taqsimlagich (load balancer) orqali
Serverless mos emas mos
Oddiylik juda oddiy proxy, TLS, sarlavha

Portlar — Telegram hujjatidagi ro'yxat (2026-10-28). Kichik bot uchun long polling yetarli va soddaroq. Webhook — ko'p trafik, bir nechta nusxa yoki serverless'da.

Lokal ms qiymatlari har ishga tushishda o'zgaradi, farqning tartibi esa bir xil: long polling xabarni deyarli darhol oladi va kamroq so'rov yuboradi.

2.2 Webhook va secret_token

Webhook manzili ochiq internetda. Uni bilgan har kim unga soxta update yuborishi mumkin: "foydalanuvchi 1 /ban yozdi". Himoya — setWebhook dagi secret_token. Telegram uni har webhook so'rovida X-Telegram-Bot-Api-Secret-Token sarlavhasida yuboradi. Hujjatga ko'ra: 1–256 belgi, faqat A-Z, a-z, 0-9, _, -.

js
import express from "express";
import { webhookCallback } from "grammy";

const app = express();
app.use(express.json());
app.post("/tg/webhook", webhookCallback(bot, "express", {
  secretToken: process.env.TG_WEBHOOK_SECRET,
}));
app.listen(3000);

// bir marta, deploy'da:
await bot.api.setWebhook("https://example.com/tg/webhook", {
  secret_token: process.env.TG_WEBHOOK_SECRET,
});

Kanon o'lchovi (IlmHamroh, 2026-10, mock):

text
setWebhook (secret'da bo'shliq): {"ok":false,"error_code":400,"description":"Bad Request: secret token contains unallowed characters"}
setWebhook: {"ok":true,"result":true}
webhook bor paytda getUpdates: {"ok":false,"error_code":409,"description":"Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first"}
bot javobi: "aks: webhook orqali" | webhook yetkazish: [{"status":200,"update_id":1013}]
noto'g'ri secret bilan soxta update: 401 secret token is wrong
secret sarlavhasiz: 401

Har qatordan bitta saboq:

  1. Bo'shliqli token rad etildi — ruxsat etilgan belgilar.
  2. 409 Conflict — webhook o'rnatilgan paytda getUpdates ishlamaydi. Prodakshndagi webhook va lokal bot.start() bitta token bilan birga ishlay olmaydi. Lokal sinov uchun alohida test bot oching.
  3. Haqiqiy update yetkazildi, bot javob berdi.
  4. Soxta update noto'g'ri sarlavha bilan — grammY 401 qaytardi, ishlovchi ishlamadi.
  5. Sarlavhasiz — ham 401.

Secret — sir: muhit o'zgaruvchisida, kodda emas. Uzunroq tasodifiy qiymat (masalan 32 bayt base64url) ishlating.

sequenceDiagram
  participant T as Telegram
  participant P as nginx (HTTPS)
  participant B as Bot (Express)
  T->>P: POST /tg/webhook + secret sarlavha
  P->>B: proxy
  B->>B: secret mosmi?
  alt mos emas
    B-->>T: 401
  else mos
    B->>B: ishlovchilar (10 s ichida)
    B-->>T: 200
  end
  Note over T: 200 bo'lmasa — keyinroq qayta yuboradi

Diagrammaga qarang: Telegram 200 javobini kutadi. Javob kelmasa yoki xato bo'lsa — update'ni qayta yuboradi. Keyingi bo'lim shu haqda.

2.3 Sekin ishlovchi: o'zimiz sinadik

grammY hujjati ogohlantiradi: webhook so'rovini tez yakunlamasangiz, Telegram update'ni yetkazilmagan deb qayta yuboradi va bot uni bir necha marta ishlaydi. Shuning uchun webhookCallback ning o'z timeout'i bor (hujjat: sukut 10 soniya); u o'tsa, sukutda xato tashlanadi.

Biz buni sinadik: ishlovchi 500 ms ishlaydi, timeoutMilliseconds: 200. Ikki rejim — onTimeout: "throw" (sukut) va "return":

js
bot.on("message:text", async () => {
  await kut(500);
  tugadi = true;
});
app.post("/tg", webhookCallback(bot, "express", {
  secretToken: "maxfiy_Token-123",
  timeoutMilliseconds: 200,
  onTimeout, // "throw" yoki "return"
}));

Natija (biz ishga tushirdik, grammY 1.46.0, Express 5.3.0, Node 24.21.0; ms har safar boshqacha):

text
onTimeout=throw: status 500 (Request timed out after 200 ms) 310 ms; ishlovchi tugaganmi: false
  500 ms dan keyin ishlovchi tugaganmi: true
onTimeout=return: status 200 (bo'sh) 237 ms; ishlovchi tugaganmi: false
  500 ms dan keyin ishlovchi tugaganmi: true

Ikki muhim fakt:

  • Timeout ishlovchini to'xtatmaydi: ikkala holatda ham u fonda tugadi.
  • throw javobi 500 bo'ldi. Telegram bunday update'ni qayta yuboradi. Ishlovchi ikkinchi marta ishlaydi (buni biz sinamadik — hujjat bo'yicha). return → 200 → qayta yuborish yo'q, lekin ish fonda davom etadi: shu payt jarayon o'lsa, ish yo'qoladi.

Xulosa: webhook ishlovchisi tez bo'lsin. Uzoq ish (fayl, tashqi API, ommaviy yuborish) — navbatga (BullMQ asoslari), javob darhol. Ishlovchilar esa takroriy update'ga chidamli bo'lsin — idempotent (Idempotency-Key): update_id ni qisqa muddat eslab qoling.

3. Telegram limitlari va 429

3.1 Hujjatdagi raqamlar

Telegram Bots FAQ (2026-10-28 da o'qildi):

  • bitta chatga sekundiga bittadan ko'p xabar yubormang; qisqa "portlash"lar mumkin, lekin oxiri 429 bo'ladi;
  • guruhda daqiqasiga 20 tadan ko'p emas;
  • ommaviy bildirishnoma — taxminan sekundiga 30 xabar, agar BotFather'da pullik broadcast (paid broadcasts) yoqilmagan bo'lsa. U yoqilsa — sekundiga 1000 tagacha; bepul 30 dan ortiq har xabar 0.1 Star turadi va botning Stars balansidan yechiladi (hujjatga ko'ra; sinalmagan).

Limit oshsa — 429 Too Many Requests: retry after N va javobda parameters.retry_after — necha soniya kutish kerak.

Diqqat: Kanon mock'idagi limitlar (chat: 1/s, 3 ta portlash; global 30/s) va retry_after qiymati — taxminiy, haqiqiy server bilan solishtirilmagan. Raqamlar mexanizmni ko'rsatadi, Telegram'ning aniq xatti-harakatini emas.

3.2 retry_after ga amal qilish

429 olganda to'g'ri harakat — aynan retry_after soniya kutish va qayta urinish. O'z backoff'ingizni (Asinxron naqshlar — retry va exponential backoff) faqat retry_after bo'lmaganda (5xx, tarmoq) ishlating:

js
function kutishMs(xato, urinish) {
  const ra = xato.parameters?.retry_after;
  if (xato.error_code === 429 && Number.isInteger(ra)) {
    return ra * 1000;
  }
  if (xato.error_code >= 500 || xato.error_code === undefined) {
    return Math.min(30_000, 500 * 2 ** urinish);
  }
  return null; // qayta urinmaymiz
}

const xatolar = [
  { error_code: 429, parameters: { retry_after: 3 } },
  { error_code: 502 },
  { error_code: 403, description: "bot was blocked by the user" },
  { error_code: 400, description: "chat not found" },
];
for (const x of xatolar) console.log(x.error_code, kutishMs(x, 2));
console.log(kutishMs({ error_code: 502 }, 10));

Konsolda:

text
429 3000
502 2000
403 null
400 null
30000
  • 429 — server aytgan vaqt, aniq.
  • 502 — o'zimizning eksponensial kutish, 30 s bilan cheklangan.
  • 403 va 400 — qayta urinish befoyda: foydalanuvchi botni bloklagan yoki chat yo'q.

3.3 autoRetry plagini

grammY'da bu mantiq tayyor: @grammyjs/auto-retry — API transformer (grammY bilan bot yozish, «Plagin tizimi»). Kanon modulida:

ts
// 429 (retry_after) va 5xx — avtomatik kutib takrorlash; 60 s dan
// uzoq kutish talab qilinsa — xato qaytariladi
bot.api.config.use(
  autoRetry({ maxRetryAttempts: 3, maxDelaySeconds: 60 }),
);

maxDelaySeconds: 60 — server 10 daqiqa kutishni aytsa, plagin kutmaydi va xatoni qaytaradi: so'rov bir necha daqiqa osilib qolmasin.

Kanon testi: mock chegarasi yoqiq (1/s, 3 portlash), bitta chatga 5 xabar ketma-ket:

ts
test("429: auto-retry retry_after ni kutib takrorlaydi " +
  "(chat: 3 portlash, 1/s)", async () => {
  tg.chatlar.set(10, []);
  const t0 = Date.now();
  for (let i = 0; i < 5; i++) {
    await bot.api.sendMessage(10, `x${String(i)}`);
  }
  const vaqt = Date.now() - t0;
  assert.equal(tg.chatlar.get(10)?.length, 5);
  assert.ok(vaqt >= 1000, `kutish bo'lishi kerak edi`);
  assert.ok(tg.chaqiruvlar.filter((c) =>
    c.method === "sendMessage" && c.params["chat_id"] === 10,
  ).length > 5);
});

Beshala xabar yetib bordi, lekin sendMessage 5 tadan ko'p chaqirildi — 429 dan keyingi takrorlar. Biz ishga tushirganimizda test ~3 s davom etdi (✔ 429: auto-retry ... (3022.1695ms)).

4. Ommaviy yuborish (broadcast)

4.1 Kanon o'lchovi

100 foydalanuvchi, 3 tasi botni bloklagan, mock global limiti 30/s, autoRetry yoqiq. Ikki usul (kanon o'lchovi, IlmHamroh, 2026-10):

js
// 1) hammasi birdan
await Promise.all(ids.map((id) => bitta(id)));

// 2) navbat: har xabardan keyin 40 ms (25 xabar/s)
for (const id of ids) {
  await bitta(id);
  await kut(40);
}
text
parallel (Promise.all)   3240 ms, natija {"ok":97,"bloklagan":3,"boshqa":0}, qo'shimcha (429 dan keyin takror) so'rovlar: 111
navbat: 25 xabar/s       4881 ms, natija {"ok":97,"bloklagan":3,"boshqa":0}, qo'shimcha (429 dan keyin takror) so'rovlar: 0
100 xabar: 429 dan keyingi qo'shimcha so'rovlar
  • Promise.all3 240 ms111 so'rov
  • navbat 25/s4 881 ms0 so'rov

Manba: IlmHamroh kanon o'lchovi, lokal soxta Bot API (limit taxminiy), 2026-10-27

Grafikka qarang: parallel usul ~1.6 s tezroq tugadi, lekin serverga 111 ta ortiqcha so'rov yubordi — har biri 429. Haqiqiy Telegram'da doimiy limit buzish uzoqroq bloklashga olib kelishi mumkin. Navbat esa limitga o'zi moslashdi: bitta ham 429 yo'q.

Ikkala usulda natija bir xil: 97 yetkazildi, 3 bloklagan (403). Bu autoRetry ning xizmati — u 429 ni yashirdi, lekin muammoni hal qilmadi.

4.2 Navbat bilan

Prodakshnda 5 000 xabarni HTTP so'rov ichida for bilan yubormang — so'rov daqiqalab osiladi, deploy uni uzadi. To'g'ri yo'l — navbat (queue):

  1. "Broadcast" buyrug'i navbatga 5 000 ta vazifa qo'yadi va darhol javob beradi.
  2. Worker (ishchi jarayon) ularni limit bilan (masalan 25/s) oladi va yuboradi.
  3. 429 — vazifa retry_after dan keyin qayta; 403 — foydalanuvchi "faol emas" deb belgilanadi, qayta urinish yo'q.

BullMQ'da tezlik cheklovi (limiter) bor — BullMQ asoslari va BullMQ ilg'or imkoniyatlari darslarida. Bugun faqat g'oya.

4.3 403: bloklagan foydalanuvchi

403 Forbidden: bot was blocked by the user — foydalanuvchi botni to'xtatgan. Qayta urinish befoyda. Ikki manba bilan biling:

Ikkalasida ham bazada faol = false. Foydalanuvchi botni qayta ishga tushirsa (kicked → member yoki /start) — true.

5. Runner: parallel ishlov

5.1 Nega

bot.start() update'larni ketma-ket ishlaydi: bitta ishlovchi 2 s bazani kutsa, boshqa hamma foydalanuvchi ham kutadi. @grammyjs/runner update'larni parallel ishlaydi.

Parallellik yangi muammo tug'diradi: bitta foydalanuvchining ikki xabari bir vaqtda ishlansa, ular sessiyani bir-birining ustiga yozadi (Sessiya va ko'p qadamli dialoglar). Yechim — sequentialize: bir kalit (chat) ichida ketma-ket, turli kalitlar — parallel.

js
import { run, sequentialize } from "@grammyjs/runner";

bot.use(sequentialize((ctx) => String(ctx.chat?.id)));
bot.on("message:text", async (ctx) => {
  const sekin = ctx.message.text.startsWith("sekin");
  await kut(sekin ? 200 : 10);
  tartib.push(`${ctx.chat.id}:${ctx.message.text}`);
});
const runner = run(bot);

Chat 7 ga "sekin" (200 ms) va "tez" (10 ms), chat 8 ga "boshqa chat" (kanon o'lchovi, runner 2.0.3):

text
runner + sequentialize (chat bo'yicha): 8:boshqa chat, 7:sekin, 7:tez — 7-chat tartibi saqlandi, 8-chat kutmadi
  • Chat 7 ichida "tez" "sekin" dan keyin — tartib saqlandi.
  • Chat 8 birinchi tugadi — u chat 7 ning sekin ishini kutmadi.

5.2 Kanon topilmasi: runner.stop()

Graceful shutdown'da (deploy, SIGTERM) bot yangi update olishni to'xtatishi va boshlangan ishlarni tugatishi kerak. Kanon tekshirdi: chat 7 ga "sekin2" (200 ms ish) yuborildi, 60 ms dan keyin — runner.stop():

text
stop oldidan sink hajmi 1; runner.stop(): 8 ms; stop qaytganda "sekin2" ishlangan: false; 300 ms dan keyin: true; mock navbatida: 0

O'qing:

  • runner.size() — 1: bitta update ishlanmoqda.
  • stop() 8 ms da qaytdi. @grammyjs/runner 2.0.3 ning tip izohida (d.ts) "ishlayotgan middleware tugashini kutadi" deyilgan — lekin qaytgan paytda "sekin2" hali ishlanmagan edi.
  • 300 ms dan keyin ish tugadi — demak jarayon tirik qolsa, ish tugaydi.
  • mock navbatida: 0 — update Telegram tomonida allaqachon tasdiqlangan (offset oshgan). Jarayon stop() dan keyin darhol o'ldirilsa — bu update yo'qoladi: Telegram uni qayta bermaydi.

Kanon xulosasi: graceful shutdown'da stop() dan keyin runner.size() === 0 bo'lguncha ham kuting. Bu topilma bitta tajribaga asoslangan (bir versiya, mock) — yangilangan versiyada xatti-harakat o'zgargan bo'lishi mumkin, o'zingiz tekshiring.

5.3 Graceful shutdown

ts
async function toxta(runner: RunnerHandle, chegaraMs = 10_000) {
  await runner.stop();
  const t0 = Date.now();
  while (runner.size() > 0 && Date.now() - t0 < chegaraMs) {
    await new Promise((r) => setTimeout(r, 50));
  }
}
process.once("SIGTERM", () => void toxta(runner));
process.once("SIGINT", () => void toxta(runner));

Bu kanon kodi emas — topilmadan kelib chiqqan naqsh, biz uni sinamadik. bot.start() bilan esa — process.once("SIGTERM", () => bot.stop()). Signallar Windows'da boshqacha ishlaydi (process obyekti) — Linux serverda (Docker) SIGTERM keladi; kanon buni sinamagan.

Webhook rejimida graceful shutdown — HTTP server uchun (Server timeout'lari va keep-alive): yangi ulanishlarni qabul qilmaslik, ochiqlarini tugatish.

Tekshirib ko'ring: Webhook rejimida deploy paytida bitta so'rov uzildi. Update yo'qoladimi?

Javob

Yo'q (hujjat bo'yicha): Telegram 200 olmagani uchun uni keyinroq qayta yuboradi. Long polling + runner'da esa update getUpdates bilan olinib, offset oshgandan keyin tasdiqlangan — jarayon o'lsa, u qaytmaydi. Shu sababli runner bilan graceful shutdown alohida e'tibor talab qiladi. Webhook'da esa aksincha muammo — takroriy ishlov, shuning uchun idempotentlik.

6. Kuzatish

  • Log. bot.catch dagi har xato — strukturali log (update_id, xato turi). 429 lar soni — limitga yaqinlashish belgisi.
  • getWebhookInfo. Telegram'da webhook holati: kutayotgan update'lar soni (pending_update_count), oxirgi xato sanasi va matni. Yetkazish muammosini Telegram tomonidan ko'rish uchun (hujjat bo'yicha, kanonda sinalmagan).
  • Token. BotFather'da almashtirish mumkin. Oqsa — darhol almashtiring va webhook'ni qayta o'rnating.

7. Hujumchi nigohi

Hujum Himoya
webhook manziliga soxta update secret_token → 401 (kanon)
bot tokeni oqdi muhit o'zgaruvchisi, BotFather'da almashtirish
botni spam bilan to'ldirish (1000 xabar) foydalanuvchi bo'yicha rate limit (Rate limiting)
sekin so'rov webhook'ni band qiladi tez javob, uzoq ish navbatda
takroriy update ikki marta to'lov/yozuv update_id bo'yicha idempotentlik

8. Ko'p uchraydigan xatolar

Xato Natija To'g'risi
webhook'da secret_token yo'q soxta update secretToken
prod webhook + lokal bot.start() bir token 409 Conflict alohida test bot
broadcast Promise.all bilan yuzlab 429 (kanon: 111) navbat, ≤ 25–30/s
403 ga qayta urinish log xatoga to'ladi faol = false
webhook ishlovchisida uzoq ish timeout → qayta yuborish navbat
runner.stop() ga ishonish ish yo'qolishi mumkin size() === 0 ni kutish
retry_after ni e'tiborsiz qoldirish 429 ko'payadi aynan shuncha kutish

9. Mashqlar

Mashqlar kurs/mashqlar/29/25-prodakshn/ da.

1-mashq (oson): javob kodi bo'yicha qaror

Har Telegram javobi uchun nima qilasiz: qayta urinish (qachon), foydalanuvchini o'chirish yoki xatoni logga yozish?

  1. 429, retry_after: 5
  2. 403 bot was blocked by the user
  3. 400 message is too long
  4. 502 Bad Gateway
  5. 409 Conflict (getUpdates da)
Yechim
  1. 5 soniyadan keyin qayta.
  2. Qayta yo'q; foydalanuvchi faol = false.
  3. Qayta yo'q — xabar o'zgarmasa, natija o'zgarmaydi; logga va kodni tuzatish (4096 belgi chegarasi).
  4. Eksponensial kutish bilan qayta.
  5. Webhook o'rnatilgan — deleteWebhook yoki boshqa token. Qayta urinish befoyda.

2-mashq (o'rta): yuborish jadvali

jadval(n, tezlik) funksiyasini yozing: n ta xabar uchun har birining yuborilish vaqtini (ms, 0 dan) qaytaradi, sekundiga tezlik tadan oshmasin (teng oraliq). 100 xabar 25/s uchun oxirgi vaqt va jami davomiylikni, 5 000 xabar 30/s uchun daqiqalarni chiqaring.

Yechim
js
function jadval(n, tezlik) {
  const oraliq = 1000 / tezlik;
  return Array.from({ length: n }, (_x, i) => Math.round(i * oraliq));
}

const j = jadval(100, 25);
console.log(j.slice(0, 4), j.at(-1));
const katta = jadval(5000, 30);
console.log((katta.at(-1) / 60_000).toFixed(1), "daqiqa");
text
[ 0, 40, 80, 120 ] 3960
2.8 daqiqa

100 xabar 25/s — oxirgisi 3.96 s da. Kanondagi navbat (har xabardan keyin 40 ms) 4 881 ms oldi — yuborishning o'z vaqti ham qo'shiladi. 5 000 kishiga ~3 daqiqa: bu HTTP so'rov ichida emas, navbatda bajariladi.

3-mashq (qiyin): retry_after ni hurmat qiluvchi yuboruvchi

Soxta yubor(id) — har 4-chaqiruvda { error_code: 429, parameters: { retry_after: 1 } } bilan rad etadi, id === 13 bo'lsa { error_code: 403 }. ishonchliYubor(idlar) yozing: har id uchun 429 bo'lsa retry_after (ms o'rniga sinov uchun shuncha "tik") kutib qayta urinadi (eng ko'pi 3 marta), 403 da to'xtatadi. Natija: { ok, bloklagan, urinishlar, kutildi }. [11, 12, 13, 14, 15] bilan sinang. Ishora: kutishni haqiqiy taymer bilan emas, hisoblagich bilan taqlid qiling.

Yechim
js
let chaqiruv = 0;
async function yubor(id) {
  chaqiruv++;
  if (chaqiruv % 4 === 0) {
    throw { error_code: 429, parameters: { retry_after: 1 } };
  }
  if (id === 13) throw { error_code: 403 };
  return true;
}

async function ishonchliYubor(idlar) {
  const n = { ok: 0, bloklagan: 0, urinishlar: 0, kutildi: 0 };
  for (const id of idlar) {
    for (let u = 0; u < 3; u++) {
      n.urinishlar++;
      try {
        await yubor(id);
        n.ok++;
        break;
      } catch (e) {
        if (e.error_code === 403) { n.bloklagan++; break; }
        if (e.error_code !== 429) throw e;
        n.kutildi += e.parameters.retry_after;
      }
    }
  }
  return n;
}

console.log(await ishonchliYubor([11, 12, 13, 14, 15]));
text
{ ok: 4, bloklagan: 1, urinishlar: 6, kutildi: 1 }

6 urinish: 5 id + bitta 429 dan keyingi takror. 403 li id qayta urinilmadi. Haqiqiy kodda kutildi o'rniga await kut(retry_after * 1000) — kanondagi autoRetry aynan shuni qiladi.

10. Loyiha qadami: autoRetry va bildirishnoma yuboruvchisi

Kanon bot.ts da prodakshn uchun ikki narsa: autoRetry (yuqorida) va telegramYuboruvchi — Bildirishnoma tizimi arxitekturasi darsidagi markaz uchun Telegram kanali. U xatoni markaz tushunadigan holatga aylantiradi:

ts
/** Bildirishnoma markazi uchun Telegram kanali: HTML xabar, 403 →
 * eskirgan (bot bloklangan/chat yo'q), 429/5xx/tarmoq → qayta. */
export function telegramYuboruvchi(api: Api): Yuboruvchi {
  return async (q, x) => {
    if (!q.chatId) return { holat: "xato", sabab: "chat ulanmagan" };
    try {
      await api.sendMessage(
        Number(q.chatId),
        `<b>${html(x.sarlavha)}</b>\n${html(x.matn)}`,
        {
          parse_mode: "HTML",
          link_preview_options: { is_disabled: true },
        },
      );
      return { holat: "yuborildi" };
    } catch (e) {
      if (e instanceof GrammyError) {
        if (e.error_code === 403 || (e.error_code === 400 &&
          e.description.includes("chat not found"))) {
          return { holat: "eskirgan" };
        }
        if (e.error_code === 429 || e.error_code >= 500) {
          return { holat: "qayta", sabab: e.description };
        }
        return { holat: "xato", sabab: e.description };
      }
      if (e instanceof HttpError) {
        return { holat: "qayta", sabab: String(e.error) };
      }
      throw e;
    }
  };
}
Holat Qachon Markaz nima qiladi
yuborildi ok yozuvni yopadi
eskirgan 403, chat topilmadi ulanishni o'chiradi, boshqa kanalga
qayta 429 (auto-retry ham yengolmadi), 5xx, tarmoq keyinroq qayta
xato boshqa 400 logga, qayta yo'q

link_preview_options: { is_disabled: true } — eslatmadagi havolalar uchun katta oldindan ko'rinish chiqmasin. Sarlavha va matn — html() dan.

Kanon testi (formatlangan, qisqartirilgan):

ts
const y = telegramYuboruvchi(bot.api);
assert.deepEqual(await y({ ...q, chatId: "501" }, x),
  { holat: "yuborildi" });
assert.equal(tg.oxirgi(501)?.text,
  "<b>Muddat yaqin</b>\nNon &lt;olish&gt;");
tg.bloklagan.add(503);
assert.deepEqual(await y({ ...q, chatId: "503" }, x),
  { holat: "eskirgan" });
assert.deepEqual(await y({ ...q, chatId: "-100999" }, x),
  { holat: "eskirgan" });

Biz ishga tushirgan test faylidan (Node 24.21.0, 9/9) shu darsga tegishli qatorlar:

text
  ✔ telegramYuboruvchi: yuborildi / 403 bloklangan → eskirgan / chat yo'q → eskirgan (11.0443ms)
  ✔ 429: auto-retry retry_after ni kutib takrorlaydi (chat: 3 portlash, 1/s) (3022.1695ms)

Commit xabari:

bash
git commit -m "feat(telegram): autoRetry, bildirishnoma yuboruvchisi"

11. Real ishda

  • Katta botlar webhook + bir nechta nusxa + navbat bilan ishlaydi; ommaviy xabar — fon vazifasi.
  • 403 statistikasi — bot "o'lik" auditoriyasini ko'rsatadi; marketing uni hisobga oladi.
  • Deploy paytida update yo'qolishi — prodakshndagi eng ko'p uchraydigan "sirli" xatolardan.
  • Intervyuda: "Webhook'ni qanday himoyalaysiz?", "5 000 foydalanuvchiga xabarni qanday yuborasiz?", "Bot to'xtaganda nima yo'qolishi mumkin?"

Xulosa

  • Webhook — secret_token bilan; noto'g'ri sarlavha 401 (kanon). Webhook bor paytda getUpdates — 409.
  • Webhook ishlovchisi tez; timeout ishni to'xtatmaydi, throw esa qayta yuborishga olib keladi (biz sinadik).
  • Limitlar: chatga ~1/s, guruhga 20/daqiqa, hammaga ~30/s; 429 da retry_after ga amal qiling.
  • Broadcast — navbat bilan: kanonda Promise.all 111 ortiqcha so'rov, 25/s navbat — 0.
  • 403 — qayta urinmang, foydalanuvchini faolsiz qiling.
  • runner + sequentialize; to'xtashda size() === 0 ni ham kuting (kanon topilmasi).

Keyingi dars: Telegram Mini App backendi va Stars — Telegram ichidagi web ilova uchun initData imzosini tekshiramiz va Stars bilan to'lov oqimini quramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!