diff --git a/CONFIGURATION_GUIDE.md b/CONFIGURATION_GUIDE.md index f6371da..071b343 100644 --- a/CONFIGURATION_GUIDE.md +++ b/CONFIGURATION_GUIDE.md @@ -285,6 +285,7 @@ MONGODB_COLLECTION=ghconfig STAFF_CHAT_ID=your_staff_chat_id FACTORY_BOT_ID=your_bot_id +FACTORY_BOT_USERNAME=your_bot_username ``` --- diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md index 773a980..4e966be 100644 --- a/IMPLEMENTATION_COMPLETE.md +++ b/IMPLEMENTATION_COMPLETE.md @@ -215,6 +215,7 @@ MONGODB_COLLECTION=ghconfig # Admin STAFF_CHAT_ID=your_admin_chat_id FACTORY_BOT_ID=your_bot_id +FACTORY_BOT_USERNAME=your_bot_username # Logging LOG_LEVEL=INFO diff --git a/README.md b/README.md index 2d5fdfb..c8f83db 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The application follows a modular architecture with the following components: STAFF_CHAT_ID=your_admin_chat_id FACTORY_BOT_ID=your_bot_id + FACTORY_BOT_USERNAME=your_bot_username ``` Generate `TELETHON_TOKEN` with: diff --git a/examples/configmap.yaml b/examples/configmap.yaml index ea779e0..8df1faf 100644 --- a/examples/configmap.yaml +++ b/examples/configmap.yaml @@ -14,3 +14,4 @@ data: TELETHON_API_ID: "123456" STAFF_CHAT_ID: "-1001234567890" FACTORY_BOT_ID: "123456789" + FACTORY_BOT_USERNAME: "grouphelpbot" diff --git a/src/api/routes/groups.py b/src/api/routes/groups.py index 65d91d1..ea7cd54 100644 --- a/src/api/routes/groups.py +++ b/src/api/routes/groups.py @@ -24,6 +24,7 @@ async def create_group(payload: CreateGroupRequest, request: Request): description=payload.description or payload.name, staff_chat_id=request.app.state.config["telegram"].get("staff_chat_id"), factory_bot_id=request.app.state.config["telegram"].get("factory_bot_id"), + factory_bot_username=request.app.state.config["telegram"].get("factory_bot_username"), ) return _envelope(message) diff --git a/src/config.py b/src/config.py index 4aa5dc6..b90224c 100644 --- a/src/config.py +++ b/src/config.py @@ -9,7 +9,8 @@ TELETHON_TOKEN = os.environ.get("TELETHON_TOKEN") TELETHON_API_HASH = os.environ.get("TELETHON_API_HASH") TELETHON_API_ID = int(os.environ.get("TELETHON_API_ID", 0)) STAFF_CHAT_ID = int(os.environ.get("STAFF_CHAT_ID", 0)) -FACTORY_BOT_ID = int(os.environ.get("FACTORY_BOT_ID", 0)) +FACTORY_BOT_ID = int(os.environ.get("FACTORY_BOT_ID") or 0) +FACTORY_BOT_USERNAME = os.environ.get("FACTORY_BOT_USERNAME", "").strip() # MongoDB configuration MONGODB_URI = os.environ.get('MONGODB_URI', '') @@ -74,7 +75,8 @@ def load_config(): 'session': get_telegram_session(), 'session_file': 'session', 'staff_chat_id': STAFF_CHAT_ID, - 'factory_bot_id': FACTORY_BOT_ID + 'factory_bot_id': FACTORY_BOT_ID, + 'factory_bot_username': FACTORY_BOT_USERNAME, }, 'mongodb': { 'uri': MONGODB_URI, diff --git a/src/handlers/admin_handler.py b/src/handlers/admin_handler.py index 6214167..305b6ba 100644 --- a/src/handlers/admin_handler.py +++ b/src/handlers/admin_handler.py @@ -14,6 +14,22 @@ class AdminHandler: def __init__(self, user_service: UserService): self.user_service = user_service + + def _ensure_user_record(self, user_id: int) -> tuple: + user = self.user_service.get_user_by_id(user_id) + if user: + return user, False + + user = User(id=user_id, username=None, first_name=f"id:{user_id}") + saved = self.user_service.save_user(user) + if not saved: + raise RuntimeError(f"Failed to create placeholder user record for {user_id}") + return user, True + + def _format_user(self, user: User, user_id: int, created: bool = False) -> str: + created_note = " (placeholder created)" if created else "" + username = f"@{user.username}" if user.username else user.name + return f" • {username} (ID: {user_id}){created_note}" def verify_access(self, chat_id: int) -> tuple: """Verify admin access""" @@ -53,19 +69,14 @@ class AdminHandler: if not user_ids: return "❌ Please provide at least one user ID.\n\nUsage: `/admin_set_users ...`" - # Verify all users exist valid_user_ids = [] - invalid_user_ids = [] + created_users = [] for user_id in user_ids: - user = self.user_service.get_user_by_id(user_id) - if user: - valid_user_ids.append(user_id) - else: - invalid_user_ids.append(user_id) - - if not valid_user_ids: - return f"❌ No valid users found. User IDs {invalid_user_ids} do not exist in database." + _, created = self._ensure_user_record(user_id) + valid_user_ids.append(user_id) + if created: + created_users.append(user_id) # Set default users if set_default_group_users(valid_user_ids): @@ -73,12 +84,11 @@ class AdminHandler: for user_id in valid_user_ids: user = self.user_service.get_user_by_id(user_id) if user: - user_list.append(f" • {user.username} (ID: {user_id})") + user_list.append(self._format_user(user, user_id, user_id in created_users)) response = "✅ Default users updated successfully:\n" + "\n".join(user_list) - - if invalid_user_ids: - response += f"\n\n⚠️ These users were not found: {invalid_user_ids}" + if created_users: + response += "\n\nℹ️ Placeholder records were created for new Telegram IDs." return response else: @@ -99,23 +109,18 @@ class AdminHandler: current_users = get_default_group_users() - # Verify all new users exist valid_user_ids = [] - invalid_user_ids = [] + created_users = [] for user_id in user_ids: if user_id not in current_users: - user = self.user_service.get_user_by_id(user_id) - if user: - valid_user_ids.append(user_id) - else: - invalid_user_ids.append(user_id) + _, created = self._ensure_user_record(user_id) + valid_user_ids.append(user_id) + if created: + created_users.append(user_id) if not valid_user_ids: - if invalid_user_ids: - return f"❌ No valid users found. User IDs {invalid_user_ids} do not exist in database." - else: - return "ℹ️ All provided users are already in the default list." + return "ℹ️ All provided users are already in the default list." # Add to default users updated_users = current_users + valid_user_ids @@ -124,12 +129,11 @@ class AdminHandler: for user_id in valid_user_ids: user = self.user_service.get_user_by_id(user_id) if user: - user_list.append(f" • {user.username} (ID: {user_id})") + user_list.append(self._format_user(user, user_id, user_id in created_users)) response = "✅ Users added to default list:\n" + "\n".join(user_list) - - if invalid_user_ids: - response += f"\n\n⚠️ These users were not found: {invalid_user_ids}" + if created_users: + response += "\n\nℹ️ Placeholder records were created for new Telegram IDs." return response else: diff --git a/src/handlers/config_handler.py b/src/handlers/config_handler.py index 143bde4..a8ebc4f 100644 --- a/src/handlers/config_handler.py +++ b/src/handlers/config_handler.py @@ -1,6 +1,7 @@ import logging from typing import List from src.config import get_default_group_users, set_default_group_users, get_qr_data, set_qr_backup_data +from src.models.user import User from src.services.user_service import UserService logger = logging.getLogger(__name__) @@ -10,6 +11,22 @@ class ConfigHandler: def __init__(self, user_service: UserService): self.user_service = user_service + + def _ensure_user_record(self, user_id: int) -> tuple: + user = self.user_service.get_user_by_id(user_id) + if user: + return user, False + + user = User(id=user_id, username=None, first_name=f"id:{user_id}") + saved = self.user_service.save_user(user) + if not saved: + raise RuntimeError(f"Failed to create placeholder user record for {user_id}") + return user, True + + def _format_user(self, user: User, user_id: int, created: bool = False) -> str: + created_note = " (placeholder created)" if created else "" + username = f"@{user.username}" if user.username else user.name + return f" • {username} (ID: {user_id}){created_note}" async def handle_get_default_users(self) -> str: """Get current default users for new groups""" @@ -37,19 +54,14 @@ class ConfigHandler: if not user_ids: return "❌ Please provide at least one user ID.\n\nUsage: `/set_default_users ...`" - # Verify all users exist valid_user_ids = [] - invalid_user_ids = [] + created_users = [] for user_id in user_ids: - user = self.user_service.get_user_by_id(user_id) - if user: - valid_user_ids.append(user_id) - else: - invalid_user_ids.append(user_id) - - if not valid_user_ids: - return f"❌ No valid users found. User IDs {invalid_user_ids} do not exist in database." + _, created = self._ensure_user_record(user_id) + valid_user_ids.append(user_id) + if created: + created_users.append(user_id) # Set default users if set_default_group_users(valid_user_ids): @@ -57,12 +69,11 @@ class ConfigHandler: for user_id in valid_user_ids: user = self.user_service.get_user_by_id(user_id) if user: - user_list.append(f" • {user.username} (ID: {user_id})") + user_list.append(self._format_user(user, user_id, user_id in created_users)) response = "✅ Default users updated successfully:\n" + "\n".join(user_list) - - if invalid_user_ids: - response += f"\n\n⚠️ These users were not found: {invalid_user_ids}" + if created_users: + response += "\n\nℹ️ Placeholder records were created for new Telegram IDs." return response else: @@ -79,23 +90,18 @@ class ConfigHandler: current_users = get_default_group_users() - # Verify all new users exist valid_user_ids = [] - invalid_user_ids = [] + created_users = [] for user_id in user_ids: if user_id not in current_users: - user = self.user_service.get_user_by_id(user_id) - if user: - valid_user_ids.append(user_id) - else: - invalid_user_ids.append(user_id) + _, created = self._ensure_user_record(user_id) + valid_user_ids.append(user_id) + if created: + created_users.append(user_id) if not valid_user_ids: - if invalid_user_ids: - return f"❌ No valid users found. User IDs {invalid_user_ids} do not exist in database." - else: - return "ℹ️ All provided users are already in the default list." + return "ℹ️ All provided users are already in the default list." # Add to default users updated_users = current_users + valid_user_ids @@ -104,12 +110,11 @@ class ConfigHandler: for user_id in valid_user_ids: user = self.user_service.get_user_by_id(user_id) if user: - user_list.append(f" • {user.username} (ID: {user_id})") + user_list.append(self._format_user(user, user_id, user_id in created_users)) response = "✅ Users added to default list:\n" + "\n".join(user_list) - - if invalid_user_ids: - response += f"\n\n⚠️ These users were not found: {invalid_user_ids}" + if created_users: + response += "\n\nℹ️ Placeholder records were created for new Telegram IDs." return response else: diff --git a/src/handlers/group_handler.py b/src/handlers/group_handler.py index 923b78c..fbabacf 100644 --- a/src/handlers/group_handler.py +++ b/src/handlers/group_handler.py @@ -22,6 +22,7 @@ class GroupHandler: status_callback: Optional[StatusCallback] = None, staff_chat_id: Optional[int] = None, factory_bot_id: Optional[int] = None, + factory_bot_username: Optional[str] = None, ) -> str: """Handle command to create a new group""" try: @@ -32,6 +33,7 @@ class GroupHandler: status_callback=status_callback, staff_chat_id=staff_chat_id, factory_bot_id=factory_bot_id, + factory_bot_username=factory_bot_username, ) if result: invite = f"\n🔗 {result['invite_link']}" if result.get("invite_link") else "" diff --git a/src/main.py b/src/main.py index 1d45db6..940a587 100644 --- a/src/main.py +++ b/src/main.py @@ -26,6 +26,7 @@ async def main(): config = load_config() staff_chat_id = config['telegram']['staff_chat_id'] factory_bot_id = config['telegram']['factory_bot_id'] + factory_bot_username = config['telegram']['factory_bot_username'] # Initialize MongoDB service mongo_service = MongoDBService( @@ -58,6 +59,7 @@ async def main(): status_callback=event.respond, staff_chat_id=staff_chat_id, factory_bot_id=factory_bot_id, + factory_bot_username=factory_bot_username, ) await event.respond(response) diff --git a/src/services/group_service.py b/src/services/group_service.py index 6f4f156..b46492e 100644 --- a/src/services/group_service.py +++ b/src/services/group_service.py @@ -106,6 +106,7 @@ class GroupService: status_callback: Optional[StatusCallback] = None, staff_chat_id: Optional[int] = None, factory_bot_id: Optional[int] = None, + factory_bot_username: Optional[str] = None, ) -> Optional[dict]: """Create a Telegram megagroup and run the IMS GroupFactory setup flow.""" try: @@ -165,6 +166,14 @@ class GroupService: except UserPrivacyRestrictedError: logger.warning(f"User {user.username or user.id} has privacy restrictions") error_count += 1 + except ValueError as e: + logger.error(f"Could not resolve Telegram entity for user {user.username or user.id}: {e}") + await self._notify( + status_callback, + f"⚠️ Could not resolve user {user.username or user.id}. " + "If this is a numeric ID, the userbot must have seen that user or you must store a username." + ) + error_count += 1 except Exception as e: logger.error(f"Error adding user {user.username or user.id}: {e}") error_count += 1 @@ -178,10 +187,12 @@ class GroupService: f"✅ Added {success_count}/{len(users)} users to the group", ) - if factory_bot_id: + factory_bot_username = (factory_bot_username or "").strip().lstrip("@") + factory_bot_ref = factory_bot_username or factory_bot_id + if factory_bot_ref: try: - logger.info(f"Promoting factory bot {factory_bot_id} as manager") - factory_bot = await self.client.get_input_entity(factory_bot_id) + logger.info(f"Promoting factory bot {factory_bot_ref} as manager") + factory_bot = await self.client.get_input_entity(factory_bot_ref) try: await self.client(InviteToChannelRequest(target_group, [factory_bot])) except Exception as e: @@ -189,8 +200,15 @@ class GroupService: await self._promote_user(target_group, factory_bot, full_admin=True) await asyncio.sleep(2) except Exception as e: - logger.error(f"Failed to promote factory bot {factory_bot_id}: {e}") - await self._notify(status_callback, f"⚠️ Failed to promote factory bot: {e}") + logger.error(f"Failed to promote factory bot {factory_bot_ref}: {e}") + if factory_bot_username: + await self._notify(status_callback, f"⚠️ Failed to promote factory bot @{factory_bot_username}: {e}") + else: + await self._notify( + status_callback, + "⚠️ Failed to promote factory bot from FACTORY_BOT_ID. " + "Set FACTORY_BOT_USERNAME so Telethon can resolve the bot entity." + ) await self._notify(status_callback, "⚙️ Sending GroupHelp setup commands...") await self.client.send_message(target_group, "/pro") @@ -257,8 +275,12 @@ class GroupService: group = await self.client.get_input_entity(group_id) for user in users: user_ref = user.username or user.id - user_to_add = await self.client.get_input_entity(user_ref) - await self.client(InviteToChannelRequest(group, [user_to_add])) + try: + user_to_add = await self.client.get_input_entity(user_ref) + await self.client(InviteToChannelRequest(group, [user_to_add])) + except ValueError as e: + logger.error(f"Could not resolve Telegram entity for user {user_ref}: {e}") + return False logger.info(f"Successfully added {len(users)} users to group {group_id}") return True