ZaminDocs

Yangilanishlarni olish

Bot uchun har bir voqea β€” Update. Ularni ikki usuldan biri bilan olasiz: getUpdates (bot o'zi so'raydi) yoki webhook (Zamin sizning serveringizga yuboradi).

Yangilanish turlari

TurQachon keladi
messageFoydalanuvchi matn, fayl, kontakt yoki joylashuv yubordi. Start bosilganda /start (yoki /start PARAM) matni keladi. Guruhda β€” privacy mode ruxsat bergan xabarlar va xizmat xabarlari; kanal izohlari chatida β€” izohlar.
edited_messageFoydalanuvchi o'z matnli xabarini tahrirladi (48 soat ichida).
callback_queryFoydalanuvchi bot xabaridagi inline tugmani bosdi (shaxsiy chat yoki guruh).
my_chat_memberFoydalanuvchi botni boshladi, blokladi/to'xtatdi yoki blokdan chiqarib qayta boshladi; guruh yoki kanalda β€” botni qo'shishdi, admin qilishdi, chiqarishdi.
chat_memberGuruhda odam qo'shildi, chiqdi, chiqarildi yoki ban qilindi (faqat shu voqealarda). Faqat admin-botlarga va faqat allowed_updates'da aniq ko'rsatilsa.
channel_postBot admin bo'lgan kanalda yangi post.
edited_channel_postKanal posti tahrirlandi.

Guruh va kanallardagi yangilanishlar: Guruhlar va kanallar.

Foydalanuvchi o'z xabarini o'chirsa, bot xabardor qilinmaydi.

my_chat_member

Shaxsiy chatda:

Voqeaold_chat_member.statusnew_chat_member.status
Birinchi marta Start (yoki birinchi xabar)leftmember
Foydalanuvchi botni blokladi yoki "To'xtatish"ni bosdimemberkicked
Blokdan chiqarib, qayta Start bosdikickedmember

Guruh va kanalda: qo'shildi β€” left β†’ member (kanalda β†’ administrator), admin qilindi β€” member β†’ administrator (huquqlar bilan), chiqarildi β€” β†’ kicked, o'zi chiqdi (leaveChat) β€” β†’ left. Misollar.

JSON
{
  "update_id": 18,
  "my_chat_member": {
    "chat": {
      "id": 4810235517,
      "type": "private",
      "first_name": "Ali",
      "last_name": "Valiyev",
      "username": "ali"
    },
    "from": {
      "id": 4810235517,
      "is_bot": false,
      "first_name": "Ali",
      "last_name": "Valiyev",
      "username": "ali",
      "language_code": "uz"
    },
    "date": 1791106200,
    "old_chat_member": {
      "user": {
        "id": 1000001,
        "is_bot": true,
        "first_name": "Ob-havo",
        "username": "ObHavoBot",
        "can_join_groups": true,
        "can_read_all_group_messages": false,
        "supports_inline_queries": false
      },
      "status": "member"
    },
    "new_chat_member": {
      "user": {
        "id": 1000001,
        "is_bot": true,
        "first_name": "Ob-havo",
        "username": "ObHavoBot",
        "can_join_groups": true,
        "can_read_all_group_messages": false,
        "supports_inline_queries": false
      },
      "status": "kicked"
    }
  }
}

getUpdates β€” long polling

getUpdates navbatdagi yangilanishlarni qaytaradi. Navbat bo'sh va timeout > 0 bo'lsa, server javobni yangilanish kelguncha yoki timeout tugaguncha ushlab turadi. Bu usul eng oddiy: ochiq port, domen yoki sertifikat kerak emas.

ParametrMa'nosi
offsetOxirgi qayta ishlangan update_id + 1. Server update_id < offset bo'lgan barcha yangilanishlarni tasdiqlangan deb o'chiradi. Manfiy qiymat β€” oxirgi |offset| ta yangilanish.
limit1–100, standart 100.
timeoutKutish, soniya. 0 (standart) β€” darhol javob. Maksimum 50 (kattaroq qiymat 50 ga qisqartiriladi). Tavsiya: 25–50.
allowed_updatesKerakli turlar ro'yxati. Bot uchun saqlanadi: ro'yxatda yo'q turdagi yangilanishlar umuman navbatga qo'yilmaydi. [] β€” barcha turlar (chat_member'dan tashqari β€” uni faqat aniq ko'rsatsangiz olasiz). Berilmasa β€” avvalgi sozlama qoladi.

