Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion activity_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@
MongoClient = None # type: ignore
_HAS_PYMONGO = False
from datetime import datetime, timezone
import atexit

# חיבור MongoDB גלובלי יחיד לכל האפליקציה
_client = None # type: ignore

def get_mongo_client(mongodb_uri: str):
"""החזרת מופע MongoClient יחיד (singleton) לכל האפליקציה.

בכל קריאה מחזיר את אותו אובייקט, ויוצר רק בפעם הראשונה.
"""
global _client
if not _HAS_PYMONGO:
raise RuntimeError("pymongo not available")
if _client is None:
# שימוש ב-timezone מודע כדי לשמור אחידות זמנים במסד
_client = MongoClient(mongodb_uri, tz_aware=True, tzinfo=timezone.utc)
return _client

def close_mongo_client() -> None:
"""סגירת החיבור הגלובלי בבטחה בזמן כיבוי השירות."""
global _client
try:
if _client is not None:
_client.close()
finally:
_client = None

# סגירה אוטומטית ביציאה מהתהליך
atexit.register(close_mongo_client)

try:
from metrics import note_active_user # type: ignore
Expand All @@ -25,7 +54,8 @@ def __init__(self, mongodb_uri, service_id, service_name=None):
try:
if not _HAS_PYMONGO:
raise RuntimeError("pymongo not available")
self.client = MongoClient(mongodb_uri, tz_aware=True, tzinfo=timezone.utc)
# שימוש ב-singleton של MongoClient כדי למנוע חיבורים מרובים
self.client = get_mongo_client(mongodb_uri)
self.db = self.client["render_bot_monitor"]
self.service_id = service_id
self.service_name = service_name or service_id
Expand Down
23 changes: 10 additions & 13 deletions bot_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
from config import config
from database import CodeSnippet, db
from conversation_handlers import MAIN_KEYBOARD
from activity_reporter import create_reporter
# Reporter מוזרק בזמן ריצה כדי למנוע יצירה בזמן import
class _NoopReporter:
def report_activity(self, user_id):
return None

reporter = _NoopReporter()

def set_activity_reporter(new_reporter):
global reporter
reporter = new_reporter or _NoopReporter()
import json
try:
import aiohttp # for GitHub rate limit check
Expand All @@ -34,18 +43,6 @@
logger = logging.getLogger(__name__)

import os as _os
_DISABLE_REPORTER = bool(int((_os.getenv("DISABLE_ACTIVITY_REPORTER", "0") or "0").strip() or 0))
if _DISABLE_REPORTER:
class _NoopReporter:
def report_activity(self, user_id):
return None
reporter = _NoopReporter()
else:
reporter = create_reporter(
mongodb_uri="mongodb+srv://mumin:M43M2TFgLfGvhBwY@muminai.tm6x81b.mongodb.net/?retryWrites=true&w=majority&appName=muminAI",
service_id="srv-d29d72adbo4c73bcuep0",
service_name="CodeBot"
)

class AdvancedBotHandlers:
"""פקודות מתקדמות של הבוט"""
Expand Down
57 changes: 43 additions & 14 deletions conversation_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
)
from database import DatabaseManager
from file_manager import backup_manager
from activity_reporter import create_reporter
# Reporter מוזרק בזמן ריצה כדי להימנע מפתיחת חיבור בעת import
class _NoopReporter:
def report_activity(self, user_id):
return None

reporter = _NoopReporter()

def set_activity_reporter(new_reporter):
global reporter
reporter = new_reporter or _NoopReporter()
from utils import get_language_emoji as get_file_emoji
from user_stats import user_stats
from typing import List, Optional, Dict, cast
Expand Down Expand Up @@ -103,11 +112,7 @@ def _format_bytes(num: int) -> str:
["☁️ Google Drive", "ℹ️ הסבר על הבוט"]
]

reporter = create_reporter(
mongodb_uri="mongodb+srv://mumin:M43M2TFgLfGvhBwY@muminai.tm6x81b.mongodb.net/?retryWrites=true&w=majority&appName=muminAI",
service_id="srv-d29d72adbo4c73bcuep0",
service_name="CodeBot"
)
# ה-reporters יוגדרו ב-main בזמן ריצה

