File size: 10,564 Bytes
7212614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# brain.py — PTB v13.15 + Flask (no .env, hardcoded token)
# -*- coding: utf-8 -*-
import sys, socket
import os
import json
import logging
import threading
from difflib import get_close_matches
from urllib.parse import urlparse

from flask import Flask, request, render_template, session, redirect, jsonify

# ===== App config (hardcoded token) =====
BOT_TOKEN = "000000000:TEST_TOKEN_PLACEHOLDER"  # ← ضع توكنك الحقيقي هنا إن رغبت
APP_HOST = "0.0.0.0"
APP_PORT = 7530
SECRET_KEY = "noura-super-secret"

# ===== Optional internal modules (loaded defensively) =====
try:
    import responses
except Exception:
    responses = None

try:
    import analyzer
except Exception:
    analyzer = None

try:
    import media_analyzer
except Exception:
    media_analyzer = None

# ===== Memory API (preferred) =====
try:
    from memory import (
        load_memory as mem_load,
        save_memory as mem_save,
        load_global_memory as mem_load_global,
        save_global_memory as mem_save_global,
    )
except Exception:
    # Fallback minimal memory (local JSON files) if memory.py not available
    def _mf_user(username: str) -> str:
        return f"memory_{username}.json"

    def mem_load(username: str):
        f = _mf_user(username)
        return json.load(open(f, encoding="utf-8")) if os.path.exists(f) else {}

    def mem_save(username: str, data: dict):
        with open(_mf_user(username), "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

    def mem_load_global():
        return json.load(open("global_memory.json", encoding="utf-8")) if os.path.exists("global_memory.json") else {}

    def mem_save_global(data: dict):
        with open("global_memory.json", "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

# ===== Telegram v13.15 imports (sync API) =====
from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import MessageHandler, filters, CallbackContext

# ===== Logging =====
logging.basicConfig(
    format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
    level=logging.INFO,
)
log = logging.getLogger("brain")

# ===== Flask =====
app = Flask(__name__)
app.secret_key = SECRET_KEY

# ===== Helpers =====
def fix_url(url: str) -> str:
    url = (url or "").strip()
    if not url:
        return url
    parsed = urlparse(url)
    if not parsed.scheme:
        if url.startswith("//"):
            return "https:" + url
        return "https://" + url
    return url

def detect_media_type(url: str) -> str:
    u = (url or "").lower()
    if u.endswith((".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp")):
        return "image"
    if u.endswith((".mp4", ".mov", ".avi", ".mkv", ".webm")):
        return "video"
    if u.endswith((".mp3", ".wav", ".ogg", ".m4a", ".flac")):
        return "audio"
    return "link"

def analyze_url_type_safe(url: str) -> str:
    if analyzer and hasattr(analyzer, "analyze_url_type"):
        try:
            return analyzer.analyze_url_type(url)
        except Exception as e:
            log.warning("analyze_url_type failed: %s", e)
    return "unknown"

def call_responses_generate(text: str, **kw) -> str:
    if responses and hasattr(responses, "generate_reply"):
        try:
            return responses.generate_reply(text, **kw) or ""
        except Exception as e:
            log.warning("responses.generate_reply failed: %s", e)
    return ""

def call_media_image(url: str) -> str:
    if media_analyzer and hasattr(media_analyzer, "analyze_image_from_url"):
        try:
            return media_analyzer.analyze_image_from_url(url)
        except Exception as e:
            log.warning("analyze_image_from_url failed: %s", e)
    return "تم استلام الصورة (معالج الصور غير متاح)."

def call_media_video(url: str) -> str:
    if media_analyzer and hasattr(media_analyzer, "analyze_video_from_url"):
        try:
            return media_analyzer.analyze_video_from_url(url)
        except Exception as e:
            log.warning("analyze_video_from_url failed: %s", e)
    return "تم استلام الفيديو (معالج الفيديو غير متاح)."

def call_media_audio(url: str) -> str:
    if media_analyzer and hasattr(media_analyzer, "analyze_audio_from_url"):
        try:
            return media_analyzer.analyze_audio_from_url(url)
        except Exception as e:
            log.warning("analyze_audio_from_url failed: %s", e)
    return "تم استلام الصوت (معالج الصوت غير متاح)."

# ===== Core reply =====
def generate_reply(message: str, username: str = "مجهول") -> str:
    user_mem = mem_load(username)
    global_mem = mem_load_global()

    # 1) exact hit in user memory
    if message in user_mem:
        return user_mem[message]

    # 2) fuzzy search in global memory
    gm_keys = list(global_mem.keys())
    if gm_keys:
        m = get_close_matches(message, gm_keys, n=1, cutoff=0.6)
        if m:
            return global_mem[m[0]]

    # 3) URLs / media
    fixed = fix_url(message)
    reply = ""
    if fixed.startswith("http://") or fixed.startswith("https://"):
        mtype = detect_media_type(fixed)
        if mtype == "image":
            reply = f"تحليل الصورة:\n{call_media_image(fixed)}"
        elif mtype == "video":
            reply = f"تحليل الفيديو:\n{call_media_video(fixed)}"
        elif mtype == "audio":
            reply = f"تحليل الصوت:\n{call_media_audio(fixed)}"
        else:
            kind = analyze_url_type_safe(fixed)
            reply = f"الرابط من نوع: {kind}"
    else:
        # 4) use responses module if present, else fallback
        alt = call_responses_generate(message, analysis={}) or ""
        reply = alt if alt else f"رد تلقائي: {message[::-1]}"

    # 5) persist
    user_mem[message] = reply
    global_mem[message] = reply
    mem_save(username, user_mem)
    mem_save_global(global_mem)

    return reply

# ===== Telegram Handlers (v13 sync) =====
def tg_send_action(update: Update, context: CallbackContext, action: ChatAction):
    try:
        context.bot.send_chat_action(chat_id=update.effective_chat.id, action=action)
    except Exception:
        pass

def handle_text(update: Update, context: CallbackContext):
    if not update.message:
        return
    text = update.message.text or ""
    tg_send_action(update, context, ChatAction.TYPING)
    try:
        resp = generate_reply(text, username=str(update.effective_user.id) if update.effective_user else "مجهول")
        update.message.reply_text(resp)
    except Exception as e:
        log.exception("Text handler error: %s", e)
        update.message.reply_text("حدث خطأ أثناء معالجة الرسالة.")

def handle_photo(update: Update, context: CallbackContext):
    if not update.message or not update.message.photo:
        return
    tg_send_action(update, context, ChatAction.UPLOAD_PHOTO)
    try:
        file = update.message.photo[-1].get_file()
        url = file.file_path  # Telegram CDN URL
        resp = call_media_image(url)
        update.message.reply_text(resp)
    except Exception as e:
        log.exception("Photo handler error: %s", e)
        update.message.reply_text("تم استلام الصورة.")

def handle_video(update: Update, context: CallbackContext):
    if not update.message or not update.message.video:
        return
    tg_send_action(update, context, ChatAction.UPLOAD_VIDEO)
    try:
        file = update.message.video.get_file()
        url = file.file_path
        resp = call_media_video(url)
        update.message.reply_text(resp)
    except Exception as e:
        log.exception("Video handler error: %s", e)
        update.message.reply_text("تم استلام الفيديو.")

def handle_audio(update: Update, context: CallbackContext):
    if not update.message or not (update.message.audio or update.message.voice):
        return
    tg_send_action(update, context, ChatAction.RECORD_AUDIO)
    try:
        fobj = update.message.audio or update.message.voice
        file = fobj.get_file()
        url = file.file_path
        resp = call_media_audio(url)
        update.message.reply_text(resp)
    except Exception as e:
        log.exception("Audio handler error: %s", e)
        update.message.reply_text("تم استلام الصوت.")

def _run_tg_updater():
    updater = Updater(BOT_TOKEN, use_context=True)
    dp = updater.dispatcher

    dp.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))
    dp.add_handler(MessageHandler(filters.PHOTO, handle_photo))
    dp.add_handler(MessageHandler(filters.VIDEO, handle_video))
    dp.add_handler(MessageHandler(filters.AUDIO | filters.VOICE, handle_audio))

    log.info("Telegram bot running (PTB v13.15)…")
    updater.start_polling()
    updater.idle()