To'g'ri tsikl

  1. offset'siz (yoki saqlangan qiymat bilan) getUpdates chaqiring.
  2. Har bir yangilanishni qayta ishlang va offset = update_id + 1 qiling.
  3. Keyingi chaqiruvda shu offset'ni yuboring β€” oldingilar o'chadi.

Bot qayta ishga tushganda offset yo'qolsa, tasdiqlanmagan yangilanishlar qayta keladi. Muhim botlarda offset'ni faylga yoki bazaga saqlang.

polling.py
import os
import time
import requests

TOKEN = os.environ["ZAMIN_BOT_TOKEN"]
API = f"https://api.zamin.app/bot{TOKEN}"


def handle(update):
    message = update.get("message")
    if message and "text" in message:
        requests.post(
            f"{API}/sendMessage",
            json={"chat_id": message["chat"]["id"], "text": message["text"]},
            timeout=15,
        )


def main():
    # Webhook o'rnatilgan bo'lsa, getUpdates 409 qaytaradi: avval o'chiramiz
    requests.post(f"{API}/deleteWebhook", timeout=15)
    offset = None
    while True:
        params = {"timeout": 30, "allowed_updates": ["message", "callback_query", "my_chat_member"]}
        if offset is not None:
            params["offset"] = offset
        try:
            r = requests.post(f"{API}/getUpdates", json=params, timeout=40)
            body = r.json()
        except (requests.RequestException, ValueError) as e:
            print("Tarmoq xatosi:", e)
            time.sleep(3)
            continue
        if not body.get("ok"):
            if body.get("error_code") == 401:
                raise SystemExit("Token noto'g'ri yoki bekor qilingan")
            time.sleep((body.get("parameters") or {}).get("retry_after", 3))
            continue
        for update in body["result"]:
            offset = update["update_id"] + 1
            try:
                handle(update)
            except Exception as e:  # bitta yangilanish butun botni to'xtatmasin
                print("Xato:", update["update_id"], e)


if __name__ == "__main__":
    main()

Bir vaqtda faqat bittasi

  • Webhook o'rnatilgan bo'lsa, getUpdates 409 Conflict: can't use getUpdates method while webhook is active… qaytaradi. Avval deleteWebhook.
  • setWebhook ochiq long poll'ni darhol 409 bilan yopadi.
  • Bir vaqtda faqat bitta getUpdates kutishi mumkin: yangi chaqiruv eskisini 409 Conflict: terminated by other getUpdates request… bilan tugatadi. Bu xato ko'rsangiz β€” botingizning ikkinchi nusxasi ishlayapti.

Olinmagan (yoki webhookka yetkazilmagan) yangilanishlar 24 soat saqlanadi; bitta bot uchun ko'pi bilan 1000 ta β€” undan oshsa, eng eskilari o'chiriladi. Bot uzoq vaqt o'chiq tursa, eski yangilanishlar yo'qoladi.

Webhooklar

setWebhook bilan HTTPS manzil bersangiz, har bir yangilanish shu manzilga POST qilinadi. Tanasi β€” bitta Update JSON obyekti.

HTTP
POST /zamin/webhook HTTP/1.1
Host: bot.example.com
Content-Type: application/json
User-Agent: ZaminBotAPI/1.0
X-Zamin-Bot-Api-Secret-Token: my-long-random-secret

{"update_id":17,"message":{"message_id":13,"from":{…},"chat":{…},"date":1791106200,"text":"/start"}}

