Mustafa-albakkar commited on
Commit
c0639e3
·
verified ·
1 Parent(s): 7630b2c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -118
app.py CHANGED
@@ -1,16 +1,13 @@
1
  # ============================================================
2
- # analyzer_agent_gradio/app.py — Telegram Analyzer Agent (async)
3
- # Mamba + GGUF LLM + Pyrogram + Gradio
4
  # ============================================================
5
- # ملاحظة: سيعمل المجدول (Scheduler) كعملية خلفية متزامنة
6
- # ولكن دوال Gradio ستكون واجهات الوصول.
7
 
8
  import os
9
  import json
10
  import asyncio
11
  from datetime import datetime
12
  import logging
13
- import threading
14
  from functools import wraps
15
  from typing import List, Dict, Any, Optional
16
 
@@ -18,10 +15,10 @@ import gradio as gr
18
  from apscheduler.schedulers.background import BackgroundScheduler
19
  scheduler = BackgroundScheduler()
20
 
21
- # LLM & Pyrogram dependencies
22
  import torch
23
  from transformers import AutoTokenizer, AutoModelForCausalLM
24
- from pyrogram import Client, errors # تم إضافة errors
25
  from llama_cpp import Llama
26
  from huggingface_hub import hf_hub_download
27
 
@@ -32,30 +29,28 @@ log = logging.getLogger("analyzer")
32
 
33
 
34
  # ---------------- Env & config ----------------
35
- # التأكد من وجود البيانات وعدم استخدام قيم افتراضية غير صالحة للـ API
36
- TG_API_ID = int(os.getenv("TG_API_ID", 0))
37
- TG_API_HASH = os.getenv("TG_API_HASH", "")
38
  TG_BOT_TOKEN = os.getenv("TG_BOT_TOKEN")
39
- TG_CHANNEL = os.getenv("TG_CHANNEL")
 
40
  LOG_PATH = os.getenv("ANALYZER_LOG", "analyzer_log.json")
41
- POSTS_LIMIT = int(os.getenv("ANALYZER_LIMIT", "80"))
 
42
 
43
  MAMBA_MODEL_PATH = os.getenv("MAMBA_MODEL_PATH", "state-spaces/mamba-1.4b-hf")
44
 
45
  # ---------------- Initialization Check ----------------
46
- if not all([TG_API_ID, TG_API_HASH, TG_BOT_TOKEN, TG_CHANNEL]):
47
- log.error("Telegram API credentials or Channel ID are missing in environment variables.")
48
- # سنسمح بالتشغيل لكن وظائف Pyrogram ستفشل
49
  IS_SERVICE_READY = False
50
- STATUS_MESSAGE = "❌ النظام غير جاهز: بيانات اعتماد Telegram مفقودة."
51
  else:
52
  IS_SERVICE_READY = True
53
- STATUS_MESSAGE = "✅ النظام جاهز للتحليل."
54
 
55
 
56
  # ---------------- Helpers for Non-Async Blocking Operations ----------------
57
- # استخدام asyncio.to_thread بديلًا لـ ThreadPoolExecutor والديكور @async_run_in_thread
58
- # لأنها الطريقة الأفضل والأكثر حداثة لدمج الدوال المتزامنة مع AsyncIO.
59
  def async_wrap_blocking(func):
60
  """Wraps a synchronous function to be run in a separate thread."""
61
  @wraps(func)
@@ -64,6 +59,8 @@ def async_wrap_blocking(func):
64
  return await asyncio.to_thread(func, *args, **kwargs)
65
  return wrapper
66
 
 
 
67
  # ---------------- Load Mamba ----------------
68
  log.info("Loading Mamba model...")
69
  try:
@@ -109,10 +106,18 @@ except Exception as e:
109
  STATUS_MESSAGE = "❌ فشل تحميل نموذج GGUF."
110
 
111
 
