پرش به محتویات

راهنمای کیبوردها و ماشین وضعیت گفتگو (FSM)

این راهنما بر اساس استانداردهای کدنویسی PEP 8 نگارش شده و نحوه ساخت کیبوردهای شیشه‌ای، کیبوردهای پاسخ و مدیریت مراحل فرم‌ها را تشریح می‌کند.


۱. کیبوردهای اینلاین شیشه‌ای (InlineKeyboardMarkup)

کلیدهای شیشه‌ای زیر پیام قرار می‌گیرند و برای باز کردن لینک‌ها یا ارسال داده‌های بازگشتی (callback_data) کاربرد دارند:

from balenova import Client, events, filters
from balenova.keyboards import InlineKeyboardButton, InlineKeyboardMarkup

app = Client.from_browser("auto")


def build_main_inline_keyboard() -> InlineKeyboardMarkup:
    """ساخت کیبورد اینلاین اصلی با ساختار ردیف‌های منظم."""
    return (
        InlineKeyboardMarkup()
        .row(
            InlineKeyboardButton(
                text="🌐 سایت مستندات",
                url="https://iliasync.github.io/balenova/",
            ),
            InlineKeyboardButton(
                text="⚡ استعلام حساب",
                callback_data="btn_check_account",
            ),
        )
        .row(
            InlineKeyboardButton(
                text="🎮 اجرای مینی‌اپ",
                web_app_url="https://app.bale.ai",
            ),
            InlineKeyboardButton(
                text="📞 پشتیبانی",
                callback_data="btn_support",
            ),
        )
    )


@app.on(events.NewMessage, filters.command("panel"))
async def handle_panel_command(event: events.NewMessage) -> None:
    keyboard = build_main_inline_keyboard()
    await event.reply("کنترل پنل کاربری:", reply_markup=keyboard)

۲. کیبوردهای پاسخ زیرین (ReplyKeyboardMarkup)

این دکمه‌ها در پایین صفحه جایگزین صفحه کلید دستگاه می‌شوند:

from balenova.keyboards import (
    KeyboardButton,
    ReplyKeyboardMarkup,
    ReplyKeyboardRemove,
)


def build_contact_reply_keyboard() -> ReplyKeyboardMarkup:
    """ساخت کیبورد پاسخ با درخواست دسترسی شماره و موقعیت."""
    return (
        ReplyKeyboardMarkup(resize_keyboard=True)
        .row(
            KeyboardButton(
                text="📱 ارسال شماره من",
                request_contact=True,
            ),
            KeyboardButton(
                text="📍 ارسال موقعیت مکانی",
                request_location=True,
            ),
        )
        .row(
            KeyboardButton(text="ℹ️ اطلاعات حساب"),
            KeyboardButton(text="❌ انصراف"),
        )
    )


@app.on(events.NewMessage, filters.command("menu"))
async def handle_menu_command(event: events.NewMessage) -> None:
    reply_kb = build_contact_reply_keyboard()
    await event.reply("لطفاً یک گزینه را انتخاب کنید:", reply_markup=reply_kb)


@app.on(events.NewMessage, filters.text == "❌ انصراف")
async def handle_cancel(event: events.NewMessage) -> None:
    # حذف کامل کیبورد از صفحه کاربر
    remove_kb = ReplyKeyboardRemove()
    await event.reply("منو بسته شد.", reply_markup=remove_kb)

۳. ماشین وضعیت گفتگو و فرم‌های چندمرحله‌ای (FSM)

سیستم FSM توکار برای نگهداری مقادیر موقت کاربران بدون تداخل پیام‌ها طراحی شده است:

from balenova import Client, events, filters
from balenova.fsm import State, StatesGroup

app = Client.from_browser("auto")


class UserProfileForm(StatesGroup):
    """تعریف مراحل فرم دریافت مشخصات کاربری."""

    waiting_for_name = State()
    waiting_for_age = State()
    waiting_for_city = State()


# مرحله ۱: شروع دریافت مشخصات
@app.on(events.NewMessage, filters.command("signup"))
async def start_signup(event: events.NewMessage) -> None:
    fsm = event.get_fsm_context()
    await fsm.set_state(UserProfileForm.waiting_for_name)
    await event.reply("سلام! لطفاً نام و نام خانوادگی خود را ارسال کنید:")


# مرحله ۲: اعتبارسنجی نام و رفتن به سن
@app.on(events.NewMessage, filters.state(UserProfileForm.waiting_for_name))
async def process_name(event: events.NewMessage) -> None:
    fsm = event.get_fsm_context()
    full_name = event.text.strip()

    if len(full_name) < 3:
        await event.reply("نام وارد شده کوتاه است. لطفاً نام کامل را بنویسید:")
        return

    await fsm.update_data(full_name=full_name)
    await fsm.set_state(UserProfileForm.waiting_for_age)
    await event.reply(f"متشکرم {full_name}! حالا سن خود را وارد کنید:")


# مرحله ۳: اعتبارسنجی سن و رفتن به شهر
@app.on(events.NewMessage, filters.state(UserProfileForm.waiting_for_age))
async def process_age(event: events.NewMessage) -> None:
    fsm = event.get_fsm_context()

    if not event.text.isdigit():
        await event.reply("لطفاً سن خود را فقط به صورت عدد بنویسید:")
        return

    await fsm.update_data(age=int(event.text))
    await fsm.set_state(UserProfileForm.waiting_for_city)
    await event.reply("در کدام شهر سکونت دارید؟")


# مرحله ۴: دریافت شهر و ذخیره نهایی
@app.on(events.NewMessage, filters.state(UserProfileForm.waiting_for_city))
async def process_city(event: events.NewMessage) -> None:
    fsm = event.get_fsm_context()
    data = await fsm.get_data()

    # پاکسازی وضعیت FSM پس از پایان فرم
    await fsm.clear()

    full_name = data.get("full_name")
    age = data.get("age")
    city = event.text.strip()

    summary = (
        "🎉 ثبت‌نام با موفقیت انجام شد:\n"
        f"👤 نام: {full_name}\n"
        f"🎂 سن: {age}\n"
        f"🏙️ شهر: {city}"
    )
    await event.reply(summary)


if __name__ == "__main__":
    app.run_forever()