رفتن به محتوای اصلی
📅 سطح: متوسط تا پیشرفته · رایگان · جدید

سورس ربات رزرو و نوبت‌دهی بله

مشتری خدمت، روز و یکی از ساعت‌های خالی را انتخاب می‌کند و شماره‌اش را با یک دکمه می‌فرستد؛ نوبت در اکسل ذخیره و برای شما ارسال می‌شود و هیچ ساعتی دو بار رزرو نمی‌شود.

طراحی و کدنویسی: به‌روزرسانی: زبان: Python 3حجم کد: حدود ۲۳۰ خطمجوز: آزاد (CC0)

پاسخ کوتاه

ربات نوبت‌دهی بله یک گفتگوی پنج‌مرحله‌ای (خدمت → روز → ساعت → نام → شماره) است. روزهای تعطیل حذف می‌شوند، فقط ساعت‌هایی نمایش داده می‌شوند که در bookings.csv رزرو نشده‌اند و درست قبل از ثبت نهایی هم دوباره بررسی می‌شود تا رزرو تکراری ممکن نباشد.

ربات رزرو و نوبت‌دهی بله چه کاری انجام می‌دهد؟

برای سالن زیبایی، مطب، آموزشگاه، کارواش و هر کسب‌وکار نوبتی. به‌جای هماهنگی تلفنی، مشتری خودش در بله خدمت را انتخاب می‌کند، از بین روزهای کاری آینده یک روز برمی‌دارد و فقط ساعت‌های خالی همان روز را می‌بیند. شماره تماس را هم با دکمهٔ «ارسال شماره» می‌فرستد تا اشتباه تایپی پیش نیاید. نوبت در bookings.csv ذخیره و همان لحظه برای مدیر ارسال می‌شود.

زبان: پایتون ۳
وابستگی: فقط requests
ذخیره: bookings.csv
دکمهٔ ارسال شماره تماس
هزینه: رایگان

آموزش راه‌اندازی ربات رزرو و نوبت‌دهی بله در ۵ قدم

این مراحل را به‌ترتیب انجام بده تا ربات روی سیستم خودت یا سرور بالا بیاید. زمان تقریبی: ۲۰ دقیقه. داخل فایل ZIP راهنمای README هم هست.

از BotFather در بله توکن ربات را بگیر.
پایتون ۳ را نصب کن و pip install requests را اجرا کن.
در فایل bot.py مقدار TOKEN و ADMIN_CHAT_ID را بگذار.
SERVICES، ساعت‌های TIME_SLOTS و روزهای تعطیل CLOSED_WEEKDAYS را مطابق کسب‌وکار خودت تنظیم کن.
python bot.py را اجرا کن، /start بزن و یک نوبت آزمایشی ثبت کن.

کل سورس ربات رزرو و نوبت‌دهی بله (کپی یا دانلود کن)

این همان کدی است که داخل فایل ZIP هم هست. می‌توانی مستقیم از اینجا کپی کنی. فقط یادت باشد مقدار TOKEN را با توکن رباتت عوض کنی.

bot.py
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
#  ربات رزرو و نوبت‌دهی بله
#  طراحی و کدنویسی: سروش مقدمی — فیلتور (filtori.com)
# ------------------------------------------------------------
#  مناسب سالن زیبایی، مطب، آموزشگاه، کارواش و هر کسب‌وکار نوبتی:
#   ۱) کاربر خدمت رو انتخاب می‌کنه
#   ۲) از بین روزهای کاری آینده یک روز انتخاب می‌کنه
#   ۳) فقط ساعت‌های «خالی» همون روز بهش نشون داده می‌شه
#   ۴) نام و شمارهٔ تماس (با یک دکمه) رو می‌فرسته
#  نوبت در bookings.csv ذخیره می‌شه و برای مدیر هم ارسال می‌شه.
#  یک ساعت هیچ‌وقت دو بار رزرو نمی‌شه.
#
#  آموزش کامل:
#     https://filtori.com/bale-bot-source/booking-bot/
#  سوال یا راهنمایی:  https://filtori.com/contact/
#  نسخهٔ اختصاصی با تقویم شمسی، پیامک یادآوری و پنل؟  09122874862
# ------------------------------------------------------------

import csv
import os
import time
from datetime import date, datetime, timedelta

import requests

TOKEN = "توکن_ربات_خودت_را_اینجا_بگذار"
BASE = f"https://tapi.bale.ai/bot{TOKEN}"

