Skip to content

Commit be301b5

Browse files
committed
feat: many features
1 parent 0683037 commit be301b5

File tree

19 files changed

+498
-492
lines changed

19 files changed

+498
-492
lines changed

Dockerfile

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
# Use the specified Python version
2-
FROM python:3.10.11-slim
1+
# Stage 1: Build stage
2+
FROM python:3.10.11-alpine AS builder
33

4-
# Set the working directory in the container
54
WORKDIR /app
65

7-
# Copy the requirements file into the container
6+
# Install build dependencies
7+
RUN apk add --no-cache build-base libffi-dev openssl-dev
8+
9+
# Copy requirements and install them
810
COPY requirements.txt .
11+
RUN pip install --no-cache-dir --upgrade pip && \
12+
pip install --no-cache-dir -r requirements.txt
13+
14+
# Stage 2: Final stage
15+
FROM python:3.10.11-alpine
16+
17+
WORKDIR /app
18+
19+
# Install runtime dependencies, including git for module management
20+
RUN apk add --no-cache libffi openssl git
921

10-
# Install dependencies
11-
RUN pip install --no-cache-dir -r requirements.txt
22+
# Copy installed packages from the builder stage
23+
COPY --from=builder /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/
24+
COPY --from=builder /usr/local/bin/ /usr/local/bin/
1225

13-
# Copy the rest of the application code into the container
26+
# Copy the application source code
1427
COPY . .
1528

16-
# Command to run the userbot when the container starts
17-
# The --accounts flag should be provided via docker-compose `command` override
29+
# Create the modules directory if it doesn't exist
30+
RUN mkdir -p /app/userbot/modules
31+
32+
# Set the entrypoint
1833
CMD ["python3", "-m", "userbot"]

docs/getting-started/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ services:
3434
restart: unless-stopped
3535
volumes:
3636
# Эта папка будет создана на вашем хосте для хранения модулей
37-
- ./userbot/modules:/app/userbot/modules
37+
- ./modules:/app/userbot/modules
3838
depends_on:
3939
db:
4040
condition: service_healthy
@@ -109,7 +109,7 @@ docker compose up -d
109109

110110
Docker скачает готовый образ DeBot, образ PostgreSQL и запустит всё в фоновом режиме.
111111

112-
**Готово!** Переходите к [следующему шагу: добавлению вашего первого аккаунта](docs/getting-started/first-run.md).
112+
**Готово!** Переходите к [следующему шагу: добавлению вашего первого аккаунта](first-run.md).
113113

114114
---
115115

scripts/setup.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
if str(project_root) not in sys.path:
1111
sys.path.insert(0, str(project_root))
1212

13-
from userbot.src.encrypt import EncryptionManager
13+
from userbot.utils.encrypt import EncryptionManager
1414

1515
def prompt_user(prompt_text: str, default: str = None) -> str:
1616
"""Prompts the user for input with an optional default value."""
@@ -74,7 +74,9 @@ def main():
7474
'',
7575
'# --- Application Settings ---',
7676
'LOG_LEVEL=INFO',
77-
'GC_INTERVAL_SECONDS=60',
77+
'LOG_ROTATION_ENABLED=True',
78+
'LOG_RETENTION_DAYS=30',
79+
'GC_INTERVAL_SECONDS=3600',
7880
'',
7981
'# --- Auto Update (only for "image" deployment) ---',
8082
'AUTO_UPDATE_ENABLED=False',
@@ -96,13 +98,14 @@ def main():
9698

9799
userbot_service_definition = "build: ." if deploy_type == "source" else f"image: whn0thacked/debot:latest"
98100
compose_content = compose_template.replace("${USERBOT_SERVICE_DEFINITION}", userbot_service_definition)
101+
102+
modules_volume = " volumes:\n - ./userbot/modules:/app/userbot/modules" if deploy_type == 'source' else " volumes:\n - ./modules:/app/userbot/modules"
103+
compose_content = compose_content.replace("${MODULES_VOLUME_DEFINITION}", modules_volume)
99104