Talablar

  • Faqat https://. Sertifikat ishonchli markaz tomonidan berilgan bo'lishi kerak (masalan, Let's Encrypt); o'z-o'zidan imzolangan sertifikat ishlamaydi.
  • Portlar: 443, 80, 88, 8443 (port ko'rsatilmasa β€” 443).
  • URL'da login/parol bo'lmasin; URL ≀2048 belgi.

Maxfiy sarlavha

setWebhook'da secret_token (1–256 belgi, A-Z a-z 0-9 _ -) bersangiz, har bir so'rov X-Zamin-Bot-Api-Secret-Token sarlavhasi bilan keladi. Sarlavha mos kelmagan so'rovlarni rad eting β€” webhook manzilingizni topgan har kim soxta yangilanish yubora oladi. Solishtirishni doimiy vaqtli funksiya bilan qiling (Python'da hmac.compare_digest).

Yetkazish va qayta urinish

  • Har qanday 2xx status β€” yetkazildi. Javob tanasi o'qilmaydi (Telegram'dagi kabi javobda metod chaqirish ishlamaydi).
  • Kutish vaqti β€” 10 soniya. Yo'naltirishlarga (3xx) ergashilmaydi: ular xato hisoblanadi.
  • Yangilanishlar bot uchun ketma-ket va tartib bilan yetkaziladi: bittasi yetkazilmaguncha keyingisi yuborilmaydi. max_connections saqlanadi, lekin hozircha parallellik yo'q.
  • Xato bo'lsa (2xx emas, ulanish xatosi yoki timeout), o'sha yangilanish qayta yuboriladi: kutish 1, 2, 4, 8 … soniyagacha ikki baravar oshadi va ko'pi bilan 10 daqiqa bo'ladi. Urinishlar yangilanish 24 soatdan eskirguncha davom etadi.
  • Oxirgi xato getWebhookInfo'da: last_error_message, last_error_date.

SSRF himoyasi: qaysi manzillar rad etiladi

Webhook manzilini istalgan kishi tanlashi mumkin, shuning uchun server ichki tarmoqqa so'rov yubormasligi uchun manzilni o'zi tekshiradi. Host nomi server tomonidan resolve qilinadi va setWebhook paytida ham, har bir yetkazishdan oldin ham tekshiriladi. Tekshirilgan IP manzilga to'g'ridan-to'g'ri ulaniladi (DNS rebinding'dan himoya; SNI va Host sarlavhasi asl nom bilan qoladi). Quyidagilar rad etiladi:

ToifaMisollar
Xususiy tarmoqlar10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, IPv6 ULA fc00::/7
Loopback va "this network"127.0.0.0/8, 0.0.0.0/8, ::1, ::
Link-local va metadata169.254.0.0/16 (jumladan 169.254.169.254), fe80::/10
CGNAT100.64.0.0/10
Multicast va zaxira224.0.0.0/4, 240.0.0.0/4, ff00::/8
Hujjat/test tarmoqlari192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 198.18.0.0/15, 2001:db8::/32, 192.0.0.0/24
IPv4 joylangan IPv6 shakllariIPv4-mapped (::ffff:…), IPv4-compatible, NAT64 (64:ff9b::/96, 64:ff9b:1::/48), 6to4 (2002::/16, 192.88.99.0/24), Teredo (2001::/32), 100::/64, fec0::/10
Zamin'ning o'zizamin.app va *.zamin.app, Zamin serverining IP manzillari
Lokal nomlarlocalhost, *.localhost, localhost.localdomain, ip6-localhost, ip6-loopback, *.local, *.internal

DNS javoblaridan birortasi ham ommaviy bo'lmagan manzil bo'lsa, butun manzil rad etiladi. Host nomida nuqta bo'lishi va faqat a-z 0-9 . - belgilaridan iborat bo'lishi kerak.

Webhook yoki getUpdates?

getUpdatesWebhook
SozlashHech narsa kerak emasDomen, HTTPS, ochiq port
KechikishJuda kichik (long poll)Juda kichik
Bir nechta serverFaqat bitta nusxaYuk taqsimlagich ortida bir nechta nusxa
Mos keladiIshlab chiqish, kichik botlar, uy serveriProduction, serverless, katta botlar

To'liq misollar: long polling echo bot va Flask webhook.