112
- # ---------------- Pyrogram Client ----------------
113
- # يتم تعريف العميل هنا
114
- tg_client = Client("analyzer_pyrogram_session", api_id=TG_API_ID, api_hash=TG_API_HASH, bot_token=TG_BOT_TOKEN)
115
-
 
 
 
 
 
 
 
 
116
 
117
  # ---------------- Core Helpers ----------------
118
  def save_log(entry: Dict[str, Any]):
@@ -129,21 +134,29 @@ def save_log(entry: Dict[str, Any]):
129
  json.dump(logs, f, ensure_ascii=False, indent=2)
130
 
131
  def encode_stats_for_mamba(posts: List[Dict[str, Any]]) -> str:
132
- """Encodes the list of posts into a single string for Mamba analysis."""
 
 
133
  lines = []
134
  for p in posts:
135
- # تبسيط الترميز لتجنب المشاكل في المدخلات الطويلة
136
- line = (
137
- f"ID:{p['id']} | Date:{p['date'][:10]} | Type:{p['message_type']} | "
138
- f"Views:{p['views']} | Fwds:{p['forwards']} | Reacts:{p['reactions_total']}"
139
- )
140
- lines.append(line)
 
 
 
 
 
 
141
  return "\n".join(lines)
142
 
143
 
144
  @async_wrap_blocking
145
  def run_mamba(text: str) -> str:
146
- """Synchronous Mamba generation function."""
147
  if mamba_model is None or mamba_tok is None:
148
  return "Error: Mamba model not loaded."
149
 
@@ -154,20 +167,19 @@ def run_mamba(text: str) -> str:
154
 
155
  @async_wrap_blocking
156
  def interpret_with_llm(mamba_output: str) -> str:
157
- """Synchronous LLM interpretation function using llama.cpp."""
158
  if llm is None:
159
  return "Error: LLM model not loaded."
160
 
161
  prompt = (
162
- "هذه نتائج تحليل إحصائي لقناة تلغرام:\n"
163
  f"{mamba_output}\n\n"
164
- "حلل الأداء واستخرج:\n"
165
- "- نقاط القوة\n"
166
- "- نقاط الضعف\n"
167
- "- أفضل أوقات النشر المتوقعة\n"
168
- "- نوع المحتوى الذي يرفع الوصول\n"
169
  "- استراتيجيات لزيادة الاشتراكات والتفاعل\n"
170
- "اكتب التحليل بالعربية وبشكل مرتب ومختصر."
171
  )
172
 
