Fix path
This commit is contained in:
16
README.md
16
README.md
@@ -75,6 +75,15 @@ docker run telegram-groupfactory
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
### GroupFactory Commands (Staff Chat Only)
|
||||||
|
```
|
||||||
|
!newgrp - Start interactive group creation
|
||||||
|
!confirm - Confirm pending group creation
|
||||||
|
!cancel - Cancel pending group creation
|
||||||
|
!PING - Check if the userbot is online
|
||||||
|
!help - Show GroupFactory commands
|
||||||
|
```
|
||||||
|
|
||||||
### Admin Commands (Admin Chat Only)
|
### Admin Commands (Admin Chat Only)
|
||||||
```
|
```
|
||||||
/admin_add_user <username> - Add user to database
|
/admin_add_user <username> - Add user to database
|
||||||
@@ -86,9 +95,9 @@ docker run telegram-groupfactory
|
|||||||
/admin_get_qr - Retrieve GroupHelp backup QR data
|
/admin_get_qr - Retrieve GroupHelp backup QR data
|
||||||
```
|
```
|
||||||
|
|
||||||
### User Commands
|
### Legacy User Commands
|
||||||
```
|
```
|
||||||
/create_group <name> - Create group with default users
|
/create_group <name> - Deprecated; use !newgrp in staff chat
|
||||||
/users - List all users
|
/users - List all users
|
||||||
/user <user_id> - Get user info
|
/user <user_id> - Get user info
|
||||||
/help - Show all available commands
|
/help - Show all available commands
|
||||||
@@ -96,7 +105,8 @@ docker run telegram-groupfactory
|
|||||||
|
|
||||||
## Available Commands
|
## Available Commands
|
||||||
|
|
||||||
- `/create_group <name>` - Create a new group
|
- `!newgrp` - Create a new group with DB users and stored GroupHelp QR import
|
||||||
|
- `/create_group <name>` - Deprecated; use `!newgrp`
|
||||||
- `/add_users <group_id> <user_ids>` - Add users to a group
|
- `/add_users <group_id> <user_ids>` - Add users to a group
|
||||||
- `/get_group <group_id>` - Get group information
|
- `/get_group <group_id>` - Get group information
|
||||||
- `/users` - List all users
|
- `/users` - List all users
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from src.api.schemas import (
|
|||||||
CommandResponse,
|
CommandResponse,
|
||||||
CreateGroupRequest,
|
CreateGroupRequest,
|
||||||
)
|
)
|
||||||
from src.config import save_user_admin_role
|
|
||||||
|
|
||||||
|
|
||||||
def _envelope(message: str) -> CommandResponse:
|
def _envelope(message: str) -> CommandResponse:
|
||||||
@@ -19,14 +18,13 @@ router = APIRouter(prefix="/api/groups", tags=["groups"], dependencies=[Depends(
|
|||||||
@router.post("", response_model=CommandResponse)
|
@router.post("", response_model=CommandResponse)
|
||||||
async def create_group(payload: CreateGroupRequest, request: Request):
|
async def create_group(payload: CreateGroupRequest, request: Request):
|
||||||
handler = request.app.state.group_handler
|
handler = request.app.state.group_handler
|
||||||
message = await handler.handle_create_group(payload.name, payload.user_ids)
|
message = await handler.handle_create_group(
|
||||||
|
payload.name,
|
||||||
# Mirror the Telegram inline-button flow: persist the admin-role choice
|
user_ids=payload.user_ids,
|
||||||
# against the bot user (the entity creating the group via the API).
|
description=payload.description or payload.name,
|
||||||
if payload.full_admin is not None:
|
staff_chat_id=request.app.state.config["telegram"].get("staff_chat_id"),
|
||||||
bot_user_id = request.app.state.config["telegram"].get("factory_bot_id")
|
factory_bot_id=request.app.state.config["telegram"].get("factory_bot_id"),
|
||||||
if bot_user_id:
|
)
|
||||||
save_user_admin_role(bot_user_id, payload.full_admin)
|
|
||||||
|
|
||||||
return _envelope(message)
|
return _envelope(message)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class AddUserRequest(BaseModel):
|
|||||||
|
|
||||||
class CreateGroupRequest(BaseModel):
|
class CreateGroupRequest(BaseModel):
|
||||||
name: str = Field(..., min_length=1)
|
name: str = Field(..., min_length=1)
|
||||||
|
description: Optional[str] = None
|
||||||
user_ids: Optional[List[int]] = None
|
user_ids: Optional[List[int]] = None
|
||||||
full_admin: Optional[bool] = None
|
full_admin: Optional[bool] = None
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
from typing import List
|
from typing import List
|
||||||
from src.config import (
|
from src.config import (
|
||||||
get_default_group_users, set_default_group_users, get_qr_data,
|
get_default_group_users, set_default_group_users, get_qr_data,
|
||||||
set_qr_backup_data, verify_admin_access, save_user_admin_role
|
set_qr_backup_data, verify_admin_access
|
||||||
)
|
)
|
||||||
from src.services.user_service import UserService
|
from src.services.user_service import UserService
|
||||||
from src.models.user import User
|
from src.models.user import User
|
||||||
@@ -191,7 +191,7 @@ class AdminHandler:
|
|||||||
import hashlib
|
import hashlib
|
||||||
user_id = int(hashlib.md5(username.encode()).hexdigest()[:8], 16)
|
user_id = int(hashlib.md5(username.encode()).hexdigest()[:8], 16)
|
||||||
|
|
||||||
user = User(id=user_id, username=username, name=username)
|
user = User(id=user_id, username=username, first_name=username)
|
||||||
success = self.user_service.save_user(user)
|
success = self.user_service.save_user(user)
|
||||||
if success:
|
if success:
|
||||||
return f"✅ User {username} added successfully (ID: {user_id})"
|
return f"✅ User {username} added successfully (ID: {user_id})"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import List
|
from typing import Awaitable, Callable, List, Optional
|
||||||
from src.services.user_service import UserService
|
from src.services.user_service import UserService
|
||||||
from src.services.group_service import GroupService
|
from src.services.group_service import GroupService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
StatusCallback = Callable[[str], Awaitable[object]]
|
||||||
|
|
||||||
class GroupHandler:
|
class GroupHandler:
|
||||||
"""Handler class for group-related commands"""
|
"""Handler class for group-related commands"""
|
||||||
|
|
||||||
@@ -12,12 +14,34 @@ class GroupHandler:
|
|||||||
self.user_service = user_service
|
self.user_service = user_service
|
||||||
self.group_service = group_service
|
self.group_service = group_service
|
||||||
|
|
||||||
async def handle_create_group(self, group_name: str, user_ids: List[int]) -> str:
|
async def handle_create_group(
|
||||||
|
self,
|
||||||
|
group_name: str,
|
||||||
|
user_ids: List[int] = None,
|
||||||
|
description: str = "",
|
||||||
|
status_callback: Optional[StatusCallback] = None,
|
||||||
|
staff_chat_id: Optional[int] = None,
|
||||||
|
factory_bot_id: Optional[int] = None,
|
||||||
|
) -> str:
|
||||||
"""Handle command to create a new group"""
|
"""Handle command to create a new group"""
|
||||||
try:
|
try:
|
||||||
group_id = await self.group_service.create_group(group_name, user_ids)
|
result = await self.group_service.create_group(
|
||||||
if group_id:
|
group_name,
|
||||||
return f"✅ Group '{group_name}' created successfully with ID: {group_id}"
|
description=description,
|
||||||
|
user_ids=user_ids,
|
||||||
|
status_callback=status_callback,
|
||||||
|
staff_chat_id=staff_chat_id,
|
||||||
|
factory_bot_id=factory_bot_id,
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
invite = f"\n🔗 {result['invite_link']}" if result.get("invite_link") else ""
|
||||||
|
qr_status = "\n✅ GroupHelp QR import sent" if result.get("qr_imported") else "\n⚠️ GroupHelp QR import not configured"
|
||||||
|
return (
|
||||||
|
f"✅ Group '{group_name}' created successfully with ID: {result['id']}\n"
|
||||||
|
f"👥 Added {result['users_added']}/{result['users_total']} users"
|
||||||
|
f"{qr_status}"
|
||||||
|
f"{invite}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return "❌ Failed to create group"
|
return "❌ Failed to create group"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class UserHandler:
|
|||||||
import hashlib
|
import hashlib
|
||||||
user_id = int(hashlib.md5(username.encode()).hexdigest()[:8], 16)
|
user_id = int(hashlib.md5(username.encode()).hexdigest()[:8], 16)
|
||||||
|
|
||||||
user = User(id=user_id, username=username, name=username)
|
user = User(id=user_id, username=username, first_name=username)
|
||||||
success = self.user_service.save_user(user)
|
success = self.user_service.save_user(user)
|
||||||
if success:
|
if success:
|
||||||
return f"✅ User {username} added successfully (ID: {user_id})"
|
return f"✅ User {username} added successfully (ID: {user_id})"
|
||||||
|
|||||||
192
src/main.py
192
src/main.py
@@ -1,9 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from telethon import Button, TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
from src.config import load_config, save_user_admin_role, get_user_admin_role
|
from src.config import load_config
|
||||||
from src.services.mongodb_service import MongoDBService
|
from src.services.mongodb_service import MongoDBService
|
||||||
from src.services.user_service import UserService
|
from src.services.user_service import UserService
|
||||||
from src.services.group_service import GroupService
|
from src.services.group_service import GroupService
|
||||||
@@ -25,6 +24,8 @@ async def main():
|
|||||||
|
|
||||||
# Load configuration
|
# Load configuration
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
staff_chat_id = config['telegram']['staff_chat_id']
|
||||||
|
factory_bot_id = config['telegram']['factory_bot_id']
|
||||||
|
|
||||||
# Initialize MongoDB service
|
# Initialize MongoDB service
|
||||||
mongo_service = MongoDBService(
|
mongo_service = MongoDBService(
|
||||||
@@ -47,46 +48,21 @@ async def main():
|
|||||||
config['telegram']['api_id'],
|
config['telegram']['api_id'],
|
||||||
config['telegram']['api_hash']
|
config['telegram']['api_hash']
|
||||||
)
|
)
|
||||||
|
group_service.set_client(client)
|
||||||
|
group_conversations = {}
|
||||||
|
|
||||||
@client.on(events.CallbackQuery())
|
async def create_group_from_staff_flow(event, name: str, description: str):
|
||||||
async def callback_handler(event):
|
response = await group_handler.handle_create_group(
|
||||||
"""Handle inline button callbacks for admin role selection"""
|
name,
|
||||||
try:
|
description=description,
|
||||||
data = event.data.decode() if isinstance(event.data, bytes) else event.data
|
status_callback=event.respond,
|
||||||
|
staff_chat_id=staff_chat_id,
|
||||||
|
factory_bot_id=factory_bot_id,
|
||||||
|
)
|
||||||
|
await event.respond(response)
|
||||||
|
|
||||||
# Parse callback data
|
def is_staff_chat(chat_id: int) -> bool:
|
||||||
if data.startswith('admin_role:'):
|
return chat_id == staff_chat_id
|
||||||
user_id = event.sender_id
|
|
||||||
is_full_admin = data.split(':')[1] == 'yes'
|
|
||||||
|
|
||||||
# Save the user's admin role preference
|
|
||||||
if save_user_admin_role(user_id, is_full_admin):
|
|
||||||
role_text = "✅ Full Group Admin" if is_full_admin else "👤 Regular Member"
|
|
||||||
await event.answer(f"Set as {role_text}")
|
|
||||||
|
|
||||||
# Edit the message to show the selection
|
|
||||||
await event.edit(f"Group Admin Role Selection\n\n{role_text} - Confirmed!")
|
|
||||||
else:
|
|
||||||
await event.answer("❌ Failed to save preference", alert=True)
|
|
||||||
|
|
||||||
elif data.startswith('group_create:'):
|
|
||||||
# Extract group name and user IDs from callback
|
|
||||||
parts = data.split(':', 2)
|
|
||||||
if len(parts) >= 3:
|
|
||||||
group_name = parts[1]
|
|
||||||
user_ids_str = parts[2]
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_ids = [int(uid) for uid in user_ids_str.split(',')] if user_ids_str else None
|
|
||||||
response = await group_handler.handle_create_group(group_name, user_ids)
|
|
||||||
await event.answer()
|
|
||||||
await event.edit(response)
|
|
||||||
except ValueError:
|
|
||||||
await event.answer("❌ Invalid user IDs", alert=True)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error handling callback: {e}")
|
|
||||||
await event.answer(f"❌ Error: {str(e)}", alert=True)
|
|
||||||
|
|
||||||
@client.on(events.NewMessage())
|
@client.on(events.NewMessage())
|
||||||
async def message_handler(event):
|
async def message_handler(event):
|
||||||
@@ -101,6 +77,105 @@ async def main():
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# ==================== IMS GROUPFACTORY STAFF FLOW ====================
|
||||||
|
if is_staff_chat(chat_id):
|
||||||
|
raw_text = text.strip()
|
||||||
|
lower_text = raw_text.lower()
|
||||||
|
|
||||||
|
if lower_text == '!ping':
|
||||||
|
await event.respond('PONG -> IMS GroupFactory Userbot')
|
||||||
|
return
|
||||||
|
|
||||||
|
if lower_text == '!help':
|
||||||
|
help_text = """**IMS GroupFactory Commands**
|
||||||
|
|
||||||
|
`!newgrp` - Start creating a new Telegram group
|
||||||
|
`!cancel` - Cancel the current operation
|
||||||
|
`!confirm` - Confirm group creation
|
||||||
|
`!PING` - Check if the userbot is online
|
||||||
|
`!help` - Display this help message"""
|
||||||
|
await event.respond(help_text)
|
||||||
|
return
|
||||||
|
|
||||||
|
if lower_text == '!cancel':
|
||||||
|
if sender_id in group_conversations:
|
||||||
|
del group_conversations[sender_id]
|
||||||
|
await event.respond('🛑 Operation canceled.')
|
||||||
|
else:
|
||||||
|
await event.respond('❓ No active operation to cancel.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if lower_text.startswith('!newgrp'):
|
||||||
|
if lower_text != '!newgrp':
|
||||||
|
cmd_args = raw_text[len('!newgrp'):].strip()
|
||||||
|
marker = "\n\nDescription:\n"
|
||||||
|
if marker in cmd_args:
|
||||||
|
name, description = cmd_args.split(marker, 1)
|
||||||
|
name = name.strip()
|
||||||
|
description = description.strip()
|
||||||
|
if not name or not description:
|
||||||
|
await event.respond('⚠️ Group name and description cannot be empty.')
|
||||||
|
return
|
||||||
|
await create_group_from_staff_flow(event, name, description)
|
||||||
|
else:
|
||||||
|
await event.respond('⚠️ Old format detected but missing description.\nUse `!newgrp` alone to start interactive mode.')
|
||||||
|
return
|
||||||
|
|
||||||
|
group_conversations[sender_id] = {
|
||||||
|
'step': 'name',
|
||||||
|
'data': {}
|
||||||
|
}
|
||||||
|
await event.respond('👋 Let\'s create a new group!\n\nPlease enter the group name:')
|
||||||
|
return
|
||||||
|
|
||||||
|
if sender_id in group_conversations:
|
||||||
|
current_step = group_conversations[sender_id]['step']
|
||||||
|
|
||||||
|
if lower_text == '!confirm' and current_step == 'confirm':
|
||||||
|
name = group_conversations[sender_id]['data']['name']
|
||||||
|
description = group_conversations[sender_id]['data']['description']
|
||||||
|
await event.respond('✅ Confirmed! Starting group creation process...')
|
||||||
|
try:
|
||||||
|
await create_group_from_staff_flow(event, name, description)
|
||||||
|
finally:
|
||||||
|
group_conversations.pop(sender_id, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
if raw_text.startswith('!'):
|
||||||
|
if current_step == 'confirm':
|
||||||
|
await event.respond('❓ Please type `!confirm` to create the group or `!cancel` to abort.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_step == 'name':
|
||||||
|
if not raw_text:
|
||||||
|
await event.respond('⚠️ Group name cannot be empty. Please try again:')
|
||||||
|
return
|
||||||
|
|
||||||
|
group_conversations[sender_id]['data']['name'] = raw_text
|
||||||
|
group_conversations[sender_id]['step'] = 'description'
|
||||||
|
await event.respond(f'📝 Group name set to: "{raw_text}"\n\nNow please enter the group description:')
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_step == 'description':
|
||||||
|
if not raw_text:
|
||||||
|
await event.respond('⚠️ Group description cannot be empty. Please try again:')
|
||||||
|
return
|
||||||
|
|
||||||
|
group_conversations[sender_id]['data']['description'] = raw_text
|
||||||
|
name = group_conversations[sender_id]['data']['name']
|
||||||
|
group_conversations[sender_id]['step'] = 'confirm'
|
||||||
|
await event.respond(
|
||||||
|
f'📋 **Group Creation Summary**\n\n'
|
||||||
|
f'**Name**: {name}\n'
|
||||||
|
f'**Description**: {raw_text}\n\n'
|
||||||
|
f'Type `!confirm` to create this group or `!cancel` to abort.'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_step == 'confirm':
|
||||||
|
await event.respond('❓ Please type `!confirm` to create the group or `!cancel` to abort.')
|
||||||
|
return
|
||||||
|
|
||||||
# ==================== ADMIN COMMANDS ====================
|
# ==================== ADMIN COMMANDS ====================
|
||||||
# All admin commands require being in the admin chat
|
# All admin commands require being in the admin chat
|
||||||
|
|
||||||
@@ -178,36 +253,7 @@ async def main():
|
|||||||
# ==================== GROUP COMMANDS ====================
|
# ==================== GROUP COMMANDS ====================
|
||||||
|
|
||||||
elif text.startswith('/create_group'):
|
elif text.startswith('/create_group'):
|
||||||
# Parse group name and optional user IDs
|
await event.respond("ℹ️ `/create_group` has been replaced. Use `!newgrp` in the staff chat.")
|
||||||
parts = text.split(maxsplit=2)
|
|
||||||
if len(parts) > 1:
|
|
||||||
group_name = parts[1]
|
|
||||||
user_ids = None
|
|
||||||
if len(parts) > 2:
|
|
||||||
try:
|
|
||||||
user_ids = [int(uid) for uid in parts[2].split(',')]
|
|
||||||
except ValueError:
|
|
||||||
await event.respond("❌ Invalid user IDs format.\n\nUsage: `/create_group <name>` or `/create_group <name> <user_id1>,<user_id2>,...`")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Create the group
|
|
||||||
response = await group_handler.handle_create_group(group_name, user_ids)
|
|
||||||
|
|
||||||
# Ask about admin role with inline buttons
|
|
||||||
buttons = [
|
|
||||||
[
|
|
||||||
Button.inline("✅ Yes, I want to be full admin", b"admin_role:yes"),
|
|
||||||
Button.inline("❌ No, just regular member", b"admin_role:no"),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
await event.respond(
|
|
||||||
f"{response}\n\n" +
|
|
||||||
"👤 Would you like to be added as a full admin to this group?",
|
|
||||||
buttons=buttons
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await event.respond("❌ Please provide a group name.\n\nUsage: `/create_group <name>` or `/create_group <name> <user_id1>,<user_id2>,...`")
|
|
||||||
|
|
||||||
elif text.startswith('/add_users'):
|
elif text.startswith('/add_users'):
|
||||||
# Parse group ID and user IDs
|
# Parse group ID and user IDs
|
||||||
@@ -287,8 +333,8 @@ async def main():
|
|||||||
• `/delete_user <user_id>` - Delete user
|
• `/delete_user <user_id>` - Delete user
|
||||||
|
|
||||||
**Group Management:**
|
**Group Management:**
|
||||||
• `/create_group <name>` - Create group with default users
|
• `!newgrp` - Create a group with database users and stored GroupHelp QR import
|
||||||
• `/create_group <name> <id1>,<id2>` - Create group with specific users
|
• `/create_group` - Deprecated; use `!newgrp`
|
||||||
• `/add_users <group_id> <id1>,<id2>` - Add users to group
|
• `/add_users <group_id> <id1>,<id2>` - Add users to group
|
||||||
• `/get_group <group_id>` - Get group info
|
• `/get_group <group_id>` - Get group info
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ class User:
|
|||||||
if self.id is None:
|
if self.id is None:
|
||||||
raise ValueError("User ID cannot be None")
|
raise ValueError("User ID cannot be None")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
parts = [part for part in [self.first_name, self.last_name] if part]
|
||||||
|
return " ".join(parts) or self.username or str(self.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict):
|
def from_dict(cls, data: dict):
|
||||||
"""Create User instance from dictionary"""
|
"""Create User instance from dictionary"""
|
||||||
|
|||||||
@@ -1,32 +1,135 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
import asyncio
|
||||||
from src.models.user import User
|
from typing import Awaitable, Callable, List, Optional
|
||||||
|
|
||||||
|
from telethon import functions, types
|
||||||
|
from telethon.errors.rpcerrorlist import PeerFloodError, UserPrivacyRestrictedError
|
||||||
|
from telethon.tl.functions.channels import CreateChannelRequest, InviteToChannelRequest
|
||||||
|
|
||||||
from src.services.mongodb_service import MongoDBService
|
from src.services.mongodb_service import MongoDBService
|
||||||
from src.config import get_telegram_client, get_default_group_users
|
from src.config import get_default_group_users, get_qr_data
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
StatusCallback = Callable[[str], Awaitable[object]]
|
||||||
|
|
||||||
class GroupService:
|
class GroupService:
|
||||||
"""Service class for group creation and management"""
|
"""Service class for group creation and management"""
|
||||||
|
|
||||||
def __init__(self, mongo_service: MongoDBService):
|
def __init__(self, mongo_service: MongoDBService, telegram_client=None):
|
||||||
self.mongo_service = mongo_service
|
self.mongo_service = mongo_service
|
||||||
self.client = get_telegram_client()
|
self.client = telegram_client
|
||||||
|
|
||||||
async def create_group(self, group_name: str, user_ids: List[int] = None) -> Optional[str]:
|
def set_client(self, telegram_client):
|
||||||
"""Create a Telegram group with specified users or default users"""
|
self.client = telegram_client
|
||||||
|
|
||||||
|
async def _notify(self, status_callback: Optional[StatusCallback], message: str):
|
||||||
|
if status_callback:
|
||||||
|
return await status_callback(message)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _edit_status(self, status_message, message: str):
|
||||||
|
if hasattr(status_message, "edit"):
|
||||||
|
await status_message.edit(message)
|
||||||
|
else:
|
||||||
|
await self.client.edit_message(status_message, message)
|
||||||
|
|
||||||
|
def _admin_rights(self, **rights):
|
||||||
|
try:
|
||||||
|
return types.ChatAdminRights(**rights)
|
||||||
|
except TypeError:
|
||||||
|
rights.pop("manage_topics", None)
|
||||||
|
return types.ChatAdminRights(**rights)
|
||||||
|
|
||||||
|
async def _promote_user(self, channel, user_id, full_admin: bool):
|
||||||
|
if full_admin:
|
||||||
|
admin_rights = self._admin_rights(
|
||||||
|
change_info=True,
|
||||||
|
post_messages=True,
|
||||||
|
edit_messages=True,
|
||||||
|
delete_messages=True,
|
||||||
|
ban_users=True,
|
||||||
|
invite_users=True,
|
||||||
|
pin_messages=True,
|
||||||
|
add_admins=True,
|
||||||
|
anonymous=False,
|
||||||
|
manage_call=True,
|
||||||
|
other=True,
|
||||||
|
manage_topics=True,
|
||||||
|
)
|
||||||
|
rank = "manager"
|
||||||
|
else:
|
||||||
|
admin_rights = self._admin_rights(
|
||||||
|
change_info=False,
|
||||||
|
post_messages=True,
|
||||||
|
edit_messages=True,
|
||||||
|
delete_messages=True,
|
||||||
|
ban_users=True,
|
||||||
|
invite_users=True,
|
||||||
|
pin_messages=True,
|
||||||
|
add_admins=False,
|
||||||
|
anonymous=False,
|
||||||
|
manage_call=True,
|
||||||
|
other=False,
|
||||||
|
manage_topics=True,
|
||||||
|
)
|
||||||
|
rank = "admin"
|
||||||
|
|
||||||
|
await self.client(functions.channels.EditAdminRequest(
|
||||||
|
channel=channel,
|
||||||
|
user_id=user_id,
|
||||||
|
admin_rights=admin_rights,
|
||||||
|
rank=rank,
|
||||||
|
))
|
||||||
|
|
||||||
|
async def _export_invite_link(self, channel) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
invite = await self.client(functions.messages.ExportChatInviteRequest(
|
||||||
|
peer=channel,
|
||||||
|
))
|
||||||
|
return getattr(invite, "link", None)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to export invite link: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _build_importbackup_message(self, qr_data: str) -> str:
|
||||||
|
qr_data = qr_data.strip()
|
||||||
|
if qr_data.lower().startswith((".importbackup", "/importbackup")):
|
||||||
|
return qr_data
|
||||||
|
return f".importbackup {qr_data}"
|
||||||
|
|
||||||
|
async def create_group(
|
||||||
|
self,
|
||||||
|
group_name: str,
|
||||||
|
description: str = "",
|
||||||
|
user_ids: List[int] = None,
|
||||||
|
status_callback: Optional[StatusCallback] = None,
|
||||||
|
staff_chat_id: Optional[int] = None,
|
||||||
|
factory_bot_id: Optional[int] = None,
|
||||||
|
) -> Optional[dict]:
|
||||||
|
"""Create a Telegram megagroup and run the IMS GroupFactory setup flow."""
|
||||||
try:
|
try:
|
||||||
if not self.client:
|
if not self.client:
|
||||||
logger.error("Telegram client not initialized")
|
logger.error("Telegram client not initialized")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Use default users if none specified
|
await self._notify(status_callback, f'🚀 Creating group "{group_name}"...')
|
||||||
|
created_channel = await self.client(CreateChannelRequest(
|
||||||
|
group_name,
|
||||||
|
description,
|
||||||
|
megagroup=True,
|
||||||
|
broadcast=False,
|
||||||
|
))
|
||||||
|
|
||||||
|
channel = created_channel.chats[0]
|
||||||
|
target_group = types.InputPeerChannel(channel.id, channel.access_hash)
|
||||||
|
await self._notify(status_callback, f'✅ Group "{group_name}" created successfully!')
|
||||||
|
|
||||||
if user_ids is None or len(user_ids) == 0:
|
if user_ids is None or len(user_ids) == 0:
|
||||||
user_ids = get_default_group_users()
|
user_ids = get_default_group_users()
|
||||||
if len(user_ids) == 0:
|
if len(user_ids) == 0:
|
||||||
logger.warning("No default users configured for group creation")
|
logger.warning("No default users configured for group creation")
|
||||||
|
|
||||||
# Get users from MongoDB
|
|
||||||
users = []
|
users = []
|
||||||
for user_id in user_ids:
|
for user_id in user_ids:
|
||||||
user = self.mongo_service.get_user_by_id(user_id)
|
user = self.mongo_service.get_user_by_id(user_id)
|
||||||
@@ -35,19 +138,98 @@ class GroupService:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"User {user_id} not found in database")
|
logger.warning(f"User {user_id} not found in database")
|
||||||
|
|
||||||
if not users:
|
await self._notify(status_callback, f"👥 Found {len(users)} configured users to add")
|
||||||
logger.warning("No valid users found for group creation")
|
status_message = await self._notify(status_callback, "👥 Adding users to the group...")
|
||||||
|
success_count = 0
|
||||||
|
error_count = 0
|
||||||
|
|
||||||
|
for index, user in enumerate(users):
|
||||||
|
try:
|
||||||
|
if index % 5 == 0 and status_message:
|
||||||
|
await self._edit_status(
|
||||||
|
status_message,
|
||||||
|
f"👥 Adding users: {index}/{len(users)} completed",
|
||||||
|
)
|
||||||
|
|
||||||
|
user_ref = user.username or user.id
|
||||||
|
user_to_add = await self.client.get_input_entity(user_ref)
|
||||||
|
await self.client(InviteToChannelRequest(target_group, [user_to_add]))
|
||||||
|
await self._promote_user(target_group, user_to_add, full_admin=False)
|
||||||
|
success_count += 1
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
except PeerFloodError:
|
||||||
|
logger.warning("Telegram flood limit reached while adding users")
|
||||||
|
await self._notify(status_callback, "⚠️ Telegram flood limit reached. Pausing for 30 seconds...")
|
||||||
|
await asyncio.sleep(30)
|
||||||
|
except UserPrivacyRestrictedError:
|
||||||
|
logger.warning(f"User {user.username or user.id} has privacy restrictions")
|
||||||
|
error_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error adding user {user.username or user.id}: {e}")
|
||||||
|
error_count += 1
|
||||||
|
if error_count > 10:
|
||||||
|
await self._notify(status_callback, "❌ Too many errors, aborting user addition!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create group using Telegram API
|
if status_message:
|
||||||
result = await self.client.create_group(group_name, [user.id for user in users])
|
await self._edit_status(
|
||||||
|
status_message,
|
||||||
|
f"✅ Added {success_count}/{len(users)} users to the group",
|
||||||
|
)
|
||||||
|
|
||||||
if result:
|
if factory_bot_id:
|
||||||
logger.info(f"Successfully created group '{group_name}' with {len(users)} members")
|
try:
|
||||||
return result.id
|
logger.info(f"Promoting factory bot {factory_bot_id} as manager")
|
||||||
|
factory_bot = await self.client.get_input_entity(factory_bot_id)
|
||||||
|
try:
|
||||||
|
await self.client(InviteToChannelRequest(target_group, [factory_bot]))
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"Factory bot invite skipped or failed before promotion: {e}")
|
||||||
|
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}")
|
||||||
|
|
||||||
|
await self._notify(status_callback, "⚙️ Sending GroupHelp setup commands...")
|
||||||
|
await self.client.send_message(target_group, "/pro")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
if staff_chat_id:
|
||||||
|
await self.client.send_message(target_group, f"/setstaffgroup {staff_chat_id}")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
qr_imported = False
|
||||||
|
qr_data = get_qr_data()
|
||||||
|
if qr_data:
|
||||||
|
logger.info("Sending GroupHelp QR backup import command")
|
||||||
|
await self.client.send_message(
|
||||||
|
target_group,
|
||||||
|
self._build_importbackup_message(qr_data),
|
||||||
|
)
|
||||||
|
qr_imported = True
|
||||||
|
if staff_chat_id:
|
||||||
|
await self.client.send_message(staff_chat_id, "✅ GroupHelp QR backup import sent to the new group.")
|
||||||
|
elif staff_chat_id:
|
||||||
|
await self.client.send_message(staff_chat_id, "⚠️ No GroupHelp QR backup data configured. Use `/admin_set_qr <qr_data>`.")
|
||||||
|
|
||||||
|
invite_link = await self._export_invite_link(target_group)
|
||||||
|
if staff_chat_id:
|
||||||
|
if invite_link:
|
||||||
|
await self.client.send_message(staff_chat_id, f"🔗 Invite link:\n\n{invite_link}")
|
||||||
else:
|
else:
|
||||||
logger.error("Failed to create group")
|
await self.client.send_message(staff_chat_id, "⚠️ Failed to generate invite link")
|
||||||
return None
|
|
||||||
|
logger.info(f"Group creation process completed for {group_name}")
|
||||||
|
return {
|
||||||
|
"id": channel.id,
|
||||||
|
"access_hash": channel.access_hash,
|
||||||
|
"title": channel.title,
|
||||||
|
"invite_link": invite_link,
|
||||||
|
"users_added": success_count,
|
||||||
|
"users_total": len(users),
|
||||||
|
"qr_imported": qr_imported,
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating group '{group_name}': {e}")
|
logger.error(f"Error creating group '{group_name}': {e}")
|
||||||
@@ -60,7 +242,6 @@ class GroupService:
|
|||||||
logger.error("Telegram client not initialized")
|
logger.error("Telegram client not initialized")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Get users from MongoDB
|
|
||||||
users = []
|
users = []
|
||||||
for user_id in user_ids:
|
for user_id in user_ids:
|
||||||
user = self.mongo_service.get_user_by_id(user_id)
|
user = self.mongo_service.get_user_by_id(user_id)
|
||||||
@@ -73,15 +254,14 @@ class GroupService:
|
|||||||
logger.warning("No valid users found for adding to group")
|
logger.warning("No valid users found for adding to group")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Add users to group using Telegram API
|
group = await self.client.get_input_entity(group_id)
|
||||||
result = await self.client.add_users_to_group(group_id, [user.id for user in users])
|
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]))
|
||||||
|
|
||||||
if result:
|
|
||||||
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
|
||||||
else:
|
|
||||||
logger.error("Failed to add users to group")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding users to group {group_id}: {e}")
|
logger.error(f"Error adding users to group {group_id}: {e}")
|
||||||
@@ -94,15 +274,13 @@ class GroupService:
|
|||||||
logger.error("Telegram client not initialized")
|
logger.error("Telegram client not initialized")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get group info using Telegram API
|
result = await self.client.get_entity(group_id)
|
||||||
result = await self.client.get_group_info(group_id)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
logger.info(f"Retrieved information for group {group_id}")
|
logger.info(f"Retrieved information for group {group_id}")
|
||||||
return result
|
return {
|
||||||
else:
|
"id": getattr(result, "id", None),
|
||||||
logger.warning(f"Failed to retrieve information for group {group_id}")
|
"title": getattr(result, "title", None),
|
||||||
return None
|
"username": getattr(result, "username", None),
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error retrieving group info for {group_id}: {e}")
|
logger.error(f"Error retrieving group info for {group_id}: {e}")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class MongoDBService:
|
|||||||
def get_users(self) -> List[User]:
|
def get_users(self) -> List[User]:
|
||||||
"""Retrieve all users from MongoDB collection"""
|
"""Retrieve all users from MongoDB collection"""
|
||||||
try:
|
try:
|
||||||
if not self.collection:
|
if self.collection is None:
|
||||||
logger.error("MongoDB collection not initialized")
|
logger.error("MongoDB collection not initialized")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class MongoDBService:
|
|||||||
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||||
"""Retrieve a specific user by ID from MongoDB"""
|
"""Retrieve a specific user by ID from MongoDB"""
|
||||||
try:
|
try:
|
||||||
if not self.collection:
|
if self.collection is None:
|
||||||
logger.error("MongoDB collection not initialized")
|
logger.error("MongoDB collection not initialized")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ class MongoDBService:
|
|||||||
def save_user(self, user: User) -> bool:
|
def save_user(self, user: User) -> bool:
|
||||||
"""Save a user to MongoDB collection"""
|
"""Save a user to MongoDB collection"""
|
||||||
try:
|
try:
|
||||||
if not self.collection:
|
if self.collection is None:
|
||||||
logger.error("MongoDB collection not initialized")
|
logger.error("MongoDB collection not initialized")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ class MongoDBService:
|
|||||||
def delete_user(self, user_id: int) -> bool:
|
def delete_user(self, user_id: int) -> bool:
|
||||||
"""Delete a user from MongoDB collection"""
|
"""Delete a user from MongoDB collection"""
|
||||||
try:
|
try:
|
||||||
if not self.collection:
|
if self.collection is None:
|
||||||
logger.error("MongoDB collection not initialized")
|
logger.error("MongoDB collection not initialized")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user