This commit is contained in:
Corrado Mulas
2026-06-19 15:34:21 +02:00
parent 40c25aac4a
commit fea971f6fd
11 changed files with 109 additions and 67 deletions

View File

@@ -285,6 +285,7 @@ MONGODB_COLLECTION=ghconfig
STAFF_CHAT_ID=your_staff_chat_id STAFF_CHAT_ID=your_staff_chat_id
FACTORY_BOT_ID=your_bot_id FACTORY_BOT_ID=your_bot_id
FACTORY_BOT_USERNAME=your_bot_username
``` ```
--- ---

View File

@@ -215,6 +215,7 @@ MONGODB_COLLECTION=ghconfig
# Admin # Admin
STAFF_CHAT_ID=your_admin_chat_id STAFF_CHAT_ID=your_admin_chat_id
FACTORY_BOT_ID=your_bot_id FACTORY_BOT_ID=your_bot_id
FACTORY_BOT_USERNAME=your_bot_username
# Logging # Logging
LOG_LEVEL=INFO LOG_LEVEL=INFO

View File

@@ -39,6 +39,7 @@ The application follows a modular architecture with the following components:
STAFF_CHAT_ID=your_admin_chat_id STAFF_CHAT_ID=your_admin_chat_id
FACTORY_BOT_ID=your_bot_id FACTORY_BOT_ID=your_bot_id
FACTORY_BOT_USERNAME=your_bot_username
``` ```
Generate `TELETHON_TOKEN` with: Generate `TELETHON_TOKEN` with:

View File

@@ -14,3 +14,4 @@ data:
TELETHON_API_ID: "123456" TELETHON_API_ID: "123456"
STAFF_CHAT_ID: "-1001234567890" STAFF_CHAT_ID: "-1001234567890"
FACTORY_BOT_ID: "123456789" FACTORY_BOT_ID: "123456789"
FACTORY_BOT_USERNAME: "grouphelpbot"

View File

@@ -24,6 +24,7 @@ async def create_group(payload: CreateGroupRequest, request: Request):
description=payload.description or payload.name, description=payload.description or payload.name,
staff_chat_id=request.app.state.config["telegram"].get("staff_chat_id"), 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_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) return _envelope(message)

View File

