Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

کتابخانه caler-bale

کتابخانه‌ای سبک و پیشرفته برای ساخت کیبوردهای اینلاین و دکمه‌های منو در ربات‌های پیام‌رسان Bale. این کتابخانه تمام جزئیات ساخت کیبورد را به صورت خودکار مدیریت می‌کند و کدنویسی شما را بسیار ساده‌تر می‌سازد.

نصب

برای نصب، کافی است از pip استفاده کنید:

pip install caler-bale

نیازمندی: پایتون ۳.۱۰ یا بالاتر

شروع سریع

یک کیبورد ساده با دو دکمه بسازید:

from caler_bale import Button, ButtonStyle, Keyboard

kb = ( Keyboard() .add_row( Button.callback("شروع", "start", style=ButtonStyle.ROCKET), Button.url_button("وب‌سایت", "https://example.com", style=ButtonStyle.INFO), ) )

print(kb.to_dict())

خروجی یک دیکشنری استاندارد است که مستقیماً به API ربات Bale داده می‌شود.

استایل‌های پیش‌فرض

کتابخانه ۱۸ استایل رنگی دارد که هرکدام یک اموجی پیش‌فرض دارند:

· DEFAULT ⚪ · PRIMARY 🔵 · SUCCESS 🟢 · DANGER 🔴 · WARNING 🟡 · INFO 🟣 · STAR ⭐ · HEART 💜 · FIRE 🔥 · SHIELD 🛡️ · ROCKET 🚀 · GHOST ✨ · SOFT 🌫️ · TECH 🧠 · VIP 👑 · NEW 🆕 · HOT ⚡ · CLEAN 🎯

برای هر دکمه می‌توانید استایل را انتخاب کنید یا با پارامتر icon اموجی دیگری جایگزین کنید.

دکمه‌ها (Button)

با استفاده از متدهای کلاس می‌توانید انواع دکمه را بسازید:

· Button.callback(text, callback_data, ...) دکمه کالبک · Button.url_button(text, url, ...) دکمه لینک · Button.inline_query(text, query, ...) دکمه جستجوی درون‌خطی · Button.action(text, action, value, ...) ساخت با نام عملکرد

پارامترهای اضافی:

· style یکی از ButtonStyle ها · icon اموجی دلخواه · badge یک متن کوتاه قبل از متن اصلی · suffix یک متن کوتاه بعد از متن اصلی · compact حالت فشرده (فقط اموجی و متن)

مثال:

btn = Button.callback("پرداخت", "pay", style=ButtonStyle.SUCCESS, badge="VIP", suffix="تومان") print(btn.render_text()) # 🟢 VIP پرداخت تومان

هر دکمه متد to_dict() برای تبدیل به دیکشنری API و to_bale() برای تبدیل به شیء Bale (در صورت نصب کتابخانه bale) دارد. همچنین می‌توانید با copy, with_style, with_icon, with_badge دکمه جدیدی بر اساس دکمه قبلی بسازید.

کیبوردها (Keyboard)

شیء Keyboard مجموعه‌ای از ردیف‌ها و دکمه‌ها را مدیریت می‌کند.

ساخت دستی

kb = Keyboard() kb.add_row(btn1, btn2) # یک ردیف اضافه می‌کند kb.add_button(btn3) # دکمه به ردیف آخر اضافه می‌شود kb.add_grid(buttons, columns=2) # دکمه‌ها را در ستون‌های مشخص می‌چیند kb.insert_row(0, btn4) # ردیف در مکان دلخواه درج می‌کند kb.remove_row(1) # ردیف را حذف می‌کند kb.merge(other_kb) # کیبورد دیگر را به این کیبورد اضافه می‌کند

الگوهای آماده

  1. صفحه‌بندی (Pager)

kb = Keyboard.pager( page=2, total_pages=5, prefix="page", prev_text="قبلی", next_text="بعدی", close_text="بستن", show_counter=True )

دکمه‌های قبلی، شمارنده (در صورت تمایل) و بعدی را می‌سازد و یک دکمه بستن هم اضافه می‌کند. callback_data ها به صورت خودکار مثل page:1, page:3, page:close ساخته می‌شوند.

  1. تأیید و انصراف (Confirm/Cancel)

kb = Keyboard.confirm_cancel( confirm_text="بله", cancel_text="خیر", confirm_data="confirm", cancel_data="cancel" )

  1. شبکه‌های اجتماعی (Social)

kb = Keyboard.social( telegram_url="https://t.me/...", website_url="https://...", github_url="https://github.com/...", instagram_url="https://instagram.com/..." )

فقط لینک‌هایی که بدهید دکمه می‌سازد و بقیه را نادیده می‌گیرد.

  1. انتخابگر (Selector)

