Eslatma bot
/eslatma 10m Choy ichish β 10 daqiqadan keyin bot eslatadi. Zamin'ning namunaviy @yordamchi_bot'idagi /eslatma kabi. Eslatmalar va offset JSON faylda saqlanadi, shuning uchun bot qayta ishga tushsa ham hech narsa yo'qolmaydi.
Kod
reminder_bot.py
import json
import os
import re
import time
import requests
TOKEN = os.environ["ZAMIN_BOT_TOKEN"]
API = f"https://api.zamin.app/bot{TOKEN}"
DB_FILE = "reminders.json"
UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400}
MAX_SECONDS = 30 * 86400
HELP = (
"β° Eslatma bot\n\n"
"/eslatma 10m Choy ichish β 10 daqiqadan keyin eslataman\n"
"Birliklar: s (soniya), m (daqiqa), h (soat), d (kun), 30 kungacha\n"
"/eslatmalar β rejalashtirilgan eslatmalar\n"
"/tozalash β hammasini o'chirish"
)
def call(method, params=None, http_timeout=15):
for _ in range(3):
r = requests.post(f"{API}/{method}", json=params or {}, timeout=http_timeout)
body = r.json()
if body.get("ok"):
return body["result"]
retry = (body.get("parameters") or {}).get("retry_after")
if body.get("error_code") == 429 and retry:
time.sleep(retry)
continue
raise RuntimeError(f"{method}: {body.get('error_code')} {body.get('description')}")
raise RuntimeError(f"{method}: juda ko'p urinish")
def load():
try:
with open(DB_FILE, encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return {"offset": None, "reminders": []}
def save(db):
tmp = DB_FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(db, f, ensure_ascii=False)
os.replace(tmp, DB_FILE) # yarim yozilgan fayl qolmasin
db = load()
def send(chat_id, text):
call("sendMessage", {"chat_id": chat_id, "text": text})
def on_message(message):
chat_id = message["chat"]["id"]
text = message.get("text", "").strip()
mine = [r for r in db["reminders"] if r["chat_id"] == chat_id]
m = re.match(r"^/eslatma(?:@\w+)?\s+(\d{1,4})([smhd])\s+(.+)$", text, re.S)
if m:
seconds = int(m.group(1)) * UNITS[m.group(2)]
if not 0 < seconds <= MAX_SECONDS:
return send(chat_id, "Muddat 1 soniyadan 30 kungacha bo'lsin.")
if len(mine) >= 50:
return send(chat_id, "Eslatmalar soni 50 tadan oshmasin.")
db["reminders"].append({"chat_id": chat_id, "at": time.time() + seconds, "text": m.group(3)[:500]})
save(db)
return send(chat_id, f"π {m.group(1)}{m.group(2)} dan keyin eslataman: {m.group(3)[:500]}")
if text.startswith("/eslatmalar"):
if not mine:
return send(chat_id, "Rejalashtirilgan eslatma yo'q.")
lines = []
for r in sorted(mine, key=lambda r: r["at"]):
minutes = max(0, int((r["at"] - time.time()) // 60))
lines.append(f"β’ {minutes} daqiqadan keyin β {r['text']}")
return send(chat_id, "β° Eslatmalar:\n" + "\n".join(lines))
if text.startswith("/tozalash"):
db["reminders"] = [r for r in db["reminders"] if r["chat_id"] != chat_id]
save(db)
return send(chat_id, "π Barcha eslatmalar o'chirildi.")
send(chat_id, HELP)
def forget(chat_id):
"""Foydalanuvchi botni blokladi: uning ma'lumotlarini saqlamaymiz."""
db["reminders"] = [r for r in db["reminders"] if r["chat_id"] != chat_id]
save(db)
def fire_due():
now = time.time()
due = [r for r in db["reminders"] if r["at"] <= now]
for r in due:
try:
send(r["chat_id"], f"β° Eslatma: {r['text']}")
except (requests.RequestException, RuntimeError, ValueError) as e:
print("Yuborilmadi:", e) # masalan, chat not found: foydalanuvchi bloklagan
db["reminders"].remove(r)
if due:
save(db)
def main():
call("deleteWebhook")
call("setMyCommands", {"commands": [
{"command": "eslatma", "description": "Eslatma: /eslatma 10m Choy ichish"},
{"command": "eslatmalar", "description": "Eslatmalarim"},
{"command": "tozalash", "description": "Hammasini o'chirish"},
]})
while True:
fire_due()
# Long poll eng yaqin eslatma vaqtidan oshmasin
upcoming = [r["at"] for r in db["reminders"]]
wait = 25 if not upcoming else max(0, min(25, int(min(upcoming) - time.time())))
params = {"timeout": wait, "allowed_updates": ["message", "my_chat_member"]}
if db["offset"] is not None:
params["offset"] = db["offset"]
try:
updates = call("getUpdates", params, http_timeout=wait + 10)
except (requests.RequestException, RuntimeError, ValueError) as e:
print("getUpdates xatosi:", e)
time.sleep(3)
continue
for update in updates:
db["offset"] = update["update_id"] + 1
try:
if "message" in update:
on_message(update["message"])
elif "my_chat_member" in update:
member = update["my_chat_member"]
if member["new_chat_member"]["status"] == "kicked":
forget(member["chat"]["id"])
except (requests.RequestException, RuntimeError, ValueError) as e:
print("Xato:", e)
if updates:
save(db) # offset ham saqlanadi: qayta ishga tushganda takrorlanmaydi
if __name__ == "__main__":
main()Ishga tushirish
Terminal
pip3 install requests
export ZAMIN_BOT_TOKEN="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
python3 reminder_bot.pyQanday ishlaydi
- Alohida taymer yoki thread yo'q: long poll'ning
timeout'i eng yaqin eslatmagacha qisqartiriladi, so'ngfire_due()vaqti kelganlarini yuboradi. - Fayl avval
.tmp'ga yoziladi vaos.replacebilan almashtiriladi β elektr o'chsa ham fayl buzilmaydi. my_chat_memberβkickedkelsa, foydalanuvchining barcha eslatmalari o'chiriladi: bloklagan odamga baribir yozib bo'lmaydi, ma'lumotini saqlash esa keraksiz.- Foydalanuvchi bloklagan bo'lsa, sendMessage
chat not foundqaytaradi β eslatma tashlab yuboriladi.