@@ -9,7 +9,8 @@ TELETHON_TOKEN = os.environ.get("TELETHON_TOKEN")
TELETHON_API_HASH = os.environ.get("TELETHON_API_HASH") TELETHON_API_HASH = os.environ.get("TELETHON_API_HASH")
TELETHON_API_ID = int(os.environ.get("TELETHON_API_ID", 0)) TELETHON_API_ID = int(os.environ.get("TELETHON_API_ID", 0))
STAFF_CHAT_ID = int(os.environ.get("STAFF_CHAT_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 configuration
MONGODB_URI = os.environ.get('MONGODB_URI', '') MONGODB_URI = os.environ.get('MONGODB_URI', '')
@@ -74,7 +75,8 @@ def load_config():
'session': get_telegram_session(), 'session': get_telegram_session(),
'session_file': 'session', 'session_file': 'session',
'staff_chat_id': STAFF_CHAT_ID, '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': { 'mongodb': {
'uri': MONGODB_URI, 'uri': MONGODB_URI,

View File

@@ -14,6 +14,22 @@ class AdminHandler:
def __init__(self, user_service: UserService): def __init__(self, user_service: UserService):
self.user_service = user_service 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: def verify_access(self, chat_id: int) -> tuple:
"""Verify admin access""" """Verify admin access"""
@@ -53,19 +69,14 @@ class AdminHandler:
if not user_ids: if not user_ids:
return "❌ Please provide at least one user ID.\n\nUsage: `/admin_set_users <user_id1> <user_id2> ...`" return "❌ Please provide at least one user ID.\n\nUsage: `/admin_set_users <user_id1> <user_id2> ...`"
# Verify all users exist
valid_user_ids = [] valid_user_ids = []
invalid_user_ids = [] created_users = []
for user_id in user_ids: for user_id in user_ids:
user = self.user_service.get_user_by_id(user_id) _, created = self._ensure_user_record(user_id)
if user: valid_user_ids.append(user_id)
valid_user_ids.append(user_id) if created:
else: created_users.append(user_id)
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."
# Set default users # Set default users
if set_default_group_users(valid_user_ids): if set_default_group_users(valid_user_ids):
@@ -73,12 +84,11 @@ class AdminHandler:
for user_id in valid_user_ids: for user_id in valid_user_ids:
user = self.user_service.get_user_by_id(user_id) user = self.user_service.get_user_by_id(user_id)
if user: 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) response = "✅ Default users updated successfully:\n" + "\n".join(user_list)
if created_users:
if invalid_user_ids: response += "\n\n Placeholder records were created for new Telegram IDs."
response += f"\n\n⚠️ These users were not found: {invalid_user_ids}"
return response return response
else: else:
@@ -99,23 +109,18 @@ class AdminHandler:
current_users = get_default_group_users() current_users = get_default_group_users()
# Verify all new users exist
valid_user_ids = [] valid_user_ids = []
invalid_user_ids = [] created_users = []
for user_id in user_ids: for user_id in user_ids:
if user_id not in current_users: if user_id not in current_users:
user = self.user_service.get_user_by_id(user_id) _, created = self._ensure_user_record(user_id)
if user: valid_user_ids.append(user_id)
valid_user_ids.append(user_id) if created:
else: created_users.append(user_id)
invalid_user_ids.append(user_id)
if not valid_user_ids: if not valid_user_ids:
if invalid_user_ids: return " All provided users are already in the default list."
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."
# Add to default users # Add to default users
updated_users = current_users + valid_user_ids updated_users = current_users + valid_user_ids
@@ -124,12 +129,11 @@ class AdminHandler:
for user_id in valid_user_ids: for user_id in valid_user_ids:
user = self.user_service.get_user_by_id(user_id) user = self.user_service.get_user_by_id(user_id)
if user: 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) response = "✅ Users added to default list:\n" + "\n".join(user_list)
if created_users:
if invalid_user_ids: response += "\n\n Placeholder records were created for new Telegram IDs."
response += f"\n\n⚠️ These users were not found: {invalid_user_ids}"
return response return response
else: else:

View File

@@ -1,6 +1,7 @@
import logging import logging
from typing import List 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.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 from src.services.user_service import UserService
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -10,6 +11,22 @@ class ConfigHandler:
def __init__(self, user_service: UserService): def __init__(self, user_service: UserService):
self.user_service = user_service 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: async def handle_get_default_users(self) -> str:
"""Get current default users for new groups""" """Get current default users for new groups"""
@@ -37,19 +54,14 @@ class ConfigHandler:
if not user_ids: if not user_ids:
return "❌ Please provide at least one user ID.\n\nUsage: `/set_default_users <user_id1> <user_id2> ...`" return "❌ Please provide at least one user ID.\n\nUsage: `/set_default_users <user_id1> <user_id2> ...`"
# Verify all users exist
valid_user_ids = [] valid_user_ids = []
invalid_user_ids = [] created_users = []
for user_id in user_ids: for user_id in user_ids:
user = self.user_service.get_user_by_id(user_id) _, created = self._ensure_user_record(user_id)
if user: valid_user_ids.append(user_id)
valid_user_ids.append(user_id) if created:
else: created_users.append(user_id)
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."
# Set default users # Set default users
if set_default_group_users(valid_user_ids): if set_default_group_users(valid_user_ids):
@@ -57,12 +69,11 @@ class ConfigHandler:
for user_id in valid_user_ids: for user_id in valid_user_ids:
user = self.user_service.get_user_by_id(user_id) user = self.user_service.get_user_by_id(user_id)
if user: 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) response = "✅ Default users updated successfully:\n" + "\n".join(user_list)
if created_users:
if invalid_user_ids: response += "\n\n Placeholder records were created for new Telegram IDs."
response += f"\n\n⚠️ These users were not found: {invalid_user_ids}"
return response return response
else: else:
@@ -79,23 +90,18 @@ class ConfigHandler:
current_users = get_default_group_users() current_users = get_default_group_users()
# Verify all new users exist
valid_user_ids = [] valid_user_ids = []
invalid_user_ids = [] created_users = []
for user_id in user_ids: for user_id in user_ids:
if user_id not in current_users: if user_id not in current_users:
user = self.user_service.get_user_by_id(user_id) _, created = self._ensure_user_record(user_id)
if user: valid_user_ids.append(user_id)
valid_user_ids.append(user_id) if created:
else: created_users.append(user_id)
invalid_user_ids.append(user_id)
if not valid_user_ids: if not valid_user_ids:
if invalid_user_ids: return " All provided users are already in the default list."
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."
# Add to default users # Add to default users
updated_users = current_users + valid_user_ids updated_users = current_users + valid_user_ids
@@ -104,12 +110,11 @@ class ConfigHandler:
for user_id in valid_user_ids: for user_id in valid_user_ids:
user = self.user_service.get_user_by_id(user_id) user = self.user_service.get_user_by_id(user_id)
if user: 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) response = "✅ Users added to default list:\n" + "\n".join(user_list)
if created_users:
if invalid_user_ids: response += "\n\n Placeholder records were created for new Telegram IDs."
response += f"\n\n⚠️ These users were not found: {invalid_user_ids}"
return response return response
else: else:

View File