100105
if not use_docker_db:
101-
# Remove the 'db' service and dependencies on it
102-
compose_content = re.sub(r'^\s*depends_on:.*?service_healthy\s*$', '', compose_content, flags=re.DOTALL | re.MULTILINE)
106+
compose_content = re.sub(r'^\s*depends_on:.*?service_healthy\s*\n', '', compose_content, flags=re.DOTALL | re.MULTILINE)
103107
compose_content = re.sub(r'^\s*db:.*?(?=\n\S|\Z)', '', compose_content, flags=re.DOTALL | re.MULTILINE)
104-
# Remove the volumes key if it's now empty and only contains postgres_data
105-
compose_content = re.sub(r'^\s*volumes:\s*\n\s*postgres_data:\s*$', '', compose_content, flags=re.MULTILINE)
108+
compose_content = re.sub(r'^\s*volumes:\s*\n\s*postgres_data:\s*\n', '', compose_content, flags=re.MULTILINE)
106109

107110
compose_file_path = project_root / "docker-compose.yml"
108111
with open(compose_file_path, "w") as f:

userbot/__init__.py

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111
from telethon.errors.rpcerrorlist import UserAlreadyParticipantError
1212
from python_socks import ProxyType
1313

14-
from userbot.src.config import API_ID, API_HASH, LOG_LEVEL
15-
from userbot.src.db.session import initialize_database, get_db
16-
from userbot.src.db.models import Account
17-
from userbot.src.encrypt import encryption_manager
18-
import userbot.src.db_manager as db_manager
19-
from userbot.src.log_handler import DBLogHandler
20-
from userbot.src.locales import translator
14+
from userbot.core.config import API_ID, API_HASH, LOG_LEVEL
15+
from userbot.db.session import initialize_database, get_db
16+
from userbot.db.models import Account
17+
from userbot.utils.encrypt import encryption_manager
18+
from userbot.db import db_manager
19+
from userbot.core.log_handler import DBLogHandler
20+
from userbot.core.locales import translator
2121

2222
logger: logging.Logger = logging.getLogger("userbot")
2323
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
2424

2525
ACTIVE_CLIENTS: Dict[int, "TelegramClient"] = {}
2626
FAKE: Faker = Faker()
27-
GLOBAL_HELP_INFO: Dict[int, Dict[str, str]] = {}
27+
GLOBAL_HELP_INFO: Dict[int, Dict[str, Any]] = {}
2828