kb = Keyboard.selector( title="شهر خود را انتخاب کنید", options=["تهران", "اصفهان", "شیراز"], selected="تهران", prefix="city", columns=2 )

گزینه‌ها را در ستون‌های مشخص می‌چیند و گزینه انتخاب‌شده را با علامت ✅ نمایش می‌دهد.

  1. کلید روشن/خاموش (Toggle Row)

kb = Keyboard.toggle_row( label="اعلان", enabled=True, on_data="notif_on", off_data="notif_off", on_text="روشن", off_text="خاموش" )

  1. نوار چیپ (Chip Bar)

chips = [("ورزشی", "sport"), ("علمی", "science"), ("هنری", "art")] kb = Keyboard.chip_bar(chips, prefix="filter", columns=3, active="sport")

برچسب‌های قابل کلیک می‌سازد و برچسب فعال را برجسته می‌کند.

  1. گرید و From Rows

kb = Keyboard.grid(buttons, columns=3) kb = Keyboard.from_rows([[btn1, btn2], [btn3]])

خروجی نهایی

با kb.to_dict() یک دیکشنری استاندارد {"inline_keyboard": [[...], ...]} می‌گیرید. اگر کتابخانه bale نصب باشد، متد kb.to_bale_markup() یک InlineKeyboardMarkup مستقیماً قابل استفاده تحویل می‌دهد.

دکمه‌های منو (MenuButton)

برای ساخت دکمه‌های غیر اینلاین مثل درخواست موقعیت یا شماره تماس:

from caler_bale import MenuButton

btn_location = MenuButton(text="ارسال موقعیت", request_location=True) btn_contact = MenuButton(text="ارسال شماره", request_contact=True)

مدیریت خطاها

کتابخانه سه کلاس خطای اختصاصی دارد:

· CalerBaleError (خطای پایه) · InvalidButtonError (خطا در ساخت دکمه) · InvalidKeyboardError (خطا در ساخت کیبورد)

این خطاها در صورت ورودی نامعتبر (متن خالی، نبود عملکرد، ردیف خالی و ...) صادر می‌شوند و از بروز باگ جلوگیری می‌کنند.

نکات تکمیلی

· کتابخانه کاملاً بدون وابستگی خارجی است و فقط از کتابخانه استاندارد پایتون استفاده می‌کند. · همه دکمه‌ها و کیبوردها اعتبارسنجی می‌شوند؛ مثلاً یک دکمه نمی‌تواند هم callback_data و هم url داشته باشد. · استایل‌ها صرفاً جنبه ظاهری دارند و در سمت کلاینت Bale نمایش داده می‌شوند. · برای ارسال کیبورد ساخته‌شده به API، کافی است خروجی to_dict() را در پارامتر reply_markup قرار دهید.

مثال کامل: ربات پیشرفته با caler_bale

در زیر یک ربات کامل پیام‌رسان Bale آورده شده که از تمام قابلیت‌های کتابخانه استفاده می‌کند. این ربات فقط با کتابخانه استاندارد پایتون، بدون کتابخانه bale، نوشته شده و از caler_bale برای کیبوردها بهره می‌برد.

import json import time import urllib.parse import urllib.request from typing import Any, Optional

from caler_bale import Button, ButtonStyle, Keyboard

TOKEN = "YOUR_BOT_TOKEN" BASE_URL = f"https://tapi.bale.ai/bot{TOKEN}"

def api_request(method: str, params: Optional[dict[str, Any]] = None, use_get: bool = False) -> dict[str, Any]: url = f"{BASE_URL}/{method}" if use_get: if params: query = urllib.parse.urlencode(params, doseq=True) url = f"{url}?{query}" req = urllib.request.Request(url, method="GET") else: data = json.dumps(params or {}, ensure_ascii=False).encode("utf-8") req = urllib.request.Request( url, data=data, method="POST", headers={"Content-Type": "application/json; charset=utf-8"}, ) with urllib.request.urlopen(req, timeout=70) as resp: raw = resp.read().decode("utf-8") result = json.loads(raw) if not result.get("ok", False): raise RuntimeError(f"API error in {method}: {result}") return result["result"]

def get_updates(offset: Optional[int] = None, timeout: int = 30, limit: int = 100) -> list[dict[str, Any]]: params: dict[str, Any] = {"timeout": timeout, "limit": limit} if offset is not None: params["offset"] = offset return api_request("getUpdates", params, use_get=True)

def send_message(chat_id: Any, text: str, reply_markup: Optional[dict[str, Any]] = None) -> dict[str, Any]: params: dict[str, Any] = {"chat_id": chat_id, "text": text} if reply_markup is not None: params["reply_markup"] = reply_markup return api_request("sendMessage", params, use_get=False)