@@ -22,6 +22,7 @@ class GroupHandler:
status_callback: Optional[StatusCallback] = None, status_callback: Optional[StatusCallback] = None,
staff_chat_id: Optional[int] = None, staff_chat_id: Optional[int] = None,
factory_bot_id: Optional[int] = None, factory_bot_id: Optional[int] = None,
factory_bot_username: Optional[str] = None,
) -> str: ) -> str:
"""Handle command to create a new group""" """Handle command to create a new group"""
try: try:
@@ -32,6 +33,7 @@ class GroupHandler:
status_callback=status_callback, status_callback=status_callback,
staff_chat_id=staff_chat_id, staff_chat_id=staff_chat_id,
factory_bot_id=factory_bot_id, factory_bot_id=factory_bot_id,
factory_bot_username=factory_bot_username,
) )
if result: if result:
invite = f"\n🔗 {result['invite_link']}" if result.get("invite_link") else "" invite = f"\n🔗 {result['invite_link']}" if result.get("invite_link") else ""

View File

@@ -26,6 +26,7 @@ async def main():
config = load_config() config = load_config()
staff_chat_id = config['telegram']['staff_chat_id'] staff_chat_id = config['telegram']['staff_chat_id']
factory_bot_id = config['telegram']['factory_bot_id'] factory_bot_id = config['telegram']['factory_bot_id']
factory_bot_username = config['telegram']['factory_bot_username']
# Initialize MongoDB service # Initialize MongoDB service
mongo_service = MongoDBService( mongo_service = MongoDBService(
@@ -58,6 +59,7 @@ async def main():
status_callback=event.respond, status_callback=event.respond,
staff_chat_id=staff_chat_id, staff_chat_id=staff_chat_id,
factory_bot_id=factory_bot_id, factory_bot_id=factory_bot_id,
factory_bot_username=factory_bot_username,
) )
await event.respond(response) await event.respond(response)

View File

@@ -106,6 +106,7 @@ class GroupService:
status_callback: Optional[StatusCallback] = None, status_callback: Optional[StatusCallback] = None,
staff_chat_id: Optional[int] = None, staff_chat_id: Optional[int] = None,
factory_bot_id: Optional[int] = None, factory_bot_id: Optional[int] = None,
factory_bot_username: Optional[str] = None,
) -> Optional[dict]: ) -> Optional[dict]:
"""Create a Telegram megagroup and run the IMS GroupFactory setup flow.""" """Create a Telegram megagroup and run the IMS GroupFactory setup flow."""
try: try:
@@ -165,6 +166,14 @@ class GroupService:
except UserPrivacyRestrictedError: except UserPrivacyRestrictedError:
logger.warning(f"User {user.username or user.id} has privacy restrictions") logger.warning(f"User {user.username or user.id} has privacy restrictions")
error_count += 1 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: except Exception as e:
logger.error(f"Error adding user {user.username or user.id}: {e}") logger.error(f"Error adding user {user.username or user.id}: {e}")
error_count += 1 error_count += 1
@@ -178,10 +187,12 @@ class GroupService:
f"✅ Added {success_count}/{len(users)} users to the group", 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: try:
logger.info(f"Promoting factory bot {factory_bot_id} as manager") logger.info(f"Promoting factory bot {factory_bot_ref} as manager")
factory_bot = await self.client.get_input_entity(factory_bot_id) factory_bot = await self.client.get_input_entity(factory_bot_ref)
try: try:
await self.client(InviteToChannelRequest(target_group, [factory_bot])) await self.client(InviteToChannelRequest(target_group, [factory_bot]))
except Exception as e: except Exception as e:
@@ -189,8 +200,15 @@ class GroupService:
await self._promote_user(target_group, factory_bot, full_admin=True) await self._promote_user(target_group, factory_bot, full_admin=True)
await asyncio.sleep(2) await asyncio.sleep(2)
except Exception as e: except Exception as e:
logger.error(f"Failed to promote factory bot {factory_bot_id}: {e}") logger.error(f"Failed to promote factory bot {factory_bot_ref}: {e}")
await self._notify(status_callback, f"⚠️ Failed to promote factory bot: {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._notify(status_callback, "⚙️ Sending GroupHelp setup commands...")
await self.client.send_message(target_group, "/pro") await self.client.send_message(target_group, "/pro")
@@ -257,8 +275,12 @@ class GroupService:
group = await self.client.get_input_entity(group_id) group = await self.client.get_input_entity(group_id)
for user in users: for user in users:
user_ref = user.username or user.id user_ref = user.username or user.id
user_to_add = await self.client.get_input_entity(user_ref) try:
await self.client(InviteToChannelRequest(group, [user_to_add])) 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}") logger.info(f"Successfully added {len(users)} users to group {group_id}")
return True return True