Spaces:
Build error
Build error
| import os, re, json, time, sqlite3, textwrap | |
| from dataclasses import dataclass | |
| from typing import Optional, Dict, List | |
| from dotenv import load_dotenv | |
| from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton | |
| from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler | |
| # ============ الإعدادات ============ | |
| load_dotenv() | |
| BOT_TOKEN = os.getenv("BOT_TOKEN", "") | |
| OWNER_ID = int(os.getenv("OWNER_ID", "0") or "0") | |
| FREE_QUOTA = int(os.getenv("FREE_QUOTA", "3")) | |
| USE_OLLAMA = os.getenv("USE_OLLAMA", "false").lower() == "true" | |
| OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3") | |
| OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434") | |
| DB_PATH = "dreambot.db" | |
| RATE_LIMIT_SECONDS = 60 # دقيقة بين كل تفسيرين لنفس المستخدم | |
| if not BOT_TOKEN: | |
| raise SystemExit("ضع BOT_TOKEN في .env") | |
| # ============ قاعدة بيانات بسيطة ============ | |
| def db_connect(): | |
| con = sqlite3.connect(DB_PATH) | |
| con.execute(""" | |
| CREATE TABLE IF NOT EXISTS users( | |
| user_id INTEGER PRIMARY KEY, | |
| used_count INTEGER DEFAULT 0, | |
| last_time INTEGER DEFAULT 0, | |
| premium INTEGER DEFAULT 0 | |
| ) | |
| """) | |
| con.commit() | |
| return con | |
| CON = db_connect() | |
| def get_user(user_id: int): | |
| cur = CON.execute("SELECT user_id, used_count, last_time, premium FROM users WHERE user_id=?", (user_id,)) | |
| row = cur.fetchone() | |
| if not row: | |
| CON.execute("INSERT INTO users(user_id, used_count, last_time, premium) VALUES(?,?,?,?)", | |
| (user_id, 0, 0, 0)) | |
| CON.commit() | |
| return (user_id, 0, 0, 0) | |
| return row | |
| def inc_usage(user_id: int): | |
| CON.execute("UPDATE users SET used_count = used_count + 1, last_time=? WHERE user_id=?", | |
| (int(time.time()), user_id)) | |
| CON.commit() | |
| def set_premium(user_id: int, val: int): | |
| CON.execute("UPDATE users SET premium=? WHERE user_id=?", (val, user_id)) | |
| CON.commit() | |
| def remaining_quota(user_id: int) -> int: | |
| u = get_user(user_id) | |
| if u[3] == 1: | |
| return 999999 | |
| return max(0, FREE_QUOTA - u[1]) | |
| def too_soon(user_id: int) -> int: | |
| u = get_user(user_id) | |
| delta = int(time.time()) - int(u[2]) | |
| return max(0, RATE_LIMIT_SECONDS - delta) | |
| # ============ أدوات اللغة (تبسيط/تنظيف) ============ | |
| AR_DIACRITICS = re.compile(r"[\u064B-\u065F\u0610-\u061A\u06D6-\u06ED]") | |
| def normalize_ar(text: str) -> str: | |
| text = text.strip() | |
| text = AR_DIACRITICS.sub("", text) | |
| # توحيد بعض الحروف | |
| text = text.replace("أ","ا").replace("إ","ا").replace("آ","ا").replace("ى","ي").replace("ة","ه") | |
| text = re.sub(r"\s+", " ", text) | |
| return text | |
| # ============ قواعد تفسير سريعة (قاموس) ============ | |
| # بسيطة ومبدئية — طوّرها كما تريد. تدعم العربية والإنجليزية. | |
| RULES: Dict[str, str] = { | |
| # Arabic | |
| "الماء": "الماء في الرؤى غالباً يُشير للحياة والرزق والصفاء. صفاء الماء يدل على صفاء القلب والنية.", | |
| "الوضوء": "الوضوء في المنام دلالة على التوبة والطهارة واستعداد لمرحلة أفضل.", | |
| "الصلاه": "الصلاة رمزية للاستقامة والطمأنينة. قد تعني زوال هم أو قضاء حاجة.", | |
| "القران": "قراءة القرآن هدى وبشرى، وقد تعني حلول البركة وطمأنينة في قرارك القادم.", | |
| "الافعى": "الأفعى قد ترمز لعدو خفي أو حسد. انتبه لحدودك وخصوصيتك.", | |
| "الثعبان": "الثعبان غالباً عداوة أو خوف مكبوت. مواجهة الثعبان قد ترمز لتخطي الخوف.", | |
| "السقوط": "السقوط يعكس قلقاً من فقدان السيطرة. راجع قراراتك وتوازناتك.", | |
| "الاسنان": "الأسنان ترتبط بالعائلة أو القوة. سقوطها قد يدل على قلق على قريب أو ضعف مؤقت.", | |
| "الزواج": "الزواج قد يكون انتقالًا أو التزاماً جديداً (عمل/شراكة/مسؤولية).", | |
| "الذهب": "الذهب رزق ومسؤولية. للرجال قد يرمز لعبء أو التزام، وللنساء لزينة ورزق.", | |
| "الميت": "رؤية الميت حق. إن كان مبتسماً فخير، وإن طلب شيئاً فصدقة له.", | |
| # English | |
| "water": "Water often symbolizes life, provision, and clarity. Clear water suggests inner peace.", | |
| "prayer": "Prayer indicates alignment, relief, or a fulfilled need.", | |
| "quran": "Reciting Quran symbolizes guidance, blessing, and calm.", | |
| "snake": "Snake may symbolize hidden enmity or anxiety. Keep healthy boundaries.", | |
| "teeth": "Teeth are tied to family or personal power. Loss may reflect worry or temporary weakness.", | |
| "falling": "Falling shows anxiety about control; reassess your balance and plans.", | |
| } | |
| KEYWORDS: List[str] = list(RULES.keys()) | |
| def match_rules(text: str) -> List[str]: | |
| hits = [] | |
| low = text.lower() | |
| for k in KEYWORDS: | |
| if k in low: | |
| hits.append(k) | |
| return hits | |
| # ============ نموذج Ollama (اختياري) ============ | |
| def ask_ollama(prompt: str) -> Optional[str]: | |
| if not USE_OLLAMA: | |
| return None | |
| try: | |
| import requests | |
| resp = requests.post(f"{OLLAMA_HOST}/api/generate", | |
| json={"model": OLLAMA_MODEL, "prompt": prompt, "stream": False}, | |
| timeout=60) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| return data.get("response") | |
| except Exception as e: | |
| return None | |
| # ============ منطق التفسير ============ | |
| def build_prompt(user_text: str) -> str: | |
| return textwrap.dedent(f""" | |
| أنت مفسر أحلام عربي رصين. فسّر الحلم التالي بلغة عربية مبسطة، بنقاط واضحة، دون جزم بالغيب، مع تنبيهات: (١) قد يكون من حديث النفس، (٢) لا تُبنى القرارات المصيرية على الرؤى." | |
| الحلم: \"\"\"{user_text.strip()}\"\"\" | |
| المطلوب: | |
| - 3-5 نقاط تفسير محتمَلة. | |
| - نصيحة عملية خفيفة للمسترشد. | |
| - ختم: "التفسير اجتهاد وظن، والعلم عند الله". | |
| """) | |
| def interpret_dream(user_text: str) -> str: | |
| clean = normalize_ar(user_text) | |
| hits = match_rules(clean) | |
| pieces = [] | |
| if hits: | |
| # جمع تفسيرات القاموس | |
| for k in hits: | |
| pieces.append(f"• {RULES[k]}") | |
| # إن توفر Ollama نستخدمه كتعزيز | |
| ai_part = ask_ollama(build_prompt(user_text)) | |
| if ai_part: | |
| pieces.append("\n— تفسير مُعزَّز بالذكاء المحلي —\n" + ai_part.strip()) | |
| if not pieces: | |
| # fallback بسيط لو ما في قواعد ولا Ollama | |
| pieces = [ | |
| "• قد يعكس الحلم مشاعرك الراهنة أو قلقاً يومياً.", | |
| "• رموز الحلم تُقرأ بالسياق: حالتك النفسية، الزمان، والأحداث الأخيرة.", | |
| "• اطلب لنفسك الخير وتحرّك بخطوة واقعية تناسبك.", | |
| ] | |
| pieces.append("\nالتنبيه: التفسير اجتهاد وظنّ، والعلم عند الله.") | |
| return "\n".join(pieces) | |
| # ============ واجهة تيليجرام ============ | |
| HELP_TEXT = ( | |
| "أهلاً بك في بوت تفسير الأحلام 🌙\n\n" | |
| "• أرسل حلمك بنص واضح وسأعطيك تفسيراً محتملاً.\n" | |
| f"• لديك {FREE_QUOTA} تفسيرات مجانية. بعدها تحتاج ترقية لحساب بريميوم.\n" | |
| "• رجاءً تجنب الألفاظ الخارجة، ولا تتخذ قرارات مصيرية بناءً على الرؤى فقط." | |
| ) | |
| PRICING_TEXT = ( | |
| "الترقية إلى بريميوم تمنحك عدد تفسيرات غير محدود + أولوية بالرد.\n" | |
| "السعر المقترح: 5$ شهرياً (يمكن تعديله). حالياً الدفع يدوي/تحويل.\n" | |
| "راسل الإدارة لإتمام الترقية." | |
| ) | |
| async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| await update.message.reply_text( | |
| "مرحبا! أنا بوت تفسير الأحلام 🌙\n\n" + HELP_TEXT, | |
| reply_markup=InlineKeyboardMarkup([ | |
| [InlineKeyboardButton("الترقية إلى بريميوم ⭐", callback_data="upgrade")], | |
| [InlineKeyboardButton("طريقة الاستخدام", callback_data="help")] | |
| ]) | |
| ) | |
| async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| await update.message.reply_text(HELP_TEXT) | |
| async def upgrade_button(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| q = update.callback_query | |
| await q.answer() | |
| if q.data == "help": | |
| await q.edit_message_text(HELP_TEXT) | |
| return | |
| if q.data == "upgrade": | |
| kb = InlineKeyboardMarkup([ | |
| [InlineKeyboardButton("راسل الدعم للترقية", url=f"tg://user?id={OWNER_ID}")] if OWNER_ID else [] | |
| ]) | |
| await q.edit_message_text(PRICING_TEXT, reply_markup=kb if OWNER_ID else None) | |
| async def set_premium_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| # أمر إداري: /setpremium <user_id> <0|1> | |
| if update.effective_user.id != OWNER_ID: | |
| return | |
| try: | |
| _, uid, val = update.message.text.split(maxsplit=2) | |
| set_premium(int(uid), int(val)) | |
| await update.message.reply_text("تم التحديث.") | |
| except Exception as e: | |
| await update.message.reply_text("استخدم: /setpremium <user_id> <0|1>") | |
| async def stats_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| if update.effective_user.id != OWNER_ID: | |
| return | |
| cur = CON.execute("SELECT COUNT(*), SUM(used_count), SUM(premium) FROM users") | |
| c, uses, pre = cur.fetchone() | |
| await update.message.reply_text(f"Users: {c}\nTotal Uses: {uses or 0}\nPremium: {pre or 0}") | |
| async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): | |
| user = update.effective_user | |
| text = (update.message.text or "").strip() | |
| if not text: | |
| return | |
| # Anti-spam | |
| wait_s = too_soon(user.id) | |
| if wait_s > 0: | |
| await update.message.reply_text(f"من فضلك انتظر {wait_s} ث قبل طلب تفسير آخر 🙏") | |
| return | |
| u = get_user(user.id) | |
| is_premium = (u[3] == 1) | |
| remain = remaining_quota(user.id) | |
| if not is_premium and remain <= 0: | |
| btn = InlineKeyboardMarkup([[InlineKeyboardButton("الترقية إلى بريميوم ⭐", callback_data="upgrade")]]) | |
| await update.message.reply_text( | |
| "انتهت حصتك المجانية من التفسيرات.\nيمكنك الترقية لمواصلة الاستخدام بلا حدود.", | |
| reply_markup=btn | |
| ) | |
| return | |
| # تفسير | |
| reply = interpret_dream(text) | |
| inc_usage(user.id) | |
| footer = f"\n\n(المتبقي مجانًا: {remaining_quota(user.id)})" if not is_premium else "\n\n(حساب بريميوم ✅)" | |
| await update.message.reply_text(reply + footer) | |
| def main(): | |
| application = Application.builder().token(BOT_TOKEN).build() | |
| application.add_handler(CommandHandler("start", start)) | |
| application.add_handler(CommandHandler("help", help_cmd)) | |
| application.add_handler(CommandHandler("setpremium", set_premium_cmd)) | |
| application.add_handler(CommandHandler("stats", stats_cmd)) | |
| application.add_handler(CallbackQueryHandler(upgrade_button)) | |
| application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) | |
| application.run_polling(allowed_updates=Update.ALL_TYPES) | |
| if __name__ == "__main__": | |
| main() | |