async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Handle /start and show the main menu."""
Expand Down Expand Up @@ -153,7 +158,10 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> i
reply_markup=reply_markup,
parse_mode=ParseMode.HTML,
)
reporter.report_activity(user_id)
try:
reporter.report_activity(user_id)
except Exception:
pass
return ConversationHandler.END
except Exception:
# אם משהו נכשל ביצירת קישור — נמשיך לזרימת ברירת המחדל
Expand All @@ -163,7 +171,10 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> i
welcome_text = MESSAGES["welcome"].format(name=safe_user_name)
keyboard = ReplyKeyboardMarkup(MAIN_KEYBOARD, resize_keyboard=True)
await update.message.reply_text(welcome_text, reply_markup=keyboard)
reporter.report_activity(user_id)
try:
reporter.report_activity(user_id)
except Exception:
pass
return ConversationHandler.END

HELP_PAGES = [
Expand Down Expand Up @@ -414,7 +425,10 @@ async def start_repo_zip_import(update: Update, context: ContextTypes.DEFAULT_TY
"🔖 אצמיד תגית repo:owner/name (אם קיימת ב-metadata). לא מתבצעת מחיקה.",
reply_markup=cancel_markup
)
reporter.report_activity(update.effective_user.id)
try:
reporter.report_activity(update.effective_user.id)
except Exception:
pass
return ConversationHandler.END

async def start_zip_create_flow(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
Expand All @@ -433,7 +447,10 @@ async def start_zip_create_flow(update: Update, context: ContextTypes.DEFAULT_TY
"כשתסיים/י, לחצ/י 'סיום' וניצור עבורך ZIP מוכן.",
reply_markup=InlineKeyboardMarkup(keyboard)
)
reporter.report_activity(update.effective_user.id)
try:
reporter.report_activity(update.effective_user.id)
except Exception:
pass
return ConversationHandler.END

async def show_by_repo_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
Expand Down Expand Up @@ -527,7 +544,10 @@ async def show_all_files(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
reply_markup=ReplyKeyboardMarkup(MAIN_KEYBOARD, resize_keyboard=True)
)

reporter.report_activity(user_id)
try:
reporter.report_activity(user_id)
except Exception:
pass
return ConversationHandler.END

async def show_large_files_direct(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
Expand All @@ -553,7 +573,10 @@ async def show_github_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -

github_handler = context.bot_data['github_handler']
await github_handler.github_menu_command(update, context)
reporter.report_activity(update.effective_user.id)
try:
reporter.report_activity(update.effective_user.id)
except Exception:
pass
return ConversationHandler.END


Expand Down Expand Up @@ -583,7 +606,10 @@ async def show_all_files_callback(update: Update, context: ContextTypes.DEFAULT_
"בחר/י דרך להצגת הקבצים:",
reply_markup=reply_markup
)
reporter.report_activity(update.effective_user.id)
try:
reporter.report_activity(update.effective_user.id)
except Exception:
pass
except Exception as e:
# אל תרשום ERROR אם זו רק הודעה שלא השתנתה
msg = str(e)
Expand Down Expand Up @@ -672,7 +698,10 @@ async def show_regular_files_callback(update: Update, context: ContextTypes.DEFA
if "message is not modified" not in str(br).lower():
raise

reporter.report_activity(user_id)
try:
reporter.report_activity(user_id)
except Exception:
pass

except Exception as e:
logger.error(f"Error in show_regular_files_callback: {e}")
Expand Down
85 changes: 68 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ class _DummyErrors:
from database import CodeSnippet, DatabaseManager, db
from services import code_service as code_processor
from bot_handlers import AdvancedBotHandlers # still used by legacy code
from bot_handlers import set_activity_reporter as set_bh_activity_reporter
from conversation_handlers import MAIN_KEYBOARD, get_save_conversation_handler
from activity_reporter import create_reporter
from conversation_handlers import set_activity_reporter as set_ch_activity_reporter
from activity_reporter import create_reporter, SimpleActivityReporter
from github_menu_handler import GitHubMenuHandler
from backup_menu_handler import BackupMenuHandler
from handlers.drive.menu import GoogleDriveMenuHandler
Expand Down Expand Up @@ -148,12 +150,8 @@ def _register_catch_all_callback(application, callback_fn) -> None:
logging.getLogger("telegram.ext.Updater").setLevel(logging.ERROR)
logging.getLogger("telegram.ext.Application").setLevel(logging.WARNING)

# יצירת אובייקט reporter גלובלי
reporter = create_reporter(
mongodb_uri=(os.getenv('REPORTER_MONGODB_URL') or os.getenv('REPORTER_MONGODB_URI') or config.MONGODB_URL),
service_id=os.getenv('REPORTER_SERVICE_ID', 'srv-d29d72adbo4c73bcuep0'),
service_name="CodeBot"
)
# Reporter יווצר ויוזרק בזמן ריצה לאחר בניית האפליקציה והקונפיג
reporter = None # type: ignore

# ===== עזר: שליחת הודעת אדמין =====
def get_admin_ids() -> list[int]:
Expand Down Expand Up @@ -650,6 +648,49 @@ async def run_polling(self, *a, **k):
except Exception:
pass

# יצירת והזרקת Activity Reporter בזמן ריצה (מונע חיבורים מרובים בזמן import)
try:
mongodb_uri = (
os.getenv('REPORTER_MONGODB_URL')
or os.getenv('REPORTER_MONGODB_URI')
or getattr(config, 'MONGODB_URL', None)
)
service_id = os.getenv('REPORTER_SERVICE_ID', getattr(config, 'BOT_LABEL', 'CodeBot'))
# תמיכה בנטרול דיווח פעילות דרך ENV
disable_reporter = bool(int((os.getenv('DISABLE_ACTIVITY_REPORTER', '0') or '0').strip() or 0))
if disable_reporter:
class _NoopReporter:
def report_activity(self, user_id):
return None
created_reporter = _NoopReporter()
else:
# יצירה בטוחה: SimpleActivityReporter מטפל בחוסר pymongo בסביבה
created_reporter = create_reporter(
mongodb_uri=mongodb_uri,
service_id=service_id,
service_name="CodeBot",
)
# עדכון גלובלי במודול זה
global reporter
reporter = created_reporter
# הזרקה למודולים שתלויים ב-report_activity
try:
set_bh_activity_reporter(created_reporter)
except Exception:
pass
try:
set_ch_activity_reporter(created_reporter)
except Exception:
pass
try:
from refactor_handlers import set_activity_reporter as set_rh_activity_reporter
set_rh_activity_reporter(created_reporter)
except Exception:
pass
except Exception:
# בסביבות CI/טסטים, אל נכשיל את הבנייה
reporter = None # type: ignore

self.setup_handlers()
self.advanced_handlers = AdvancedBotHandlers(self.application)
# רישום קטגוריית "⭐ מועדפים" לתפריט "📚 הקבצים"
Expand Down Expand Up @@ -1173,7 +1214,8 @@ async def show_docs(update: Update, context: ContextTypes.DEFAULT_TYPE):

async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""פקודת עזרה מפורטת"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context)
response = """
📚 <b>רשימת הפקודות המלאה:</b>
Expand Down Expand Up @@ -1220,7 +1262,8 @@ async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE)

