nouradreams / dream_bot.py
osamabyc86's picture
Upload 4 files
b0c0602 verified
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()