ADMIN_CHAT_ID = 123456789  # <-- chat_id مدیر برای دریافت نوبت‌های جدید

# ------------------------- تنظیمات کسب‌وکار -------------------------
SERVICES = ["کوتاهی مو", "رنگ مو", "مانیکور", "مشاوره"]
TIME_SLOTS = ["10:00", "11:00", "12:00", "16:00", "17:00", "18:00"]
DAYS_AHEAD = 7            # چند روز آینده برای رزرو نمایش داده بشه
CLOSED_WEEKDAYS = {4}     # روزهای تعطیل؛ 4 = جمعه (دوشنبه=0 ... یکشنبه=6)
BOOKINGS_FILE = "bookings.csv"
# -----------------------------------------------------------------

WEEKDAY_NAMES = ["دوشنبه", "سه‌شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه", "یکشنبه"]
CANCEL = "❌ انصراف"

sessions = {}  # حافظهٔ موقت گفتگو برای هر کاربر


def send_message(chat_id, text, keyboard=None):
    """ارسال پیام؛ keyboard یک لیست از ردیف‌های دکمه است یا None برای حذف کیبورد."""
    data = {"chat_id": chat_id, "text": text}
    if keyboard:
        data["reply_markup"] = {"keyboard": keyboard, "resize_keyboard": True}
    else:
        data["reply_markup"] = {"remove_keyboard": True}
    try:
        requests.post(f"{BASE}/sendMessage", json=data, timeout=15)
    except requests.RequestException as error:
        print("خطا در ارسال پیام:", error)


def get_updates(offset):
    params = {"offset": offset, "timeout": 30}
    try:
        return requests.get(f"{BASE}/getUpdates", params=params, timeout=40).json()
    except (requests.RequestException, ValueError) as error:
        print("خطا در دریافت پیام‌ها:", error)
        return {"ok": False, "result": []}


def rows(items, size=2):
    """تبدیل یک لیست ساده به ردیف‌های دکمه (هر ردیف size دکمه)."""
    buttons = [{"text": item} for item in items]
    return [buttons[i:i + size] for i in range(0, len(buttons), size)] + [[{"text": CANCEL}]]


def open_days():
    """روزهای کاری آینده؛ خروجی: {برچسب دکمه: تاریخ}"""
    days = {}
    today = date.today()
    for offset in range(1, DAYS_AHEAD + 1):
        day = today + timedelta(days=offset)
        if day.weekday() in CLOSED_WEEKDAYS:
            continue
        label = f"{WEEKDAY_NAMES[day.weekday()]} {day.isoformat()}"
        days[label] = day.isoformat()
    return days


def load_bookings():
    if not os.path.exists(BOOKINGS_FILE):
        return []
    with open(BOOKINGS_FILE, "r", newline="", encoding="utf-8-sig") as file:
        return list(csv.DictReader(file))


def free_slots(day_iso):
    """ساعت‌هایی از یک روز که هنوز رزرو نشده‌اند."""
    taken = {b["time"] for b in load_bookings() if b["date"] == day_iso}
    return [slot for slot in TIME_SLOTS if slot not in taken]


def save_booking(data):
    is_new = not os.path.exists(BOOKINGS_FILE)
    with open(BOOKINGS_FILE, "a", newline="", encoding="utf-8-sig") as file:
        writer = csv.DictWriter(
            file, fieldnames=["created", "date", "time", "service", "name", "phone", "chat_id"]
        )
        if is_new:
            writer.writeheader()
        writer.writerow(data)


def start(chat_id):
    sessions[chat_id] = {"step": "service"}
    send_message(chat_id, "سلام 👋 برای رزرو نوبت، اول خدمت موردنظرت رو انتخاب کن:", rows(SERVICES))