async def save_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""פקודת שמירת קוד"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context)
user_id = update.effective_user.id

Expand Down Expand Up @@ -1268,7 +1311,8 @@ async def save_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE)

async def list_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""הצגת רשימת הקטעים של המשתמש"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
user_id = update.effective_user.id

files = db.get_user_files(user_id, limit=20)
Expand Down Expand Up @@ -1306,7 +1350,8 @@ async def list_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE)

async def search_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""חיפוש קטעי קוד"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context)
user_id = update.effective_user.id

Expand Down Expand Up @@ -1399,7 +1444,8 @@ async def check_commands(self, update: Update, context: ContextTypes.DEFAULT_TYP

async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""הצגת סטטיסטיקות המשתמש או מנהל"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context) # הוספת רישום משתמש לסטטיסטיקות
user_id = update.effective_user.id

Expand Down Expand Up @@ -2198,7 +2244,8 @@ def _parse_repo_full_from_label(label: str) -> str:
else:
await update.message.reply_text("❌ שגיאה בשמירת הקובץ")

reporter.report_activity(user_id)
if reporter is not None:
reporter.report_activity(user_id)

except Exception as e:
logger.error(f"שגיאה בטיפול בקובץ: {e}")
Expand All @@ -2212,7 +2259,8 @@ def _parse_repo_full_from_label(label: str) -> str:

async def handle_text_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""טיפול בהודעות טקסט (קוד פוטנציאלי)"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context)
user_id = update.effective_user.id
text = update.message.text
Expand Down Expand Up @@ -2349,7 +2397,8 @@ async def handle_text_message(self, update: Update, context: ContextTypes.DEFAUL

async def _save_code_snippet(self, update: Update, context: ContextTypes.DEFAULT_TYPE, code: str):
"""שמירה בפועל של קטע קוד"""
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
saving_data = context.user_data.pop('saving_file')

if len(code) > config.MAX_CODE_SIZE:
Expand Down Expand Up @@ -2587,7 +2636,8 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): #
# שמור משתמש במסד נתונים (INSERT OR IGNORE)
db_manager.save_user(user_id, username)

reporter.report_activity(user_id)
if reporter is not None:
reporter.report_activity(user_id)
await log_user_activity(update, context) # הוספת רישום משתמש לסטטיסטיקות

# בדיקה אם המשתמש הגיע מה-Web App או רוצה להוסיף קובץ
Expand Down Expand Up @@ -2655,7 +2705,8 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): #
)

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): # noqa: D401
reporter.report_activity(update.effective_user.id)
if reporter is not None:
reporter.report_activity(update.effective_user.id)
await log_user_activity(update, context) # הוספת רישום משתמש לסטטיסטיקות
await update.message.reply_text(
"ℹ️ השתמש ב/start כדי להתחיל.\n\n"
Expand Down
Loading
Loading