173
  res = llm(
@@ -180,74 +192,72 @@ def interpret_with_llm(mamba_output: str) -> str:
180
  return res["choices"][0]["text"].strip()
181
 
182
 
183
- # ---------------- Fetch Telegram Stats ----------------
184
- async def fetch_telegram_stats(limit: int = POSTS_LIMIT) -> List[Dict[str, Any]]:
185
- """Fetches post statistics from Telegram asynchronously."""
186
- if not IS_SERVICE_READY:
187
- raise RuntimeError(STATUS_MESSAGE)
 
188
 
189
- posts = []
190
  try:
191
- # Pyrogram client management
192
- await tg_client.start()
193
 
194
- async for msg in tg_client.get_chat_history(TG_CHANNEL, limit=limit):
195
- if msg is None:
196
- continue
197
-
198
- views = getattr(msg, "views", 0) or 0
199
- forwards = getattr(msg, "forwards", 0) or 0
200
- reactions_total = 0
201
- reactions_breakdown = {}
202
-
203
- if getattr(msg, "reactions", None):
204
- try:
205
- for r in msg.reactions.reactions:
206
- reactions_total += r.count
207
- reactions_breakdown[r.emoji] = r.count
208
- except Exception:
209
- pass
210
-
211
- edited_date = msg.edit_date.isoformat() if getattr(msg, "edit_date", None) else None
212
- has_media = bool(msg.media)
213
- message_type = msg.media.value if msg.media else 'text'
214
-
215
- posts.append({
216
- "id": msg.message_id if hasattr(msg, "message_id") else msg.id,
217
- "date": msg.date.isoformat() if getattr(msg, "date", None) else None,
218
- "edited_date": edited_date,
219
- "views": views,
220
- "forwards": forwards,
221
- "reactions_total": reactions_total,
222
- "reactions_breakdown": reactions_breakdown,
223
- "has_media": has_media,
224
- "message_type": message_type
225
- })
226
-
227
- await tg_client.stop()
228
- log.info(f"Successfully fetched {len(posts)} posts from Telegram.")
229
- return posts
230
-
231
- except errors.AuthKeyUnregistered:
232
- log.error("Pyrogram Auth Error: The session is invalid. Check TG_BOT_TOKEN.")
233
- if tg_client.is_running: await tg_client.stop()
234
  raise RuntimeError("فشل مصادقة Telegram: تأكد من صحة TG_BOT_TOKEN.")
235
- except errors.ChannelInvalid:
236
- log.error(f"Pyrogram Error: Channel {TG_CHANNEL} is invalid or inaccessible.")
237
- if tg_client.is_running: await tg_client.stop()
238
- raise RuntimeError(f"قناة Telegram غير صالحة: {TG_CHANNEL}.")
239
  except Exception as e:
240
  log.error(f"An unexpected error occurred during Telegram fetch: {e}")
241
- if tg_client.is_running: await tg_client.stop()
242
  raise RuntimeError(f"خطأ غير متوقع في Telegram: {e}")
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  # ---------------- Main Analysis Pipeline ----------------
 
 
246
  async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
247
  """Runs the full analysis pipeline."""
248
  log.info("Running manual analysis job...")
249
 
250
- # 1. جلب البيانات
251
  try:
252
  posts = await fetch_telegram_stats(limit=limit)
253
  except RuntimeError as e:
@@ -258,10 +268,11 @@ async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
258
  return {"status": "error", "message": error_detail, "log_entry": entry}
259
 
260
  if not posts:
261
- log.warning("No posts found for analysis.")
262
- entry = {"time": datetime.utcnow().isoformat(), "error": "no_posts"}
 
263
  save_log(entry)
264
- return {"status": "warning", "message": "لم يتم العثور على منشورات للتحليل.", "log_entry": entry}
265
 
266
  # 2. تشغيل Mamba
267
  stats_text = encode_stats_for_mamba(posts)
@@ -272,10 +283,9 @@ async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
272
 
273
  entry = {
274
  "time": datetime.utcnow().isoformat(),
275
- "posts_count": len(posts),
276
- # في Gradio، قد لا نريد عرض الحقول الكبيرة
277
- # "stats_text": stats_text,
278
- # "mamba_output": mamba_out,
279
  "advice": interpretation
280
  }
281
 
@@ -285,7 +295,8 @@ async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
285
  # تنسيق الخرج لـ Gradio
286
  output_message = (
287
  f"**✅ اكتمل التحليل بنجاح!**\n\n"
288
- f"**عدد المنشورات التي تم تحليلها:** {len(posts)}\n"
 
289
  f"**وقت التحليل:** {entry['time']}\n\n"
290
  f"--- **توصيات الذكاء الاصطناعي** ---\n"
291
  f"{interpretation}\n\n"
@@ -296,12 +307,13 @@ async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
296
  return {"status": "success", "message": output_message, "log_entry": entry}
297
 
298
 
299
- # ---------------- Gradio Interface Functions ----------------
 
 
300
 
301
  async def gradio_run_once(limit: int) -> str:
302
  """Gradio function to run the analysis manually."""
303
 
304
- # Gradio يعرض رسالة التحميل (Loading) تلقائياً، لذلك لا حاجة لرسالة "جاري التشغيل"
305
  result = await run_analysis_pipeline(limit=limit)
306
 
307
  if result.get("status") == "success":
@@ -319,14 +331,13 @@ def gradio_get_logs() -> str:
319
  if not logs:
320
  return "لا توجد سجلات تحليل متاحة."
321
 
322
- # تنسيق السجلات لعرضها في Gradio Markdown
323
  output = "## 📄 سجلات التحليل الأخيرة\n"
324
  for i, entry in enumerate(logs):
325
  output += f"### {i+1}. تحليل بتاريخ {entry.get('time', 'N/A')}\n"
326
  if entry.get('error'):
327
  output += f"**❌ خطأ:** {entry['error']}\n"
328
  else:
329
- output += f"**✅ العدد:** {entry.get('posts_count', 0)}\n"
330
  output += f"**💡 النصيحة:**\n```\n{entry.get('advice', 'N/A')[:300]}...\n```\n"
331
  output += "---\n"
332
 
@@ -336,10 +347,7 @@ def gradio_get_logs() -> str:
336
  def daily_job_wrapper():
337
  """Synchronous wrapper to run the async job in the scheduler."""
338
  log.info("Running scheduled analysis job via Gradio wrapper...")
339
- # يجب أن يتم تشغيل الدالة async داخل حلقة أحداث (Event loop) منفصلة أو قائمة.
340
- # بما أننا نستخدم BackgroundScheduler، يجب استخدام طريقة لتشغيل async داخل thread متزامن.
341
  try:
342
- # يتم إنشاء حلقة أحداث جديدة خصيصًا لهذه المهمة المجدولة
343
  loop = asyncio.new_event_loop()
344
  asyncio.set_event_loop(loop)
345
  result = loop.run_until_complete(run_analysis_pipeline())
@@ -347,11 +355,9 @@ def daily_job_wrapper():
347
  except Exception as e:
348
  log.error(f"Error in scheduled job: {e}")
349
  finally:
350
- # إغلاق الحلقة
351
  loop.close()
352
 
353
  if IS_SERVICE_READY:
354
- # إضافة المهمة المجدولة: التشغيل كل يوم في الساعة 00:05 UTC (أو حسب التوقيت المحلي للخادم)
355
  scheduler.add_job(daily_job_wrapper, "cron", hour=13, minute=5)
356
  scheduler.start()
357
  log.info("Scheduler started for daily analysis.")
@@ -361,24 +367,25 @@ else:
361
  # ---------------- Gradio Interface ----------------
362
 
363
  with gr.Blocks(title="Telegram Channel Analyzer Agent") as demo:
364
- gr.Markdown("# 🤖 وكيل تحليل قناة Telegram (Mamba & LLama.cpp)")
365
  gr.Markdown(f"**حالة الخدمة:** {STATUS_MESSAGE}")
 
366
  gr.Markdown("---")
367
 
368
  with gr.Tab("تشغيل التحليل يدوياً"):
369
- gr.Markdown(f"## ⚙️ تشغيل لمرة واحدة - القناة: `{TG_CHANNEL}`")
 
370
  limit_input = gr.Slider(
371
  minimum=10,
372
  maximum=300,
373
  step=10,
374
  value=POSTS_LIMIT,
375
- label="الحد الأقصى لعدد المنشورات المراد تحليلها"
376
  )
377
- run_button = gr.Button("🚀 بدء التحليل الآن")
378
 
379
  output_textbox = gr.Markdown(label="نتيجة التحليل")
380
 
381
- # ربط الدالة بزر Gradio
382
  run_button.click(
383
  fn=gradio_run_once,
384
  inputs=[limit_input],
@@ -395,7 +402,6 @@ with gr.Blocks(title="Telegram Channel Analyzer Agent") as demo:
395
  inputs=[],
396
  outputs=[log_output]
397
  )
398
- # تحميل السجلات عند فتح التبويب
399
  demo.load(
400
  fn=gradio_get_logs,
401
  inputs=[],
@@ -405,7 +411,5 @@ with gr.Blocks(title="Telegram Channel Analyzer Agent") as demo:
405
  # ---------------- Main Entry Point ----------------
406
  if __name__ == "__main__":
407
  log.info("Starting Analyzer Agent Gradio Interface...")
408
- # Gradio يستخدم عادة المنفذ 7860 افتراضيًا
409
  PORT = int(os.getenv("PORT", "7860"))
410
- # `share=True` لإنشاء رابط عام مؤقت (للإختبار فقط)
411
  demo.launch(server_name="0.0.0.0", server_port=PORT)
 
1
  # ============================================================
2
+ # analyzer_agent_gradio/app.py — Telegram Analyzer Agent (Bot API)
3
+ # Mamba + GGUF LLM + python-telegram-bot + Gradio
4
  # ============================================================
 
 
5
 
6
  import os
7
  import json
8
  import asyncio
9
  from datetime import datetime
10
  import logging
 
11
  from functools import wraps
12
  from typing import List, Dict, Any, Optional
13
 
 
15
  from apscheduler.schedulers.background import BackgroundScheduler
16
  scheduler = BackgroundScheduler()
17
 
18
+ # LLM & telegram dependencies
19
  import torch
20
  from transformers import AutoTokenizer, AutoModelForCausalLM
21
+ import telegram # <<< التغيير الرئيسي: استخدام مكتبة Telegram
22
  from llama_cpp import Llama
23
  from huggingface_hub import hf_hub_download
24
 
 
29
 
30
 
31
  # ---------------- Env & config ----------------
32
+ TG_API_ID = int(os.getenv("TG_API_ID", 0)) # لم تعد مستخدمة
33
+ TG_API_HASH = os.getenv("TG_API_HASH", "") # لم تعد مستخدمة
 
34
  TG_BOT_TOKEN = os.getenv("TG_BOT_TOKEN")
35
+ # TG_CHANNEL يجب أن تكون معرف القناة (مثل @ChannelUsername)
36
+ TG_CHANNEL = os.getenv("TG_CHANNEL")
37
  LOG_PATH = os.getenv("ANALYZER_LOG", "analyzer_log.json")
38
+ # ملاحظة: حد المنشورات لم يعد عمليًا مع Bot API لأنه لا يمكن جلب التاريخ
39
+ POSTS_LIMIT = int(os.getenv("ANALYZER_LIMIT", "80"))
40
 
41
  MAMBA_MODEL_PATH = os.getenv("MAMBA_MODEL_PATH", "state-spaces/mamba-1.4b-hf")
42
 
43
  # ---------------- Initialization Check ----------------
44
+ if not all([TG_BOT_TOKEN, TG_CHANNEL]):
45
+ log.error("Telegram Bot Token or Channel ID are missing in environment variables.")
 
46
  IS_SERVICE_READY = False
47
+ STATUS_MESSAGE = "❌ النظام غير جاهز: بيانات اعتماد البوت أو معرف القناة مفقودة."
48
  else:
49
  IS_SERVICE_READY = True
50
+ STATUS_MESSAGE = "✅ النظام جاهز للتحليل (Bot API)."
51
 
52
 
53
  # ---------------- Helpers for Non-Async Blocking Operations ----------------
 
 
54
  def async_wrap_blocking(func):
55
  """Wraps a synchronous function to be run in a separate thread."""
56
  @wraps(func)
 
59
  return await asyncio.to_thread(func, *args, **kwargs)
60
  return wrapper
61
 
62
+ # ... (بقية تعريفات وتحميل نماذج Mamba و LLM كما هي) ...
63
+
64
  # ---------------- Load Mamba ----------------
65
  log.info("Loading Mamba model...")
66
  try:
 
106
  STATUS_MESSAGE = "❌ فشل تحميل نموذج GGUF."
107
 
108
 
109
+ # ---------------- Telegram Bot Client ----------------
110
+ # تعريف البوت
111
+ if IS_SERVICE_READY:
112
+ try:
113
+ # إنشاء كائن البوت (متزامن)
114
+ bot = telegram.Bot(token=TG_BOT_TOKEN)
115
+ except Exception as e:
116
+ log.error(f"Failed to initialize Telegram Bot: {e}")
117
+ IS_SERVICE_READY = False
118
+ STATUS_MESSAGE = "❌ فشل تهيئة كائن البوت."
119
+ else:
120
+ bot = None
121
 
122
  # ---------------- Core Helpers ----------------
123
  def save_log(entry: Dict[str, Any]):
 
134
  json.dump(logs, f, ensure_ascii=False, indent=2)
135
 
136
  def encode_stats_for_mamba(posts: List[Dict[str, Any]]) -> str:
137
+ """Encodes the list of posts into a single string for Mamba analysis.
138
+ (تعديل بسيط ليناسب بيانات Bot API المحدودة)
139
+ """
140
  lines = []
141
  for p in posts:
142
+ # البيانات المتاحة من getChat هي عدد الأعضاء، وهذه إحصائية واحدة وليست بيانات منشورات متسلسلة.
143
+ # سنستخدم هذه الدالة لتحليل بيانات القناة الأساسية إذا تم استدعاؤها بهذا الشكل.
144
+ if p.get('chat_id'): # بيانات القناة
145
+ line = (
146
+ f"Channel Name:{p.get('title')} | Members:{p.get('members')} | "
147
+ f"Admins:{p.get('admins_count')}"
148
+ )
149
+ lines.append(line)
150
+ break # تحليل إحصائية القناة مرة واحدة فقط
151
+ # يمكنك إضافة تحليل التفاعلات إذا استخدمت طريقة Webhook لتخزينها
152
+ if not lines:
153
+ return "No sufficient data for sequential analysis. Analyzing overall channel status."
154
  return "\n".join(lines)
155
 
156
 
157
  @async_wrap_blocking
158
  def run_mamba(text: str) -> str:
159
+ # ... (دالة Mamba تبقى كما هي) ...
160
  if mamba_model is None or mamba_tok is None:
161
  return "Error: Mamba model not loaded."
162
 
 
167
 
168
  @async_wrap_blocking
169
  def interpret_with_llm(mamba_output: str) -> str:
170
+ # ... (دالة LLM تبقى كما هي، لكن مع تعديل بسيط في الوصف ليتناسب مع التحليل الجديد) ...
171
  if llm is None:
172
  return "Error: LLM model not loaded."
173
 
174
  prompt = (
175
+ "هذه نتائج تحليل إحصائي لقناة تلغرام (معلومات عامة فقط):\n"
176
  f"{mamba_output}\n\n"
177
+ "حلل أداء القناة الأساسي (عدد الأعضاء والمشرفين).\n"
178
+ "استخرج:\n"
179
+ "- نقاط القوة (مثل عدد الأعضاء)\n"
180
+ "- نقاط الضعف (مثل نقص البيانات الزمنية أو التفاعلات)\n"
 
181
  "- استراتيجيات لزيادة الاشتراكات والتفاعل\n"
182
+ "اكتب التحليل بالعربية وبشكل مرتب ومختصر، مع ذكر القيود على البيانات المتاحة."
183
  )
184
 
185
  res = llm(
 
192
  return res["choices"][0]["text"].strip()
193
 
194
 
195
+ # ---------------- Fetch Telegram Stats (Bot API) ----------------
196
+ # دالة متزامنة لتنفيذها داخل async_to_thread
197
+ def _fetch_channel_info_sync() -> Dict[str, Any]:
198
+ """Synchronous function to fetch basic channel stats using Bot API."""
199
+ if bot is None:
200
+ raise RuntimeError("Telegram Bot is not initialized.")
201
 
 
202
  try:
203
+ # 1. جلب معلومات القناة الأساسية
204
+ chat_info = bot.get_chat(chat_id=TG_CHANNEL)
205
 
206
+ # 2. جلب المشرفين لتقدير النشاط الإداري
207
+ admins = bot.get_chat_administrators(chat_id=TG_CHANNEL)
208
+ admins_count = len(admins)
209
+
210
+ # تحويل البيانات إلى تنسيق موحد للتحليل
211
+ stats = {
212
+ "chat_id": chat_info.id,
213
+ "title": chat_info.title,
214
+ # Bot API يعطي عدد الأعضاء (members_count)
215
+ "members": chat_info.members_count,
216
+ "admins_count": admins_count,
217
+ "description": chat_info.description,
218
+ "date": datetime.utcnow().isoformat(),
219
+ }
220
+
221
+ return stats
222
+
223
+ except telegram.error.Unauthorized:
224
+ log.error("Telegram Auth Error: The Bot Token is invalid.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  raise RuntimeError("فشل مصادقة Telegram: تأكد من صحة TG_BOT_TOKEN.")
226
+ except telegram.error.BadRequest as e:
227
+ log.error(f"Telegram Bot API Error: {e}")
228
+ # قد يحدث هذا إذا كانت القناة خاصة والبوت غير مضاف
229
+ raise RuntimeError(f"قناة Telegram غير صالحة أو البوت غير مضاف: {TG_CHANNEL}.")
230
  except Exception as e:
231
  log.error(f"An unexpected error occurred during Telegram fetch: {e}")
 
232
  raise RuntimeError(f"خطأ غير متوقع في Telegram: {e}")
233
 
234
+ # الدالة الواجهة غير المتزامنة
235
+ async def fetch_telegram_stats(limit: int = POSTS_LIMIT) -> List[Dict[str, Any]]:
236
+ """Fetches general channel statistics from Telegram asynchronously."""
237
+ if not IS_SERVICE_READY:
238
+ raise RuntimeError(STATUS_MESSAGE)
239
+
240
+ try:
241
+ # تشغيل الدالة المتزامنة في خيط منفصل
242
+ stats = await asyncio.to_thread(_fetch_channel_info_sync)
243
+
244
+ log.info(f"Successfully fetched stats for channel {stats['title']}.")
245
+ # نرسل قائمة تحتوي على قاموس واحد وهو إحصائيات القناة
246
+ return [stats]
247
+ except RuntimeError as e:
248
+ raise e
249
+ except Exception as e:
250
+ raise RuntimeError(f"خطأ في جلب بيانات Telegram عبر Bot API: {e}")
251
+
252
 
253
  # ---------------- Main Analysis Pipeline ----------------
254
+ # ... (دالة run_analysis_pipeline تبقى كما هي) ...
255
+
256
  async def run_analysis_pipeline(limit: int = POSTS_LIMIT) -> Dict[str, Any]:
257
  """Runs the full analysis pipeline."""
258
  log.info("Running manual analysis job...")
259
 
260
+ # 1. جلب البيانات (الآن جلب بيانات القناة الأساسية)
261
  try:
262
  posts = await fetch_telegram_stats(limit=limit)
263
  except RuntimeError as e:
 
268
  return {"status": "error", "message": error_detail, "log_entry": entry}
269
 
270
  if not posts:
271
+ # في حالة Bot API، يجب أن يكون هناك دائماً إحصائية واحدة للقناة إذا نجح الجلب
272
+ log.warning("No channel stats found for analysis.")
273
+ entry = {"time": datetime.utcnow().isoformat(), "error": "no_stats"}
274
  save_log(entry)
275
+ return {"status": "warning", "message": "لم يتم العثور على إحصائيات للقناة.", "log_entry": entry}
276
 
277
  # 2. تشغيل Mamba
278
  stats_text = encode_stats_for_mamba(posts)
 
283
 
284
  entry = {
285
  "time": datetime.utcnow().isoformat(),
286
+ "posts_count": 1, # إحصائية واحدة فقط للقناة
287
+ "channel_title": posts[0].get('title'),
288
+ "channel_members": posts[0].get('members'),
 
289
  "advice": interpretation
290
  }
291
 
 
295
  # تنسيق الخرج لـ Gradio
296
  output_message = (
297
  f"**✅ اكتمل التحليل بنجاح!**\n\n"
298
+ f"**القناة:** {posts[0].get('title')}\n"
299
+ f"**عدد الأعضاء:** {posts[0].get('members')}\n"
300
  f"**وقت التحليل:** {entry['time']}\n\n"
301
  f"--- **توصيات الذكاء الاصطناعي** ---\n"
302
  f"{interpretation}\n\n"
 
307
  return {"status": "success", "message": output_message, "log_entry": entry}
308
 
309
 
310
+ # ---------------- Gradio Interface Functions & Scheduler ----------------
311
+ # تبقى كما هي، حيث أنها تتعامل مع دوال async
312
+ # ... (بقية الكود: gradio_run_once, gradio_get_logs, daily_job_wrapper, Gradio Interface) ...
313
 
314
  async def gradio_run_once(limit: int) -> str:
315
  """Gradio function to run the analysis manually."""
316
 
 
317
  result = await run_analysis_pipeline(limit=limit)
318
 
319
  if result.get("status") == "success":
 
331
  if not logs:
332
  return "لا توجد سجلات تحليل متاحة."
333
 
 
334
  output = "## 📄 سجلات التحليل الأخيرة\n"
335
  for i, entry in enumerate(logs):
336
  output += f"### {i+1}. تحليل بتاريخ {entry.get('time', 'N/A')}\n"
337
  if entry.get('error'):
338
  output += f"**❌ خطأ:** {entry['error']}\n"
339
  else:
340
+ output += f"**✅ القناة:** {entry.get('channel_title', 'N/A')}\n"
341
  output += f"**💡 النصيحة:**\n```\n{entry.get('advice', 'N/A')[:300]}...\n```\n"
342
  output += "---\n"
343
 
 
347
  def daily_job_wrapper():
348
  """Synchronous wrapper to run the async job in the scheduler."""
349
  log.info("Running scheduled analysis job via Gradio wrapper...")
 
 
350
  try:
 
351
  loop = asyncio.new_event_loop()
352
  asyncio.set_event_loop(loop)
353
  result = loop.run_until_complete(run_analysis_pipeline())
 
355
  except Exception as e:
356
  log.error(f"Error in scheduled job: {e}")
357
  finally:
 
358
  loop.close()
359
 
360
  if IS_SERVICE_READY:
 
361
  scheduler.add_job(daily_job_wrapper, "cron", hour=13, minute=5)
362
  scheduler.start()
363
  log.info("Scheduler started for daily analysis.")
 
367
  # ---------------- Gradio Interface ----------------
368
 
369
  with gr.Blocks(title="Telegram Channel Analyzer Agent") as demo:
370
+ gr.Markdown("# 🤖 وكيل تحليل قناة Telegram (Bot API - محدود البيانات)")
371
  gr.Markdown(f"**حالة الخدمة:** {STATUS_MESSAGE}")
372
+ gr.Markdown(f"**القناة المستهدفة:** `{TG_CHANNEL}`")
373
  gr.Markdown("---")
374
 
375
  with gr.Tab("تشغيل التحليل يدوياً"):
376
+ gr.Markdown("## ⚙️ تشغيل لمرة واحدة - جلب إحصائيات القناة الأساسية")
377
+ # تم إبقاء شريط التمرير لكنه لن يؤثر فعليًا على Bot API
378
  limit_input = gr.Slider(
379
  minimum=10,
380
  maximum=300,
381
  step=10,
382
  value=POSTS_LIMIT,
383
+ label="الحد الأقصى للمنشورات (غير مستخدم مع Bot API)"
384
  )
385
+ run_button = gr.Button("🚀 بدء تحليل معلومات القناة الآن")
386
 
387
  output_textbox = gr.Markdown(label="نتيجة التحليل")
388
 
 
389
  run_button.click(
390
  fn=gradio_run_once,
391
  inputs=[limit_input],
 
402
  inputs=[],
403
  outputs=[log_output]
404
  )
 
405
  demo.load(
406
  fn=gradio_get_logs,
407
  inputs=[],
 
411
  # ---------------- Main Entry Point ----------------
412
  if __name__ == "__main__":
413
  log.info("Starting Analyzer Agent Gradio Interface...")
 
414
  PORT = int(os.getenv("PORT", "7860"))
 
415
  demo.launch(server_name="0.0.0.0", server_port=PORT)