def answer_callback_query(callback_query_id: str, text: str = "", show_alert: bool = False) -> dict[str, Any]: params: dict[str, Any] = {"callback_query_id": callback_query_id} if text: params["text"] = text if show_alert: params["show_alert"] = True return api_request("answerCallbackQuery", params, use_get=False)

def get_chat_id_from_update(update: dict[str, Any]) -> Optional[Any]: if "message" in update and update["message"]: return update["message"]["chat"]["id"] if "edited_message" in update and update["edited_message"]: return update["edited_message"]["chat"]["id"] if "callback_query" in update and update["callback_query"]: msg = update["callback_query"].get("message") if msg: return msg["chat"]["id"] return None

def main_menu_keyboard() -> Keyboard: return ( Keyboard() .add_row( Button.callback("فروشگاه", "shop:page:1", style=ButtonStyle.ROCKET, badge="جدید"), Button.callback("تنظیمات", "settings", style=ButtonStyle.TECH), ) .add_row( Button.callback("شبکه‌های اجتماعی", "social", style=ButtonStyle.VIP), Button.callback("انتخاب زبان", "lang:select", style=ButtonStyle.INFO), ) .add_row( Button.callback("پروفایل", "profile", style=ButtonStyle.PRIMARY, icon="🆔"), Button.callback("خروج", "exit", style=ButtonStyle.DANGER), ) .add_row( Button.url_button("مستندات", "https://github.com/your/caler_bale", style=ButtonStyle.GHOST), ) )

def shop_page_keyboard(page: int, total_pages: int = 3) -> Keyboard: products = { 1: ["محصول A - ۱۰۰ تومان", "محصول B - ۲۰۰ تومان"], 2: ["محصول C - ۱۵۰ تومان", "محصول D - ۲۵۰ تومان"], 3: ["محصول E - ۳۰۰ تومان", "محصول F - ۵۰ تومان"], } kb = Keyboard() for product in products.get(page, []): kb.add_row( Button.callback(f"🛒 {product}", f"buy:{product}", style=ButtonStyle.SUCCESS, icon="💰") ) pager = Keyboard.pager( page=page, total_pages=total_pages, prefix="shop:page", prev_text="◀️ قبلی", next_text="بعدی ▶️", close_text="بازگشت به منو", show_counter=True, ) kb.merge(pager) return kb

def settings_keyboard(notif_enabled: bool = True) -> Keyboard: kb = Keyboard() kb.merge( Keyboard.toggle_row( label="اعلان‌ها", enabled=notif_enabled, on_data="notif:off", off_data="notif:on", on_text="روشن", off_text="خاموش", ) ) kb.add_row( Button.callback("حذف حساب", "delete_account", style=ButtonStyle.DANGER, icon="⚠️") ) kb.add_row( Button.callback("بازگشت به منو", "menu", style=ButtonStyle.DEFAULT) ) return kb

def confirm_delete_keyboard() -> Keyboard: return Keyboard.confirm_cancel( confirm_text="بله، حذف شود", cancel_text="انصراف", confirm_data="delete:confirm", cancel_data="delete:cancel", )

def social_keyboard() -> Keyboard: return Keyboard.social( telegram_url="https://t.me/yourchannel", website_url="https://yourwebsite.com", github_url="https://github.com/your", instagram_url="https://instagram.com/your", )

def language_selector_keyboard(selected_lang: Optional[str] = None) -> Keyboard: return Keyboard.selector( title="انتخاب زبان", options=["فارسی", "English", "العربية"], selected=selected_lang, prefix="lang", columns=2, )

def chip_bar_keyboard(active_category: Optional[str] = None) -> Keyboard: chips = [ ("همه", "all"), ("الکترونیک", "electronics"), ("پوشاک", "clothing"), ("کتاب", "books"), ] return Keyboard.chip_bar(chips, prefix="filter", columns=4, active=active_category)

def profile_keyboard() -> Keyboard: kb = Keyboard() kb.add_row( Button.callback("آمار", "stats", style=ButtonStyle.STAR, icon="📈", suffix="کلیک کن"), Button.inline_query("جستجوی کاربر", "user:", current_chat=True, style=ButtonStyle.GHOST), ) kb.add_row( Button.callback("ویرایش نام", "edit_name", style=ButtonStyle.WARNING, compact=True), Button.callback("تغییر عکس", "change_photo", style=ButtonStyle.INFO, compact=True), ) kb.add_row( Button.callback("بازگشت", "menu", style=ButtonStyle.DEFAULT) ) return kb

user_state = {}