def run_telegram_bot_thread():
    t = threading.Thread(target=_run_tg_updater, daemon=True)
    t.start()

# ===== Flask routes =====
@app.route("/")
def home():
    return render_template("login.html") if os.path.exists("templates/login.html") else "OK"

@app.route("/chat", methods=["GET", "POST"])
def chat():
    if request.method == "POST":
        session["username"] = request.form.get("username", "مجهول")
        return render_template("index.html", username=session["username"]) if os.path.exists("templates/index.html") else f"Hello {session['username']}"
    if "username" in session:
        return render_template("index.html", username=session["username"]) if os.path.exists("templates/index.html") else f"Hello {session['username']}"
    return redirect("/")

@app.route("/api", methods=["POST"])
def api():
    data = request.json or {}
    username = data.get("username", "مجهول")
    message = data.get("message", "")
    return jsonify({"reply": generate_reply(message, username)})

@app.route("/memory")
def view_memory():
    if "username" not in session:
        return redirect("/")
    memory = mem_load(session["username"])
    return render_template("memory.html", username=session["username"], memory=memory) if os.path.exists("templates/memory.html") else jsonify(memory)

# ===== Main =====
if __name__ == "__main__":
    # شغّل بوت تيليجرام في Thread منفصل
    run_telegram_bot_thread()

    # شغّل Flask على 0.0.0.0:APP_PORT
    print(f"[brain] Flask listening on {APP_HOST}:{APP_PORT} (token set={bool(BOT_TOKEN)})")
    app.run(host=APP_HOST, port=APP_PORT)