def handle(message):
    chat_id = message["chat"]["id"]
    text = message.get("text", "")
    contact = message.get("contact")

    if text == "/start":
        start(chat_id)
        return
    if text == CANCEL:
        sessions.pop(chat_id, None)
        send_message(chat_id, "رزرو لغو شد. هر وقت خواستی دوباره /start بزن.")
        return
    if chat_id not in sessions:
        send_message(chat_id, "برای رزرو نوبت، دستور /start را بفرستید.")
        return

    session = sessions[chat_id]
    step = session["step"]

    if step == "service":
        if text not in SERVICES:
            send_message(chat_id, "لطفاً یکی از خدمات رو از دکمه‌ها انتخاب کن.", rows(SERVICES))
            return
        session["service"] = text
        session["days"] = open_days()
        session["step"] = "day"
        send_message(chat_id, "کدوم روز برات مناسبه؟", rows(list(session["days"])))

    elif step == "day":
        if text not in session["days"]:
            send_message(chat_id, "لطفاً روز رو از دکمه‌ها انتخاب کن.", rows(list(session["days"])))
            return
        day_iso = session["days"][text]
        slots = free_slots(day_iso)
        if not slots:
            send_message(chat_id, "همهٔ ساعت‌های این روز پر شده 😕 یک روز دیگه انتخاب کن.",
                         rows(list(session["days"])))
            return
        session["date"] = day_iso
        session["day_label"] = text
        session["step"] = "time"
        send_message(chat_id, "ساعت‌های خالی این روز:", rows(slots, size=3))

    elif step == "time":
        if text not in free_slots(session["date"]):
            send_message(chat_id, "این ساعت در دسترس نیست؛ یکی از دکمه‌ها رو انتخاب کن.",
                         rows(free_slots(session["date"]), size=3))
            return
        session["time"] = text
        session["step"] = "name"
        send_message(chat_id, "نام و نام خانوادگی‌ات رو بنویس:", [[{"text": CANCEL}]])

    elif step == "name":
        if not text:
            return
        session["name"] = text
        session["step"] = "phone"
        send_message(
            chat_id,
            "در آخر شمارهٔ تماست رو با دکمهٔ زیر بفرست (یا تایپ کن):",
            [[{"text": "📱 ارسال شماره تماس", "request_contact": True}], [{"text": CANCEL}]],
        )

    elif step == "phone":
        phone = contact["phone_number"] if contact else text
        if not phone:
            return
        # بررسی دوباره: شاید در این فاصله کس دیگری همین ساعت را گرفته باشد
        if session["time"] not in free_slots(session["date"]):
            session["step"] = "time"
            send_message(chat_id, "متأسفانه این ساعت همین الان رزرو شد 😕 ساعت دیگری انتخاب کن:",
                         rows(free_slots(session["date"]), size=3))
            return

        booking = {
            "created": datetime.now().strftime("%Y-%m-%d %H:%M"),
            "date": session["date"],
            "time": session["time"],
            "service": session["service"],
            "name": session["name"],
            "phone": phone,
            "chat_id": chat_id,
        }
        save_booking(booking)
        summary = (
            f"🗓 {session['day_label']} ساعت {session['time']}\n"
            f"✂️ خدمت: {session['service']}\n"
            f"👤 {session['name']}  |  📞 {phone}"
        )
        send_message(chat_id, "نوبتت ثبت شد ✅\n\n" + summary + "\n\nمنتظرت هستیم 🌷")
        send_message(ADMIN_CHAT_ID, "📌 نوبت جدید:\n\n" + summary)
        del sessions[chat_id]


def main():
    print("ربات نوبت‌دهی فیلتور روشن شد... (Ctrl+C برای خاموش‌کردن)")
    offset = 0
    while True:
        updates = get_updates(offset)
        if not updates.get("ok"):
            time.sleep(2)
            continue

        for update in updates.get("result", []):
            offset = update["update_id"] + 1
            message = update.get("message")
            if message:
                handle(message)

        time.sleep(0.5)


if __name__ == "__main__":
    main()
کل سورس این ربات ↑ — همین حالا کپی یا دانلود کن

کد را خط‌به‌خط بفهم

کپی‌کردن کافی نیست؛ اگر بدانی هر بخش چرا آن‌جاست، می‌توانی ربات را برای کسب‌وکار خودت تغییر بدهی. مهم‌ترین قطعه‌های ربات رزرو و نوبت‌دهی بله:

۱همهٔ تنظیمات در یک جا

SERVICES = ["کوتاهی مو", "رنگ مو", "مانیکور", "مشاوره"]
TIME_SLOTS = ["10:00", "11:00", "12:00", "16:00", "17:00", "18:00"]
CLOSED_WEEKDAYS = {4}     # 4 = جمعه

برای شخصی‌سازی کافی است همین چند خط را عوض کنی. در پایتون شمارهٔ روزهای هفته از دوشنبه شروع می‌شود (دوشنبه=0 … جمعه=4 … یکشنبه=6)؛ برای تعطیلی پنجشنبه‌ها هم عدد 3 را اضافه کن.