2929
def _generate_random_device() -> Dict[str, str]:
3030
return {
@@ -35,7 +35,6 @@ def _generate_random_device() -> Dict[str, str]:
3535

3636
class TelegramClient(TelethonTelegramClient):
3737
def __init__(self, *args, **kwargs):
38-
# Pop the custom argument before it's passed to the parent constructor
3938
self.account_id: Optional[int] = kwargs.pop('account_id', None)
4039
super().__init__(*args, **kwargs)
4140
self.lang_code: str = 'ru'
@@ -48,16 +47,20 @@ async def get_string(self, key: str, module_name: Optional[str] = None, **kwargs
4847
return translator.get_string(self.lang_code, key, module_name, **kwargs)
4948

5049
def get_all_clients(self) -> List["TelegramClient"]:
51-
"""
52-
Returns a list of all active client instances.
53-
54-
This method is intended for use in trusted modules for multi-account operations.
55-
56-
Returns:
57-
List[TelegramClient]: A list of all currently active and authorized clients.
58-
"""
5950
return list(ACTIVE_CLIENTS.values())
6051

52+
async def get_module_config(self, module_name: str) -> Dict[str, Any]:
53+
if not self.account_id:
54+
return {}
55+
56+
async with get_db() as db:
57+
module = await db_manager.get_module_by_name(db, module_name)
58+
if not module:
59+
return {}
60+
61+
config = await db_manager.get_module_config(db, self.account_id, module.module_id)
62+
return config or {}
63+
6164
async def db_setup() -> None:
6265
if not API_ID or not API_HASH:
6366
logger.critical("API_ID or API_HASH is not set. Please run 'python3 -m scripts.setup'. Exiting.")
@@ -77,12 +80,12 @@ async def db_setup() -> None:
7780
logger.info("Database schema checked and DB logger attached.")
7881

7982
async def start_individual_client(client: TelegramClient, account: Account) -> None:
80-
from userbot.src.core_handlers import (
83+
from userbot.handlers.core_handlers import (
8184
load_account_modules, help_commands_handler, about_command_handler,
8285
add_account_handler, delete_account_handler, toggle_account_handler,
8386
list_accounts_handler, set_lang_handler, addmod_handler, delmod_handler,
84-
trustmod_handler, configmod_handler, update_modules_handler, logs_handler,
85-
restart_handler, ping_handler
87+
trustmod_handler, configmod_handler, update_module_handler, update_modules_handler,
88+
logs_handler, restart_handler, ping_handler
8689
)
8790

8891
client.lang_code = account.lang_code
@@ -95,27 +98,24 @@ async def start_individual_client(client: TelegramClient, account: Account) -> N
9598
logger.info(f"Client for account '{account.account_name}' (ID: {account_id}) is authorized.")
9699
GLOBAL_HELP_INFO[account_id] = {}
97100

98-
# Register all core handlers
99-
client.add_event_handler(help_commands_handler, events.NewMessage(outgoing=True, pattern=r"^\.help$"))
101+
client.add_event_handler(help_commands_handler, events.NewMessage(outgoing=True, pattern=r"^\.help(?:\s+(.+))?$"))
100102
client.add_event_handler(about_command_handler, events.NewMessage(outgoing=True, pattern=r"^\.about$"))
101-
# Account Management
102103
client.add_event_handler(list_accounts_handler, events.NewMessage(outgoing=True, pattern=r"^\.listaccs$"))
103104
client.add_event_handler(add_account_handler, events.NewMessage(outgoing=True, pattern=r"^\.addacc\s+([a-zA-Z0-9_]+)$"))
104105
client.add_event_handler(delete_account_handler, events.NewMessage(outgoing=True, pattern=r"^\.delacc\s+([a-zA-Z0-9_]+)$"))
105106
client.add_event_handler(toggle_account_handler, events.NewMessage(outgoing=True, pattern=r"^\.toggleacc\s+([a-zA-Z0-9_]+)$"))
106107
client.add_event_handler(set_lang_handler, events.NewMessage(outgoing=True, pattern=r"^\.setlang\s+(.+)"))
107-
# Module Management
108-
client.add_event_handler(addmod_handler, events.NewMessage(outgoing=True, pattern=r"^\.addmod$"))
108+
client.add_event_handler(addmod_handler, events.NewMessage(outgoing=True, pattern=r"^\.addmod\s+(.+)"))
109109
client.add_event_handler(delmod_handler, events.NewMessage(outgoing=True, pattern=r"^\.delmod\s+(.+)"))
110110
client.add_event_handler(trustmod_handler, events.NewMessage(outgoing=True, pattern=r"^\.trustmod\s+(.+)"))
111111
client.add_event_handler(configmod_handler, events.NewMessage(outgoing=True, pattern=r"^\.configmod\s+(.+)"))
112-
# Utilities
113112
client.add_event_handler(ping_handler, events.NewMessage(outgoing=True, pattern=r"^\.ping$"))
114113
client.add_event_handler(restart_handler, events.NewMessage(outgoing=True, pattern=r"^\.restart$"))
114+
client.add_event_handler(update_module_handler, events.NewMessage(outgoing=True, pattern=r"^\.updatemodule\s+(.+)"))
115115
client.add_event_handler(update_modules_handler, events.NewMessage(outgoing=True, pattern=r"^\.updatemodules$"))
116116
client.add_event_handler(logs_handler, events.NewMessage(outgoing=True, pattern=r"^\.logs.*"))
117117

118-
await load_account_modules(account_id, client, GLOBAL_HELP_INFO[account_id])
118+
await load_account_modules(account_id, client)
119119

120120
try:
121121
await client(JoinChannelRequest('https://t.me/DeBot_userbot'))
@@ -184,14 +184,9 @@ async def manage_clients() -> None:
184184
continue
185185

186186
new_client: TelegramClient = TelegramClient(
187-
session=session_instance,
188-
api_id=int(acc_api_id),
189-
api_hash=acc_api_hash,
190-
device_model=account.device_model,
191-
system_version=account.system_version,
192-
app_version=account.app_version,
193-
proxy=proxy_details,
194-
account_id=account.account_id
187+
session=session_instance, api_id=int(acc_api_id), api_hash=acc_api_hash,
188+
device_model=account.device_model, system_version=account.system_version,
189+
app_version=account.app_version, proxy=proxy_details, account_id=account.account_id
195190
)
196191
ACTIVE_CLIENTS[account.account_id] = new_client
197192
tasks.append(start_individual_client(new_client, account))

userbot/__main__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import gc
33
import logging
44
import sys
5-
from typing import Dict, Any
65

76
from apscheduler.schedulers.asyncio import AsyncIOScheduler
87
from rich.console import Console
@@ -11,10 +10,10 @@
1110
from userbot import (
1211
db_setup, manage_clients
1312
)
14-
from userbot.src.config import GC_INTERVAL_SECONDS, LOG_QUEUE_INTERVAL_SECONDS, TIMEZONE, LOG_ROTATION_ENABLED, LOG_RETENTION_DAYS
15-
from userbot.src.db.session import get_db
16-
import userbot.src.db_manager as db_manager
17-
from userbot.src.log_handler import log_queue
13+
from userbot.core.config import GC_INTERVAL_SECONDS, LOG_QUEUE_INTERVAL_SECONDS, TIMEZONE, LOG_ROTATION_ENABLED, LOG_RETENTION_DAYS
14+
from userbot.db.session import get_db
15+
from userbot.db import db_manager
16+
from userbot.core.log_handler import log_queue
1817

1918
# Suppress noisy APScheduler logs
2019
logging.getLogger('apscheduler').setLevel(logging.WARNING)
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,8 @@ def get_string(
136136
"""
137137
string_val: Optional[str] = None
138138

139-
# 1. Try module-specific locales
140139
if module_name:
141-
module_locales_path: Path = Path("userbot/modules") / module_name / "locales"
142-
# Try requested lang, then ru, then en for the module
140+
module_locales_path: Path = Path(f"userbot/modules/{module_name}/locales")
143141
for lang in (lang_code, 'ru', 'en'):
144142
locale_data = self._load_locale_file(module_locales_path / f"{lang}.json")
145143
if locale_data and key in locale_data:
@@ -148,7 +146,6 @@ def get_string(
148146
if string_val:
149147
return string_val.format(**kwargs) if kwargs else string_val
150148

151-
# 2. Try core locales
152149
for lang in (lang_code, 'ru', 'en'):
153150
core_locale_data = self._load_locale_file(self.core_locales_path / f"{lang}.json")
154151
if core_locale_data and key in core_locale_data:
@@ -158,7 +155,6 @@ def get_string(
158155
if string_val:
159156
return string_val.format(**kwargs) if kwargs else string_val
160157

161-
# 3. Fallback
162158
logger.warning(f"Translation key '{key}' not found for lang '{lang_code}' (module: {module_name}).")
163159
return key
164160

0 commit comments

Comments
 (0)