Python yordamchi klassi
Kutubxonasiz, bitta fayldagi kichik o'ram: call() metodi, ZaminError xatosi, 429'da avtomatik kutish, multipart yuklash, fayl yuklab olish va yangilanishlar generatori.
zamin.py
zamin.py
import json
import time
import requests
class ZaminError(Exception):
"""Bot API xatosi: code (error_code), description, retry_after."""
def __init__(self, code, description, retry_after=None):
super().__init__(f"{code}: {description}")
self.code = code
self.description = description
self.retry_after = retry_after
def with_retry(send, retries=3, max_wait=60):
"""send() so'rovni yuboradi va javob JSON'ini (dict) qaytaradi.
429 kelsa parameters.retry_after soniya kutib, aynan o'sha so'rovni qayta yuboradi.
Limit rad etgan so'rov hech narsa qilmagan (xabar ketmagan, fayl saqlanmagan),
shuning uchun qayta yuborish xavfsiz: xabar ikki marta chiqmaydi.
"""
for attempt in range(retries + 1):
body = send()
if body.get("error_code") != 429 or attempt == retries:
return body
wait = (body.get("parameters") or {}).get("retry_after") or 1
if wait > max_wait:
return body # masalan, kunlik chatlar limiti (3600 s): kutmasdan xatoni qaytaramiz
time.sleep(wait)
class ZaminBot:
"""Zamin Bot API uchun kichik o'ram (wrapper)."""
def __init__(self, token, base_url="https://api.zamin.app"):
self.api = f"{base_url}/bot{token}"
self.file_api = f"{base_url}/file/bot{token}"
self.http = requests.Session()
def call(self, method, params=None, files=None, http_timeout=15, retries=3):
"""Metodni chaqiradi va result'ni qaytaradi. 429 da retry_after kutib qayta urinadi.
files: {"photo": ochiq_fayl} — qayta urinishda fayl boshidan yuboriladi.
"""
params = params or {}
def send():
if files:
for f in files.values():
f.seek(0)
# multipart: murakkab qiymatlar (reply_markup) JSON matn bo'lib yuboriladi
data = {k: json.dumps(v) if isinstance(v, (dict, list)) else v for k, v in params.items()}
r = self.http.post(f"{self.api}/{method}", data=data, files=files, timeout=http_timeout)
else:
r = self.http.post(f"{self.api}/{method}", json=params, timeout=http_timeout)
try:
return r.json()
except ValueError:
if r.status_code == 429:
# proksi darajasidagi IP limiti: javob JSON bo'lmasligi mumkin
return {"ok": False, "error_code": 429, "parameters": {"retry_after": 1}}
raise ZaminError(r.status_code, f"JSON bo'lmagan javob (HTTP {r.status_code})")
body = with_retry(send, retries)
if body.get("ok"):
return body["result"]
retry = (body.get("parameters") or {}).get("retry_after")
raise ZaminError(body.get("error_code", 0), body.get("description", ""), retry)
# --- qulay qisqartmalar ---
def send_message(self, chat_id, text, **extra):
return self.call("sendMessage", {"chat_id": chat_id, "text": text, **extra})
def send_photo(self, chat_id, path_or_file_id, caption=None):
params = {"chat_id": chat_id}
if caption:
params["caption"] = caption
if path_or_file_id.startswith("BQ"):
return self.call("sendPhoto", {**params, "photo": path_or_file_id})
with open(path_or_file_id, "rb") as f:
return self.call("sendPhoto", params, files={"photo": f}, http_timeout=60)
def download(self, file_id, path, retries=3):
info = self.call("getFile", {"file_id": file_id})
url = f"{self.file_api}/{info['file_path']}"
for attempt in range(retries + 1):
with self.http.get(url, stream=True, timeout=60) as r:
if r.status_code == 429 and attempt < retries:
# bir vaqtda juda ko'p yuklab olish (bot bo'yicha 3 ta): retry_after kutamiz
try:
wait = r.json()["parameters"]["retry_after"]
except (ValueError, KeyError, TypeError):
wait = 1
time.sleep(wait)
continue
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(64 * 1024):
f.write(chunk)
return path
def updates(self, timeout=30, allowed=None):
"""Yangilanishlar generatori (long polling). offset'ni o'zi boshqaradi."""
offset = None
while True:
params = {"timeout": timeout}
if offset is not None:
params["offset"] = offset
if allowed is not None:
params["allowed_updates"] = allowed
try:
batch = self.call("getUpdates", params, http_timeout=timeout + 10)
except ZaminError as e:
if e.code in (401, 409):
raise # token noto'g'ri yoki webhook yoqilgan / boshqa nusxa ishlayapti
time.sleep(e.retry_after or 3)
continue
except requests.RequestException:
time.sleep(3)
continue
for update in batch:
offset = update["update_id"] + 1
yield updateFoydalanish
main.py
import os
from zamin import ZaminBot, ZaminError # yuqoridagi kod zamin.py faylida
bot = ZaminBot(os.environ["ZAMIN_BOT_TOKEN"])
me = bot.call("getMe")
print(f"@{me['username']} ishga tushdi")
bot.call("deleteWebhook")
for update in bot.updates(allowed=["message"]):
message = update.get("message")
if not message:
continue
chat_id = message["chat"]["id"]
try:
if "photo" in message:
bot.download(message["photo"][-1]["file_id"], f"rasm_{message['message_id']}.jpg")
bot.send_message(chat_id, "Rasm saqlandi ✅")
elif "text" in message:
bot.send_message(chat_id, message["text"].upper())
except ZaminError as e:
print("Bot API xatosi:", e.code, e.description)Eslatmalar
- 429 va
with_retry. Har bir limit429 Too Many Requests: retry after Nvaparameters.retry_afterbilan javob beradi. Rad etilgan so'rov hech narsa qilmagan, shuning uchunwith_retryretry_aftersoniya kutib aynan o'sha so'rovni qayta yuboradi (xabar ikki marta chiqmaydi).max_wait'dan uzoq kutish kerak bo'lsa (masalan, kunlik limit — 3600 s), xato darhol qaytadi. Fayl yuklash ham qayta urinadi (fayl boshidan o'qiladi) — bir vaqtdagi fayl uzatish limitiretry_after: 1beradi.download()ham yuklab olishdagi 429'ni kutib qayta urinadi. call()parametrlarni lug'at (dict) sifatida oladi — shunda getUpdates'ningtimeoutparametri HTTP kutish vaqti bilan aralashmaydi (http_timeoutalohida).- Multipart so'rovda
reply_markupkabi obyektlar JSON matnga aylantiriladi. send_photo:BQ…bilan boshlansa file_id, aks holda fayl yo'li deb qabul qilinadi.updates()401 va 409 xatolarida to'xtaydi: token noto'g'ri, webhook yoqilgan yoki botning boshqa nusxasi ishlayapti.