۲ساختن روزهای قابل رزرو

day = today + timedelta(days=offset)
if day.weekday() in CLOSED_WEEKDAYS:
    continue

ربات از فردا تا DAYS_AHEAD روز بعد را می‌سازد و روزهای تعطیل را کنار می‌گذارد. برچسب هر دکمه شامل نام روز و تاریخ است و در session نگه داشته می‌شود تا از روی متن دکمه دوباره به تاریخ برسیم.

۳فقط ساعت‌های خالی

taken = {b["time"] for b in load_bookings() if b["date"] == day_iso}
return [slot for slot in TIME_SLOTS if slot not in taken]

ساعت‌های رزروشدهٔ همان روز از فایل خوانده می‌شوند و از لیست حذف می‌شوند؛ کاربر اصلاً ساعت پر را نمی‌بیند. اگر همهٔ ساعت‌ها پر باشد، ربات می‌خواهد روز دیگری انتخاب شود.

۴بررسی دوباره قبل از ثبت

if session["time"] not in free_slots(session["date"]):
    session["step"] = "time"

ممکن است دو نفر هم‌زمان یک ساعت را انتخاب کنند. قبل از ذخیرهٔ نهایی دوباره چک می‌کنیم؛ اگر ساعت در این فاصله پر شده باشد، کاربر به انتخاب ساعت برمی‌گردد و نوبت تکراری ثبت نمی‌شود.

۵دکمهٔ ارسال شماره تماس

[{"text": "📱 ارسال شماره تماس", "request_contact": True}]

با request_contact بله شمارهٔ ثبت‌شدهٔ کاربر را با تایید خودش می‌فرستد؛ هم سریع‌تر است هم اشتباه تایپی ندارد. ربات شمارهٔ تایپ‌شده را هم قبول می‌کند.

🔁 همین کد روی تلگرام هم اجرا می‌شود: فقط BASE را به https://api.telegram.org/bot{TOKEN} تغییر بده. نسخهٔ تلگرامی آماده را در سورس رایگان ربات تلگرام ببین.

سوال‌های پرتکرار دربارهٔ ربات رزرو و نوبت‌دهی بله

ممکن است دو نفر یک ساعت را رزرو کنند؟

خیر. ربات فقط ساعت‌های خالی را نشان می‌دهد و درست قبل از ثبت نهایی هم دوباره بررسی می‌کند؛ اگر ساعت در این فاصله پر شده باشد، از کاربر می‌خواهد ساعت دیگری انتخاب کند.

تاریخ‌ها شمسی نمایش داده می‌شوند؟

این سورس تاریخ میلادی را کنار نام روز نشان می‌دهد تا بدون کتابخانهٔ اضافه اجرا شود. برای تاریخ شمسی می‌توان کتابخانهٔ jdatetime را اضافه کرد؛ در نسخهٔ اختصاصی این کار انجام می‌شود.

نوبت‌ها کجا ذخیره می‌شوند؟

در فایل bookings.csv کنار برنامه، همراه تاریخ، ساعت، خدمت، نام و شماره تماس. فایل با اکسل هم درست باز می‌شود.

می‌شود برای مشتری یادآوری فرستاد؟

این سورس یادآوری ندارد. یادآوری خودکار در بله یا با پیامک، لغو نوبت توسط مشتری، پرداخت بیعانه و پنل مدیریت در نسخهٔ اختصاصی قابل پیاده‌سازی است.

سوال یا مشکلی داشتم از کجا راهنمایی بگیرم؟

از صفحهٔ تماس با ما پیام بدهید یا با ۰۹۱۲۲۸۷۴۸۶۲ تماس بگیرید؛ برای نسخهٔ اختصاصی همین ربات هم همین مسیر را استفاده کنید.

طراح و برنامه‌نویس این سورس‌ها · بنیان‌گذار فیلتور

مدیر بخش دیتابیس همراه اول و فعال در توسعهٔ نرم‌افزار از سال ۱۳۹۱؛ با سابقهٔ کار روی سامانه‌های بانکی و پرتراکنش. این سورس‌ها را طوری نوشته که ساده، خوانا و قابل توسعه باشند و مستقیم در کسب‌وکار واقعی استفاده شوند.

سوالی داری یا راهنمایی می‌خواهی؟

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