def handle_callback(update: dict[str, Any]) -> None: cq = update.get("callback_query") or {} callback_query_id = cq.get("id") if not callback_query_id: return data = cq.get("data") or "" msg = cq.get("message") or {} chat_id = msg.get("chat", {}).get("id") if not chat_id: return answer_callback_query(callback_query_id)

if data == "menu": send_message(chat_id, "منوی اصلی:", reply_markup=main_menu_keyboard().to_dict()) elif data.startswith("shop:page:"): try: page = int(data.split(":")[-1]) except ValueError: page = 1 kb = shop_page_keyboard(page) chip = chip_bar_keyboard(active_category=user_state.get(chat_id, {}).get("filter")) kb.merge(chip) send_message(chat_id, "محصولات:", reply_markup=kb.to_dict()) elif data.startswith("buy:"): product = data[len("buy:"):] answer_callback_query(callback_query_id, f"محصول «{product}» به سبد خرید افزوده شد", show_alert=True) elif data in ("notif:on", "notif:off"): new_state = data == "notif:on" user_state.setdefault(chat_id, {})["notif"] = new_state kb = settings_keyboard(notif_enabled=new_state) send_message(chat_id, f"اعلان‌ها {'روشن' if new_state else 'خاموش'} شد.", reply_markup=kb.to_dict()) elif data == "delete_account": send_message(chat_id, "آیا مطمئنی می‌خواهی حساب کاربری‌ات حذف شود؟", reply_markup=confirm_delete_keyboard().to_dict()) elif data == "delete:confirm": send_message(chat_id, "حساب کاربری با موفقیت حذف شد. خدانگهدار!", reply_markup=main_menu_keyboard().to_dict()) elif data == "delete:cancel": send_message(chat_id, "عملیات حذف لغو شد.", reply_markup=settings_keyboard().to_dict()) elif data == "settings": send_message(chat_id, "تنظیمات:", reply_markup=settings_keyboard().to_dict()) elif data == "social": send_message(chat_id, "شبکه‌های اجتماعی ما:", reply_markup=social_keyboard().to_dict()) elif data == "lang:select": send_message(chat_id, "زبان مورد نظر را انتخاب کن:", reply_markup=language_selector_keyboard().to_dict()) elif data.startswith("lang:"): lang = data[len("lang:"):] if lang in ("فارسی", "English", "العربية"): user_state.setdefault(chat_id, {})["lang"] = lang kb = language_selector_keyboard(selected_lang=lang) send_message(chat_id, f"زبان به «{lang}» تغییر یافت.", reply_markup=kb.to_dict()) elif data.startswith("filter:"): category = data[len("filter:"):] user_state.setdefault(chat_id, {})["filter"] = category chip = chip_bar_keyboard(active_category=category) send_message(chat_id, f"دسته‌بندی: {category}", reply_markup=chip.to_dict()) elif data == "profile": send_message(chat_id, "پروفایل شما:", reply_markup=profile_keyboard().to_dict()) elif data == "stats": kb = Keyboard() kb.add_row(Button.callback("بازدید: ۱۲۳۴", "noop", style=ButtonStyle.STAR, badge="VIP", compact=True)) kb.add_row(Button.callback("بازگشت", "profile", style=ButtonStyle.DEFAULT)) send_message(chat_id, "آمار حساب:", reply_markup=kb.to_dict()) elif data == "exit": send_message(chat_id, "به امید دیدار دوباره 👋", reply_markup=None) else: answer_callback_query(callback_query_id, "دکمه نامعتبر", show_alert=True)

def handle_message(update: dict[str, Any]) -> None: chat_id = get_chat_id_from_update(update) if chat_id is None: return msg = update.get("message") or update.get("edited_message") or {} text = (msg.get("text") or "").strip().lower() if text in ("/start", "start", "menu"): send_message(chat_id, "سلام 👋\nبه ربات پیشرفته caler_bale خوش آمدی.", reply_markup=main_menu_keyboard().to_dict()) else: send_message(chat_id, "متوجه نشدم. منوی اصلی:", reply_markup=main_menu_keyboard().to_dict())

def main() -> None: me = api_request("getMe", use_get=True) print(f"Logged in as: @{me.get('username', 'unknown')}") offset: Optional[int] = None while True: try: updates = get_updates(offset=offset, timeout=30, limit=100) for update in updates: update_id = update["update_id"] offset = update_id + 1 if "message" in update or "edited_message" in update: handle_message(update) elif "callback_query" in update: handle_callback(update) except KeyboardInterrupt: print("Stopped.") break except Exception as e: print(f"Error: {e}") time.sleep(2)

if name == "main": main()

مشارکت

اگر پیشنهادی دارید یا اشکالی پیدا کردید، لطفاً در گیت‌هاب issue یا pull request ثبت کنید.

مجوز

این پروژه تحت مجوز MIT منتشر شده است.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors