مشتری خدمت، روز و یکی از ساعتهای خالی را انتخاب میکند و شمارهاش را با یک دکمه میفرستد؛ نوبت ثبت و برای شما ارسال میشود و هیچ ساعتی دو بار رزرو نمیشود.
سوال یا راهنمایی لازم داری؟ از صفحهٔ تماس با ما بپرس.
این ربات برای کسبوکارهای نوبتی مثل سالن زیبایی، مطب، آموزشگاه و کارواش است. کاربر خدمت را انتخاب میکند، از بین روزهای کاری آینده (بدون روزهای تعطیل) یک روز برمیدارد و فقط ساعتهای خالی همان روز را میبیند. بعد نام و شماره تماسش را میفرستد. نوبت در bookings.csv ذخیره و برای مدیر ارسال میشود.
این مراحل را بهترتیب انجام بده تا ربات روی سیستم خودت یا سرور بالا بیاید. داخل فایل ZIP یک راهنمای README هم هست.
این همان کدی است که داخل فایل ZIP هم هست. فقط یادت باشد مقدار TOKEN را با توکن رباتت عوض کنی.
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# ربات رزرو و نوبتدهی تلگرام
# طراحی و کدنویسی: سروش مقدمی — فیلتور (filtori.com)
# ------------------------------------------------------------
# مناسب سالن زیبایی، مطب، آموزشگاه، کارواش و هر کسبوکار نوبتی:
# ۱) کاربر خدمت رو انتخاب میکنه
# ۲) از بین روزهای کاری آینده یک روز انتخاب میکنه
# ۳) فقط ساعتهای «خالی» همون روز بهش نشون داده میشه
# ۴) نام و شمارهٔ تماس (با یک دکمه) رو میفرسته
# نوبت در bookings.csv ذخیره میشه و برای مدیر هم ارسال میشه.
# یک ساعت هیچوقت دو بار رزرو نمیشه.
#
# ⚠️ نکته: ربات تلگرام باید روی سروری اجرا بشه که به
# api.telegram.org دسترسی داره.
#
# آموزش کامل:
# https://filtori.com/telegram-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://api.telegram.org/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 را با خدمات و ساعتهای کاری خودت عوض کن.
CLOSED_WEEKDAYS روزهای تعطیل است (دوشنبه=0 … جمعه=4 … یکشنبه=6) و DAYS_AHEAD تعداد روزهای قابل رزرو.
این سورس تاریخ میلادی نشان میدهد تا بدون کتابخانهٔ اضافه اجرا شود. برای تاریخ شمسی میتوان کتابخانهٔ jdatetime را اضافه کرد؛ در نسخهٔ اختصاصی یادآوری پیامکی و پنل هم اضافه میشود.
اگر ربات اجرا نشد، اول پیام خطای ترمینال را با این جدول مقایسه کن. اگر خطایت اینجا نبود، از صفحهٔ تماس با ما بپرس.
| پیام خطا | علت | راهحل |
|---|---|---|
401 Unauthorized | توکن اشتباه است یا فاصلهٔ اضافه دارد. | توکن را دوباره از BotFather کپی کن و بین دو علامت نقلقول بگذار. |
409 Conflict | یک نسخهٔ دیگر از ربات در حال اجراست یا روی ربات وبهوک تنظیم شده. | نسخهٔ دیگر را خاموش کن. اگر قبلاً وبهوک گذاشتهای، آدرس api.telegram.org/bot<TOKEN>/deleteWebhook را یک بار باز کن. |
| Timeout یا ConnectionError | سرور به api.telegram.org دسترسی ندارد. | ربات را روی سروری اجرا کن که به تلگرام دسترسی دارد؛ روی بیشتر هاستهای داخل ایران این آدرس باز نیست. |
ModuleNotFoundError: requests | کتابخانهٔ requests نصب نشده. | دستور pip install -r requirements.txt یا pip install requests را اجرا کن. |
429 Too Many Requests | در زمان کوتاه پیام زیادی ارسال شده. | بین ارسالها مکث بگذار؛ تلگرام حدود ۳۰ پیام در ثانیه اجازه میدهد. |
| ربات در گروه جواب نمیدهد | Privacy Mode ربات روشن است. | در BotFather دستور /setprivacy را بزن و آن را Disable کن، یا ربات را ادمین گروه کن. |
با python bot.py ربات تا وقتی ترمینال باز است کار میکند. برای اجرای دائمی روی سرور لینوکس (Ubuntu/Debian)، فایلها را در پوشهٔ /opt/mybot بگذار و یک سرویس systemd بساز تا ربات بعد از خطا یا ریاستارت سرور خودش دوباره روشن شود.
[Unit]
Description=Filtori Telegram Bot
After=network-online.target
[Service]
WorkingDirectory=/opt/mybot
ExecStart=/usr/bin/python3 /opt/mybot/bot.py
Environment=PYTHONUNBUFFERED=1
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo nano /etc/systemd/system/mybot.service # متن بالا را اینجا بگذار
sudo systemctl daemon-reload
sudo systemctl enable --now mybot # روشنکردن + اجرای خودکار بعد از ریاستارت
journalctl -u mybot -f # دیدن لاگها
راهاندازی روی سرور برایت سخت است؟ نصب و راهاندازی را به ما بسپار.
درخواست راهاندازیاین سورس برای یادگیری و شروع کار عالی است. وقتی ربات قرار است هر روز با مشتری واقعی کار کند، معمولاً به دیتابیس، پنل مدیریت، امنیت بیشتر و پشتیبانی نیاز دارد. تیم فیلتور همین ربات رزرو و نوبتدهی تلگرام را اختصاصی و متناسب با فرآیند کسبوکارت میسازد.
| امکان | سورس رایگان | نسخهٔ اختصاصی |
|---|---|---|
| ذخیرهسازی | فایل CSV/TXT | دیتابیس |
| پنل مدیریت | — | ✓ |
| پرداخت آنلاین | — | ✓ |
| اتصال به سایت / CRM | — | ✓ |
| نصب و پشتیبانی | خودت | تیم فیلتور |
خیر. ربات فقط ساعتهای خالی را نشان میدهد و درست قبل از ثبت نهایی هم دوباره بررسی میکند؛ اگر ساعت در این فاصله پر شده باشد، از کاربر میخواهد ساعت دیگری انتخاب کند.
در فایل bookings.csv کنار برنامه، همراه تاریخ، ساعت، خدمت، نام و شماره تماس. فایل با اکسل هم باز میشود.
این سورس یادآوری ندارد. یادآوری خودکار در تلگرام یا با پیامک، لغو نوبت توسط مشتری و پنل مدیریت در نسخهٔ اختصاصی قابل پیادهسازی است.
از صفحهٔ تماس با ما در filtori.com/contact پیام بدهید یا با شمارهٔ ۰۹۱۲۲۸۷۴۸۶۲ تماس بگیرید؛ اگر نسخهٔ اختصاصی همین ربات را برای کسبوکارتان بخواهید هم همین مسیر را استفاده کنید.
اگر در راهاندازی گیر کردی، یا میخواهی همین ربات برای کسبوکارت نصب و شخصیسازی شود، از صفحهٔ تماس با ما پیام بده.