diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..96cf3c9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,13 @@
+blank_issues_enabled: true
+contact_links:
+ - name: WGDashboard Community Support (Github)
+ url: https://github.com/orgs/WGDashboard/discussions
+ about: Please ask and answer questions here.
+
+ - name: WGDashboard Community Support (Discord)
+ url: https://discord.gg/72TwzjeuWm
+ about: Discord Server about WGDashboard
+
+ - name: WGDashboard Issue reporting (Bugs and Feature Requests (FR))
+ url: https://github.com/WGDashboard/WGDashboard/issues
+ about: Please report bugs or feature requests here so they can be tracked
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..c3aaf24
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/src"
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "github-actions"
+ directory: "/.github"
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "docker"
+ directory: "/docker"
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "docker-compose"
+ directory: "/docker"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/codeql-analyze.yaml b/.github/workflows/codeql-analyze.yaml
new file mode 100644
index 0000000..f6b2267
--- /dev/null
+++ b/.github/workflows/codeql-analyze.yaml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ main ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ main ]
+ schedule:
+ - cron: '30 5 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v4
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
diff --git a/.gitignore b/.gitignore
index 2eea525..2216582 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
-.env
\ No newline at end of file
+.env
+**venv**
+**cache**
+*.log
+src/data
\ No newline at end of file
diff --git a/README.md b/README.md
index 79a87f8..9032be2 100644
--- a/README.md
+++ b/README.md
@@ -1,484 +1,9 @@
-# 🤖 WGDashboard Telegram Bot
+# 🤖 WGDashboard Erinys (Telegram Bot)
-[**🇪🇸 Español**](#-español) | [**🇬🇧 English**](#-english)
+[**🇬🇧 English**](#-english) | [**🇪🇸 Español**](./docs/README_ESPANOL.md)
---
-## 🇪🇸 Español
-
-# 🤖 Bot de Telegram para WGDashboard
-
-Bot profesional de Telegram para administrar y consultar WireGuard mediante la API de WGDashboard. Permite a administradores gestionar peers, monitorear estado del servidor y ejecutar acciones administrativas directamente desde Telegram.
-
-## ✨ Características principales
-
-### Administradores
-- 📡 **Gestión de Configuraciones**: Ver todas las configuraciones WireGuard
-- 👥 **Gestión de Peers**: Crear, eliminar, ver detalles y monitorear peers
-- 🔒 **Restricciones**: Restringir/permitir acceso de peers específicos
-- 🧹 **Limpiar Tráfico**: Resetear contadores de datos de peers
-- ⏰ **Schedule Jobs**: Crear trabajos programados (límites de datos y fechas de expiración)
-- 🖥️ **Estado del Sistema**: Monitorear CPU, memoria, discos e interfaces
-- 📊 **Estadísticas**: Ver información detallada del sistema
-- 👷 **Supervisión de Operadores**: Ver actividad de operadores autorizados
-- 🌐 **Multiidioma**: Soporte para español e inglés
-
-### Operadores
-- ➕ **Crear Peers Temporales**: Generar peers con límites automáticos
-- 📥 **Descargar Config**: Obtener archivo `.conf` para el nuevo peer
-- 🌐 **Cambiar Idioma**: Seleccionar entre español e inglés
-- ⏳ **Límites Automáticos**: 1 GB de datos y 24 horas de duración por peer
-
-## 📁 Estructura del proyecto
-
-```
-TGBot-saveOK/
-├── main.py # Punto de entrada del bot
-├── config.py # Configuración y variables de entorno
-├── handlers.py # Handlers de comandos y callbacks (4100+ líneas)
-├── keyboards.py # Teclados inline de Telegram
-├── i18n.py # Sistema de traducción multiidioma
-├── operators.py # Control de operadores autorizados
-├── utils.py # Funciones utilitarias
-├── wg_api.py # Cliente de la API WGDashboard
-├── setup_logging.py # Configuración de logs
-├── .env # Variables de entorno (NO subir a git)
-├── requirements.txt # Dependencias del proyecto
-├── README.md # Este archivo
-├── locales/
-│ ├── es.json # Traducción al español
-│ └── en.json # Traducción al inglés
-└── info/
- ├── resumen.md # Documentación técnica
- ├── CAMBIOS_REALIZADOS.md # Historial de cambios
- └── IMPLEMENTACION_*.md # Documentación de features
-```
-
-## 🛠️ Requisitos previos
-
-### Sistema operativo
-- Linux (Ubuntu 20.04 LTS o superior recomendado)
-- Python 3.10 o superior
-- pip (Python Package Manager)
-
-### Dependencias externas
-- **WireGuard**: Instalado y configurado en el servidor
-- **WGDashboard**: Funcionando y accesible via API
-- **Bot de Telegram**: Creado con [@BotFather](https://t.me/BotFather)
-
-## 📦 Instalación paso a paso
-
-### 1. Clonar o descargar el proyecto
-
-```bash
-git clone https://github.com/tu-usuario/TGBot-saveOK.git
-cd TGBot-saveOK
-```
-
-### 2. Crear entorno virtual (recomendado)
-
-```bash
-python3 -m venv .venv
-source .venv/bin/activate # En Windows: .venv\Scripts\activate
-```
-
-### 3. Instalar dependencias
-
-```bash
-pip install -r requirements.txt
-```
-
-### 4. Configurar variables de entorno
-
-Crear archivo `.env` en la raíz del proyecto:
-
-```bash
-# ===== TELEGRAM =====
-TELEGRAM_BOT_TOKEN=tu_token_aqui # Obtener de @BotFather en Telegram
-
-# ===== WGDASHBOARD API =====
-WG_API_BASE_URL=https://tu-dominio.com/api # URL base de la API
-WG_API_KEY=tu_api_key_aqui # API Key de WGDashboard
-WG_API_PREFIX=wg # Prefijo de la API (si aplica)
-API_TIMEOUT=10 # Timeout en segundos
-
-# ===== LOGGING =====
-LOG_FILE=wg_bot.log # Archivo de logs
-LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR
-
-# ===== ACCESO =====
-ALLOWED_USERS=123456789,987654321 # IDs de Telegram autorizados
-ROLE_ADMIN=admin # Rol de administrador
-ROLE_OPERATOR=operator # Rol de operador
-ADMIN_IDS=123456789 # IDs que son administradores
-OPERATOR_IDS=987654321,111222333 # IDs que son operadores
-
-# ===== LÍMITES DE OPERADOR =====
-OPERATOR_DATA_LIMIT_GB=1 # GB por peer creado
-OPERATOR_TIME_LIMIT_HOURS=24 # Horas de duración del peer
-```
-
-### 5. Configurar usuarios autorizados
-
-Editar `config.py` y agregar los IDs de Telegram:
-
-```python
-# IDs autorizados a usar el bot
-ALLOWED_USERS = [
- 123456789, # Admin 1
- 987654321, # Admin 2
- 111222333, # Operador 1
-]
-
-# Configuración de roles
-ADMIN_USERS = [123456789, 987654321]
-OPERATOR_USERS = [111222333]
-
-# Límites para operadores
-OPERATOR_DATA_LIMIT_GB = 1 # 1 GB por peer
-OPERATOR_TIME_LIMIT_HOURS = 24 # 24 horas de duración
-```
-
-**Para obtener tu ID de Telegram:**
-1. Abre [@userinfobot](https://t.me/userinfobot) en Telegram
-2. El bot te mostrará tu ID
-
-## 🚀 Ejecutar el bot
-
-### Desarrollo local
-
-```bash
-# Con el entorno virtual activado
-python main.py
-```
-
-### Producción en VPS
-
-#### Opción 1: Usando tmux (simple)
-
-```bash
-# Instalar tmux
-sudo apt-get install tmux
-
-# Crear sesión
-tmux new-session -d -s wgbot -c /ruta/al/bot python main.py
-
-# Ver sesión
-tmux list-sessions
-
-# Conectar a sesión
-tmux attach-session -t wgbot
-
-# Desconectar (Ctrl+b, luego d)
-```
-
-#### Opción 2: Usando systemd (recomendado)
-
-**Crear archivo de servicio:**
-
-```bash
-sudo nano /etc/systemd/system/wgbot.service
-```
-
-**Contenido del archivo:**
-
-```ini
-[Unit]
-Description=WGDashboard Telegram Bot
-After=network-online.target
-Wants=network-online.target
-
-[Service]
-Type=simple
-User=wgbot
-Group=wgbot
-WorkingDirectory=/home/wgbot/TGBot-saveOK
-Environment="PATH=/home/wgbot/TGBot-saveOK/.venv/bin"
-ExecStart=/home/wgbot/TGBot-saveOK/.venv/bin/python main.py
-Restart=always
-RestartSec=10
-StandardOutput=journal
-StandardError=journal
-
-[Install]
-WantedBy=multi-user.target
-```
-
-**Habilitar e iniciar el servicio:**
-
-```bash
-# Recargar systemd
-sudo systemctl daemon-reload
-
-# Habilitar para que inicie automáticamente
-sudo systemctl enable wgbot
-
-# Iniciar el bot
-sudo systemctl start wgbot
-
-# Ver estado
-sudo systemctl status wgbot
-
-# Ver logs en tiempo real
-sudo journalctl -u wgbot -f
-
-# Detener
-sudo systemctl stop wgbot
-
-# Reiniciar
-sudo systemctl restart wgbot
-```
-
-#### Opción 3: Usando Docker (si está disponible)
-
-```dockerfile
-FROM python:3.11-slim
-
-WORKDIR /app
-
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
-
-COPY . .
-
-CMD ["python", "main.py"]
-```
-
-```bash
-# Construir imagen
-docker build -t wgbot .
-
-# Ejecutar contenedor
-docker run -d --name wgbot --env-file .env wgbot
-```
-
-## 📋 Comandos disponibles
-
-### Para Administradores
-
-| Comando | Descripción |
-|---------|------------|
-| `/start` | Inicia el bot y muestra menú principal |
-| `/help` | Muestra ayuda y comandos disponibles |
-| `/stats` | Muestra estadísticas del sistema |
-| `/configs` | Lista todas las configuraciones WireGuard |
-
-**Funciones via menú:**
-- 📡 Configuraciones - Gestionar todas las configs
-- 👥 Peers - Ver, crear, eliminar, listar
-- 🖥️ Estado del Sistema - CPU, RAM, discos, network
-- ⚡ Protocolos - Ver protocolos habilitados
-- 👷 Operadores - Ver actividad de operadores
-- 🌐 Idioma - Cambiar entre español e inglés
-
-### Para Operadores
-
-| Comando | Descripción |
-|---------|------------|
-| `/start` | Muestra menú para crear peers |
-| `/help` | Información sobre límites y funciones |
-
-**Funciones:**
-- ➕ Crear Peer - Generar nuevo peer temporal
-- 📥 Descargar - Obtener archivo de configuración
-- 🌐 Idioma - Cambiar idioma
-
-## 🔐 Seguridad y control de acceso
-
-### Niveles de permisos
-
-**Administrador (acceso completo)**
-- Todas las funciones del bot
-- Gestión total de peers
-- Acceso a estadísticas
-- Supervisión de operadores
-
-**Operador (acceso limitado)**
-- Solo crear peers temporales
-- Descargar configuración de peers creados
-- Sin acceso a gestión de peers existentes
-
-**No autorizado (sin acceso)**
-- El bot rechaza cualquier comando
-- Mensaje de error: "Acceso restringido"
-
-### Mejores prácticas de seguridad
-
-1. **API Key de WGDashboard**
- - Usar en variables de entorno `.env`
- - Nunca subir `.env` a git
- - Agregar a `.gitignore`
-
-2. **Token del Bot de Telegram**
- - Mantener en `.env`
- - Cambiar si se expone
- - Usar restricciones de webhook si es posible
-
-3. **Acceso al servidor**
- - Firewall activo
- - SSH con claves públicas
- - Cambiar puerto SSH por defecto
- - Monitorear logs regularmente
-
-4. **WGDashboard**
- - Detrás de proxy reverso (nginx)
- - HTTPS obligatorio
- - Autenticación fuerte
- - Backups regulares
-
-## 📊 Funcionalidades detalladas
-
-### Gestión de Peers
-
-#### Crear Peer
-- Nombre único
-- IP automática
-- Claves WireGuard generadas
-- Archivo `.conf` enviado directamente
-- Límites automáticos aplicados
-
-#### Detalles de Peer
-- Información de red (IP, endpoint, DNS)
-- Estado de conexión (conectado/desconectado)
-- Tráfico (enviado, recibido, acumulativo)
-- Claves (pública, pre-shared)
-- Trabajos programados
-- Enlaces compartidos
-
-#### Restricciones
-- Restringir peer (bloquea conexión)
-- Permitir access (quita restricción)
-- Visualización por página
-- Filtros disponibles
-
-### Schedule Jobs
-
-Los trabajos programados permiten:
-- **Límite de datos**: Restringir cuando alcanza X GB
-- **Fecha de expiración**: Restringir automáticamente en fecha X
-- Crear/eliminar jobs sobre la marcha
-- Gestión completa de tareas
-
-### Estadísticas del Sistema
-
-Monitorea en tiempo real:
-- 💻 Uso de CPU (%)
-- 🧠 Uso de RAM (% y detalles)
-- 💾 Uso de discos (principales)
-- 📡 Interfaces de red (tráfico)
-- 🔗 Interfaces WireGuard
-
-## 🌐 Multiidioma
-
-El bot soporta automáticamente:
-- 🇪🇸 **Español** - Interfaz completa en español
-- 🇬🇧 **Inglés** - Interfaz completa en inglés
-
-Los usuarios pueden cambiar el idioma desde el menú en cualquier momento. Las preferencias se guardan automáticamente.
-
-## 📝 Logs y debugging
-
-### Ver logs en tiempo real
-
-```bash
-# Si usas systemd
-sudo journalctl -u wgbot -f
-
-# Si ejecutas directamente
-tail -f wg_bot.log
-```
-
-### Niveles de log
-
-```
-DEBUG - Información detallada para debugging
-INFO - Eventos importantes
-WARNING - Advertencias (problemas potenciales)
-ERROR - Errores que afectan funcionamiento
-```
-
-### Configurar nivel de log
-
-Editar `.env`:
-```
-LOG_LEVEL=DEBUG # Para más detalle en desarrollo
-```
-
-## 🆘 Troubleshooting
-
-### El bot no responde
-
-```bash
-# Verificar que está corriendo
-sudo systemctl status wgbot
-
-# Ver logs
-sudo journalctl -u wgbot -n 50
-
-# Reiniciar
-sudo systemctl restart wgbot
-```
-
-### Error: "No se puede conectar a API"
-
-- Verificar `WG_API_BASE_URL` en `.env`
-- Verificar `WG_API_KEY` es correcta
-- Probar conectividad: `curl https://tu-url/api/handshake`
-- Verificar firewall/proxy permite la conexión
-
-### Error: "Token de Telegram inválido"
-
-- Verificar `TELEGRAM_BOT_TOKEN` en `.env`
-- Crear nuevo token con [@BotFather](https://t.me/BotFather)
-- Verificar no hay espacios en blanco
-
-### El bot no ve mis cambios
-
-```bash
-# Reiniciar para cargar cambios
-sudo systemctl restart wgbot
-
-# O si usas tmux
-tmux send-keys -t wgbot "C-c"
-python main.py
-```
-
-## 📚 Documentación adicional
-
-- `info/resumen.md` - Análisis técnico de la API
-- `info/CAMBIOS_REALIZADOS.md` - Historial de cambios
-- `info/IMPLEMENTACION_*.md` - Detalles de features
-
-## 🤝 Contribuciones
-
-Las contribuciones son bienvenidas. Por favor:
-
-1. Fork del proyecto
-2. Crear rama para tu feature (`git checkout -b feature/AmazingFeature`)
-3. Commit cambios (`git commit -m 'Add AmazingFeature'`)
-4. Push a rama (`git push origin feature/AmazingFeature`)
-5. Abrir Pull Request
-
-## 📄 Licencia
-
-Este proyecto se distribuye bajo la licencia MIT. Puedes usarlo, modificarlo y redistribuirlo libremente.
-
-## 👨💻 Autor
-
-**Jorge Elián Martinez Perdomo**
-
-Bot profesional de Telegram para administración de WireGuard usando WGDashboard
-
-- GitHub: [@jemartinezp1994](https://github.com/jemartinezp1994)
-- Telegram: [@jemp01K](https://t.me/jemp01K)
-- Discord: ff_c3r0
-
----
-
-## 🇬🇧 English
-
-# 🤖 Telegram Bot for WGDashboard
-
Professional Telegram bot to manage and monitor WireGuard through the WGDashboard API. Allows administrators to manage peers, monitor server status, and execute administrative actions directly from Telegram.
## ✨ Main Features
@@ -528,7 +53,7 @@ TGBot-saveOK/
## 🛠️ Prerequisites
### Operating System
-- Linux (Ubuntu 20.04 LTS or higher recommended)
+- Linux (Ubuntu 22.04 LTS or higher recommended)
- Python 3.10 or higher
- pip (Python Package Manager)
@@ -542,8 +67,8 @@ TGBot-saveOK/
### 1. Clone or download the project
```bash
-git clone https://github.com/your-username/TGBot-saveOK.git
-cd TGBot-saveOK
+git clone https://github.com/WGDashboard/WGErinys
+cd WGErinys
```
### 2. Create virtual environment (recommended)
@@ -625,25 +150,7 @@ python main.py
### Production on VPS
-#### Option 1: Using tmux (simple)
-
-```bash
-# Install tmux
-sudo apt-get install tmux
-
-# Create session
-tmux new-session -d -s wgbot -c /path/to/bot python main.py
-
-# View sessions
-tmux list-sessions
-
-# Connect to session
-tmux attach-session -t wgbot
-
-# Disconnect (Ctrl+b, then d)
-```
-
-#### Option 2: Using systemd (recommended)
+#### Option 1: Using systemd (recommended)
**Create service file:**
@@ -700,27 +207,32 @@ sudo systemctl stop wgbot
sudo systemctl restart wgbot
```
-#### Option 3: Using Docker (if available)
+#### Option 2: Using Docker (if available)
-```dockerfile
-FROM python:3.11-slim
+```bash
+# Build image
+docker build . -f docker/Dockerfile -t wgerinys:latest
-WORKDIR /app
+# Run container
+docker run -d --name wgerinys --env-file .env wgerinys:latest
+```
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
+#### Option 3: Using tmux (simple)
-COPY . .
+```bash
+# Install tmux
+sudo apt-get install tmux
-CMD ["python", "main.py"]
-```
+# Create session
+tmux new-session -d -s wgbot -c /path/to/bot python main.py
-```bash
-# Build image
-docker build -t wgbot .
+# View sessions
+tmux list-sessions
-# Run container
-docker run -d --name wgbot --env-file .env wgbot
+# Connect to session
+tmux attach-session -t wgbot
+
+# Disconnect (Ctrl+b, then d)
```
## 📋 Available Commands
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1f6890f..6888202 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1 +1,14 @@
-FROM debian:stable-slim AS builder
+# Use a small Debian base image for our image.
+FROM python:3.13-slim-trixie
+
+RUN mkdir -p /opt/wgerinys/src
+
+COPY ./src/requirements.txt /opt/wgerinys/src/requirements.txt
+
+RUN pip3 install --upgrade pip && \
+ pip3 install -r /opt/wgerinys/src/requirements.txt && \
+ ls -la /opt/wgerinys/src/
+
+COPY ./src /opt/wgerinys/src
+
+ENTRYPOINT ["python3", "/opt/wgerinys/src/main.py"]
\ No newline at end of file
diff --git a/docs/README_ESPANOL.md b/docs/README_ESPANOL.md
new file mode 100644
index 0000000..aee2b28
--- /dev/null
+++ b/docs/README_ESPANOL.md
@@ -0,0 +1,455 @@
+# 🤖 WGDashboard Erinys (Telegram Bot)
+
+---
+
+Bot profesional de Telegram para administrar y consultar WireGuard mediante la API de WGDashboard. Permite a administradores gestionar peers, monitorear estado del servidor y ejecutar acciones administrativas directamente desde Telegram.
+
+## ✨ Características principales
+
+### Administradores
+- 📡 **Gestión de Configuraciones**: Ver todas las configuraciones WireGuard
+- 👥 **Gestión de Peers**: Crear, eliminar, ver detalles y monitorear peers
+- 🔒 **Restricciones**: Restringir/permitir acceso de peers específicos
+- 🧹 **Limpiar Tráfico**: Resetear contadores de datos de peers
+- ⏰ **Schedule Jobs**: Crear trabajos programados (límites de datos y fechas de expiración)
+- 🖥️ **Estado del Sistema**: Monitorear CPU, memoria, discos e interfaces
+- 📊 **Estadísticas**: Ver información detallada del sistema
+- 👷 **Supervisión de Operadores**: Ver actividad de operadores autorizados
+- 🌐 **Multiidioma**: Soporte para español e inglés
+
+### Operadores
+- ➕ **Crear Peers Temporales**: Generar peers con límites automáticos
+- 📥 **Descargar Config**: Obtener archivo `.conf` para el nuevo peer
+- 🌐 **Cambiar Idioma**: Seleccionar entre español e inglés
+- ⏳ **Límites Automáticos**: 1 GB de datos y 24 horas de duración por peer
+
+## 📁 Estructura del proyecto
+
+```
+TGBot-saveOK/
+├── main.py # Punto de entrada del bot
+├── config.py # Configuración y variables de entorno
+├── handlers.py # Handlers de comandos y callbacks (4100+ líneas)
+├── keyboards.py # Teclados inline de Telegram
+├── i18n.py # Sistema de traducción multiidioma
+├── operators.py # Control de operadores autorizados
+├── utils.py # Funciones utilitarias
+├── wg_api.py # Cliente de la API WGDashboard
+├── setup_logging.py # Configuración de logs
+├── .env # Variables de entorno (NO subir a git)
+├── requirements.txt # Dependencias del proyecto
+├── README.md # Este archivo
+├── locales/
+│ ├── es.json # Traducción al español
+│ └── en.json # Traducción al inglés
+└── info/
+ ├── resumen.md # Documentación técnica
+ ├── CAMBIOS_REALIZADOS.md # Historial de cambios
+ └── IMPLEMENTACION_*.md # Documentación de features
+```
+
+## 🛠️ Requisitos previos
+
+### Sistema operativo
+- Linux (Ubuntu 20.04 LTS o superior recomendado)
+- Python 3.10 o superior
+- pip (Python Package Manager)
+
+### Dependencias externas
+- **WireGuard**: Instalado y configurado en el servidor
+- **WGDashboard**: Funcionando y accesible via API
+- **Bot de Telegram**: Creado con [@BotFather](https://t.me/BotFather)
+
+## 📦 Instalación paso a paso
+
+### 1. Clonar o descargar el proyecto
+
+```bash
+git clone https://github.com/WGDashboard/WGErinys
+cd WGErinys
+```
+
+### 2. Crear entorno virtual (recomendado)
+
+```bash
+python3 -m venv .venv
+source .venv/bin/activate # En Windows: .venv\Scripts\activate
+```
+
+### 3. Instalar dependencias
+
+```bash
+pip install -r requirements.txt
+```
+
+### 4. Configurar variables de entorno
+
+Crear archivo `.env` en la raíz del proyecto:
+
+```bash
+# ===== TELEGRAM =====
+TELEGRAM_BOT_TOKEN=tu_token_aqui # Obtener de @BotFather en Telegram
+
+# ===== WGDASHBOARD API =====
+WG_API_BASE_URL=https://tu-dominio.com/api # URL base de la API
+WG_API_KEY=tu_api_key_aqui # API Key de WGDashboard
+WG_API_PREFIX=wg # Prefijo de la API (si aplica)
+API_TIMEOUT=10 # Timeout en segundos
+
+# ===== LOGGING =====
+LOG_FILE=wg_bot.log # Archivo de logs
+LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR
+
+# ===== ACCESO =====
+ALLOWED_USERS=123456789,987654321 # IDs de Telegram autorizados
+ROLE_ADMIN=admin # Rol de administrador
+ROLE_OPERATOR=operator # Rol de operador
+ADMIN_IDS=123456789 # IDs que son administradores
+OPERATOR_IDS=987654321,111222333 # IDs que son operadores
+
+# ===== LÍMITES DE OPERADOR =====
+OPERATOR_DATA_LIMIT_GB=1 # GB por peer creado
+OPERATOR_TIME_LIMIT_HOURS=24 # Horas de duración del peer
+```
+
+### 5. Configurar usuarios autorizados
+
+Editar `config.py` y agregar los IDs de Telegram:
+
+```python
+# IDs autorizados a usar el bot
+ALLOWED_USERS = [
+ 123456789, # Admin 1
+ 987654321, # Admin 2
+ 111222333, # Operador 1
+]
+
+# Configuración de roles
+ADMIN_USERS = [123456789, 987654321]
+OPERATOR_USERS = [111222333]
+
+# Límites para operadores
+OPERATOR_DATA_LIMIT_GB = 1 # 1 GB por peer
+OPERATOR_TIME_LIMIT_HOURS = 24 # 24 horas de duración
+```
+
+**Para obtener tu ID de Telegram:**
+1. Abre [@userinfobot](https://t.me/userinfobot) en Telegram
+2. El bot te mostrará tu ID
+
+## 🚀 Ejecutar el bot
+
+### Desarrollo local
+
+```bash
+# Con el entorno virtual activado
+python main.py
+```
+
+### Producción en VPS
+
+#### Opción 1: Usando systemd (recomendado)
+
+**Crear archivo de servicio:**
+
+```bash
+sudo nano /etc/systemd/system/wgbot.service
+```
+
+**Contenido del archivo:**
+
+```ini
+[Unit]
+Description=WGDashboard Telegram Bot
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=wgbot
+Group=wgbot
+WorkingDirectory=/home/wgbot/TGBot-saveOK
+Environment="PATH=/home/wgbot/TGBot-saveOK/.venv/bin"
+ExecStart=/home/wgbot/TGBot-saveOK/.venv/bin/python main.py
+Restart=always
+RestartSec=10
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target
+```
+
+**Habilitar e iniciar el servicio:**
+
+```bash
+# Recargar systemd
+sudo systemctl daemon-reload
+
+# Habilitar para que inicie automáticamente
+sudo systemctl enable wgbot
+
+# Iniciar el bot
+sudo systemctl start wgbot
+
+# Ver estado
+sudo systemctl status wgbot
+
+# Ver logs en tiempo real
+sudo journalctl -u wgbot -f
+
+# Detener
+sudo systemctl stop wgbot
+
+# Reiniciar
+sudo systemctl restart wgbot
+```
+
+#### Opción 2: Usando Docker (si está disponible)
+
+```bash
+# Construir imagen
+docker build . -f docker/Dockerfile -t wgerinys:latest
+
+# Ejecutar contenedor
+docker run -d --name wgerinys --env-file .env wgerinys:latest
+```
+
+#### Opción 3: Usando tmux (simple)
+
+```bash
+# Instalar tmux
+sudo apt-get install tmux
+
+# Crear sesión
+tmux new-session -d -s wgbot -c /ruta/al/bot python main.py
+
+# Ver sesión
+tmux list-sessions
+
+# Conectar a sesión
+tmux attach-session -t wgbot
+
+# Desconectar (Ctrl+b, luego d)
+```
+
+## 📋 Comandos disponibles
+
+### Para Administradores
+
+| Comando | Descripción |
+|---------|------------|
+| `/start` | Inicia el bot y muestra menú principal |
+| `/help` | Muestra ayuda y comandos disponibles |
+| `/stats` | Muestra estadísticas del sistema |
+| `/configs` | Lista todas las configuraciones WireGuard |
+
+**Funciones via menú:**
+- 📡 Configuraciones - Gestionar todas las configs
+- 👥 Peers - Ver, crear, eliminar, listar
+- 🖥️ Estado del Sistema - CPU, RAM, discos, network
+- ⚡ Protocolos - Ver protocolos habilitados
+- 👷 Operadores - Ver actividad de operadores
+- 🌐 Idioma - Cambiar entre español e inglés
+
+### Para Operadores
+
+| Comando | Descripción |
+|---------|------------|
+| `/start` | Muestra menú para crear peers |
+| `/help` | Información sobre límites y funciones |
+
+**Funciones:**
+- ➕ Crear Peer - Generar nuevo peer temporal
+- 📥 Descargar - Obtener archivo de configuración
+- 🌐 Idioma - Cambiar idioma
+
+## 🔐 Seguridad y control de acceso
+
+### Niveles de permisos
+
+**Administrador (acceso completo)**
+- Todas las funciones del bot
+- Gestión total de peers
+- Acceso a estadísticas
+- Supervisión de operadores
+
+**Operador (acceso limitado)**
+- Solo crear peers temporales
+- Descargar configuración de peers creados
+- Sin acceso a gestión de peers existentes
+
+**No autorizado (sin acceso)**
+- El bot rechaza cualquier comando
+- Mensaje de error: "Acceso restringido"
+
+### Mejores prácticas de seguridad
+
+1. **API Key de WGDashboard**
+ - Usar en variables de entorno `.env`
+ - Nunca subir `.env` a git
+ - Agregar a `.gitignore`
+
+2. **Token del Bot de Telegram**
+ - Mantener en `.env`
+ - Cambiar si se expone
+ - Usar restricciones de webhook si es posible
+
+3. **Acceso al servidor**
+ - Firewall activo
+ - SSH con claves públicas
+ - Cambiar puerto SSH por defecto
+ - Monitorear logs regularmente
+
+4. **WGDashboard**
+ - Detrás de proxy reverso (nginx)
+ - HTTPS obligatorio
+ - Autenticación fuerte
+ - Backups regulares
+
+## 📊 Funcionalidades detalladas
+
+### Gestión de Peers
+
+#### Crear Peer
+- Nombre único
+- IP automática
+- Claves WireGuard generadas
+- Archivo `.conf` enviado directamente
+- Límites automáticos aplicados
+
+#### Detalles de Peer
+- Información de red (IP, endpoint, DNS)
+- Estado de conexión (conectado/desconectado)
+- Tráfico (enviado, recibido, acumulativo)
+- Claves (pública, pre-shared)
+- Trabajos programados
+- Enlaces compartidos
+
+#### Restricciones
+- Restringir peer (bloquea conexión)
+- Permitir access (quita restricción)
+- Visualización por página
+- Filtros disponibles
+
+### Schedule Jobs
+
+Los trabajos programados permiten:
+- **Límite de datos**: Restringir cuando alcanza X GB
+- **Fecha de expiración**: Restringir automáticamente en fecha X
+- Crear/eliminar jobs sobre la marcha
+- Gestión completa de tareas
+
+### Estadísticas del Sistema
+
+Monitorea en tiempo real:
+- 💻 Uso de CPU (%)
+- 🧠 Uso de RAM (% y detalles)
+- 💾 Uso de discos (principales)
+- 📡 Interfaces de red (tráfico)
+- 🔗 Interfaces WireGuard
+
+## 🌐 Multiidioma
+
+El bot soporta automáticamente:
+- 🇪🇸 **Español** - Interfaz completa en español
+- 🇬🇧 **Inglés** - Interfaz completa en inglés
+
+Los usuarios pueden cambiar el idioma desde el menú en cualquier momento. Las preferencias se guardan automáticamente.
+
+## 📝 Logs y debugging
+
+### Ver logs en tiempo real
+
+```bash
+# Si usas systemd
+sudo journalctl -u wgbot -f
+
+# Si ejecutas directamente
+tail -f wg_bot.log
+```
+
+### Niveles de log
+
+```
+DEBUG - Información detallada para debugging
+INFO - Eventos importantes
+WARNING - Advertencias (problemas potenciales)
+ERROR - Errores que afectan funcionamiento
+```
+
+### Configurar nivel de log
+
+Editar `.env`:
+```
+LOG_LEVEL=DEBUG # Para más detalle en desarrollo
+```
+
+## 🆘 Troubleshooting
+
+### El bot no responde
+
+```bash
+# Verificar que está corriendo
+sudo systemctl status wgbot
+
+# Ver logs
+sudo journalctl -u wgbot -n 50
+
+# Reiniciar
+sudo systemctl restart wgbot
+```
+
+### Error: "No se puede conectar a API"
+
+- Verificar `WG_API_BASE_URL` en `.env`
+- Verificar `WG_API_KEY` es correcta
+- Probar conectividad: `curl https://tu-url/api/handshake`
+- Verificar firewall/proxy permite la conexión
+
+### Error: "Token de Telegram inválido"
+
+- Verificar `TELEGRAM_BOT_TOKEN` en `.env`
+- Crear nuevo token con [@BotFather](https://t.me/BotFather)
+- Verificar no hay espacios en blanco
+
+### El bot no ve mis cambios
+
+```bash
+# Reiniciar para cargar cambios
+sudo systemctl restart wgbot
+
+# O si usas tmux
+tmux send-keys -t wgbot "C-c"
+python main.py
+```
+
+## 📚 Documentación adicional
+
+- `info/resumen.md` - Análisis técnico de la API
+- `info/CAMBIOS_REALIZADOS.md` - Historial de cambios
+- `info/IMPLEMENTACION_*.md` - Detalles de features
+
+## 🤝 Contribuciones
+
+Las contribuciones son bienvenidas. Por favor:
+
+1. Fork del proyecto
+2. Crear rama para tu feature (`git checkout -b feature/AmazingFeature`)
+3. Commit cambios (`git commit -m 'Add AmazingFeature'`)
+4. Push a rama (`git push origin feature/AmazingFeature`)
+5. Abrir Pull Request
+
+## 📄 Licencia
+
+Este proyecto se distribuye bajo la licencia MIT. Puedes usarlo, modificarlo y redistribuirlo libremente.
+
+## 👨💻 Autor
+
+**Jorge Elián Martinez Perdomo**
+
+Bot profesional de Telegram para administración de WireGuard usando WGDashboard
+
+- GitHub: [@jemartinezp1994](https://github.com/jemartinezp1994)
+- Telegram: [@jemp01K](https://t.me/jemp01K)
+- Discord: ff_c3r0
diff --git a/src/main.py b/src/main.py
index 9471a9e..faf8f47 100644
--- a/src/main.py
+++ b/src/main.py
@@ -5,7 +5,7 @@
import signal
import sys
import asyncio
-from handlers import language_command
+
from telegram import Update
from telegram.ext import (
ApplicationBuilder,
@@ -17,13 +17,14 @@
)
# Importar configuraciones y módulos
-import config
-from setup_logging import logger
-from handlers import (
- start_command, help_command, stats_command, configs_command,
- callback_handler, text_message_handler
+from modules import config
+
+from modules.setup_logging import logger
+from modules.handlers import (
+ callback_handler, configs_command, help_command, language_command,
+ start_command, stats_command, text_message_handler
)
-from utils import is_allowed, t
+from modules.utils import is_allowed, translate
# ================= FUNCIONES DE UTILIDAD ================= #
def validate_environment():
@@ -84,7 +85,7 @@ async def stats_command_admin(update: Update, context: ContextTypes.DEFAULT_TYPE
user_id = update.effective_user.id
if not is_admin(user_id):
await update.message.reply_text(
- t(user_id, "errors.access_denied"),
+ translate(user_id, "errors.access_denied"),
parse_mode="Markdown"
)
return
@@ -94,7 +95,7 @@ async def configs_command_admin(update: Update, context: ContextTypes.DEFAULT_TY
user_id = update.effective_user.id
if not is_admin(user_id):
await update.message.reply_text(
- t(user_id, "errors.access_denied"),
+ translate(user_id, "errors.access_denied"),
parse_mode="Markdown"
)
return
@@ -174,4 +175,4 @@ def main():
logger.info("👋 Bot detenido correctamente")
if __name__ == "__main__":
- main()
+ main()
\ No newline at end of file
diff --git a/src/config.py b/src/modules/config.py
similarity index 86%
rename from src/config.py
rename to src/modules/config.py
index 3f7b1f7..34ac42d 100644
--- a/src/config.py
+++ b/src/modules/config.py
@@ -13,12 +13,12 @@
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
# ================= WGDASHBOARD API ================= #
-WG_API_BASE_URL = os.getenv("WG_API_BASE_URL", "http://localhost:10086/api")
-WG_API_KEY = os.getenv("WG_API_KEY", "")
+WGD_API_BASE_URL = os.getenv("WGD_API_BASE_URL", "http://localhost:10086/api")
+WGD_API_KEY = os.getenv("WGD_API_KEY", "")
API_TIMEOUT = int(os.getenv("API_TIMEOUT", "10"))
# Opcional: Prefijo para la URL del dashboard
-WG_API_PREFIX = os.getenv("WG_API_PREFIX", "")
+WGD_API_PREFIX = os.getenv("WGD_API_PREFIX", "")
# ================= SEGURIDAD ================= #
# Roles
@@ -39,7 +39,7 @@
OPERATORS_DB = os.path.join(DATA_DIR, "operator_peers.json")
# ================= LOGGING ================= #
-LOG_FILE = os.getenv("LOG_FILE", "wg_bot.log")
+LOG_FILE = os.getenv("LOG_FILE", "wgd_bot.log")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
LOG_MAX_SIZE = 10 * 1024 * 1024 # 10MB
LOG_BACKUP_COUNT = 5
@@ -61,11 +61,11 @@ def validate_config():
if not TELEGRAM_BOT_TOKEN:
errors.append("TELEGRAM_BOT_TOKEN no configurado")
- if not WG_API_KEY:
- errors.append("WG_API_KEY no configurada")
+ if not WGD_API_KEY:
+ errors.append("WGD_API_KEY no configurada")
- if not WG_API_BASE_URL:
- errors.append("WG_API_BASE_URL no configurada")
+ if not WGD_API_BASE_URL:
+ errors.append("WGD_API_BASE_URL no configurada")
if not ALLOWED_USERS:
errors.append("ALLOWED_USERS está vacío")
diff --git a/src/handlers.py b/src/modules/handlers.py
similarity index 75%
rename from src/handlers.py
rename to src/modules/handlers.py
index 76186b0..fab9b58 100644
--- a/src/handlers.py
+++ b/src/modules/handlers.py
@@ -28,27 +28,27 @@
except ImportError:
HAS_CRYPTOGRAPHY = False
-from config import ROLE_OPERATOR, ALLOWED_USERS, ROLE_ADMIN, OPERATOR_DATA_LIMIT_GB, OPERATOR_TIME_LIMIT_HOURS
-from operators import operators_db
-from utils import is_allowed, is_admin, is_operator, can_operator_create_peer, log_command_with_role
+from .config import ROLE_OPERATOR, ALLOWED_USERS, ROLE_ADMIN, OPERATOR_DATA_LIMIT_GB, OPERATOR_TIME_LIMIT_HOURS
+from .operators import operators_db
+from .utils import is_allowed, is_admin, is_operator, can_operator_create_peer, log_command_with_role
-from config import ALLOWED_USERS
-from wg_api import api_client
-from keyboards import (
+from .config import ALLOWED_USERS
+from .wgd_api import api_client
+from .keyboards import (
main_menu, config_menu, paginated_configs_menu, restrictions_menu,
paginated_restricted_peers_menu, paginated_unrestricted_peers_menu,
paginated_reset_traffic_menu, confirmation_menu, back_button,
refresh_button, operator_main_menu,
InlineKeyboardMarkup, decode_callback_data
)
-from utils import (
+from .utils import (
is_allowed, get_user_name, format_peer_info,
format_system_status, format_config_summary,
send_large_message, log_command, log_callback, log_error,
format_bytes_human, format_time_ago,
log_callback_with_role, log_command_with_role,
is_admin, is_operator, can_operator_create_peer,
- t, get_user_language, set_user_language, escape_markdown
+ translate, get_user_language, set_user_language, escape_markdown
)
logger = logging.getLogger(__name__)
@@ -56,7 +56,7 @@
# ================= FUNCIONES AUXILIARES ================= #
def format_peer_for_list(peer: Dict, user_id: int) -> str:
"""Formatea un peer para la lista básica"""
- name = peer.get('name', t(user_id, "common.no_name"))
+ name = peer.get('name', translate(user_id, "common.no_name"))
latest_handshake = peer.get('latest_handshake_seconds', 0)
status = peer.get('status', 'stopped')
@@ -64,8 +64,8 @@ def format_peer_for_list(peer: Dict, user_id: int) -> str:
status_emoji = "✅" if status == 'running' and latest_handshake > 0 else "❌"
last_seen = format_time_ago(latest_handshake, user_id)
- allowed_ip = peer.get('allowed_ip', t(user_id, "common.na"))
- return f"{status_emoji} **{name}** - IP: `{allowed_ip}` - {t(user_id, 'peers.connected').split(':')[0]}: {last_seen}"
+ allowed_ip = peer.get('allowed_ip', translate(user_id, "common.na"))
+ return f"{status_emoji} **{name}** - IP: `{allowed_ip}` - {translate(user_id, 'peers.connected').split(':')[0]}: {last_seen}"
@@ -73,13 +73,13 @@ def format_schedule_job_for_list(job: Dict, user_id: int) -> str:
"""Formatea un schedule job para la lista"""
action = job.get('Action', 'desconocido')
field = job.get('Field', 'desconocido')
- value = job.get('Value', t(user_id, "common.na"))
+ value = job.get('Value', translate(user_id, "common.na"))
if field == "total_data":
- field_text = t(user_id, "schedule.data_limit")
+ field_text = translate(user_id, "schedule.data_limit")
value_display = f"{value} GB"
elif field == "date":
- field_text = t(user_id, "schedule.expiry_date")
+ field_text = translate(user_id, "schedule.expiry_date")
value_display = value
else:
field_text = field
@@ -89,16 +89,16 @@ def format_schedule_job_for_list(job: Dict, user_id: int) -> str:
def format_peer_for_detail(peer: Dict, user_id: int) -> str:
"""Formatea un peer para vista detallada (con Markdown)"""
- name = peer.get('name', t(user_id, "common.no_name"))
+ name = peer.get('name', translate(user_id, "common.no_name"))
status = peer.get('status', 'stopped')
latest_handshake = peer.get('latest_handshake_seconds', 0)
- allowed_ip = peer.get('allowed_ip', t(user_id, "common.na"))
- endpoint = peer.get('endpoint', t(user_id, "common.na"))
+ allowed_ip = peer.get('allowed_ip', translate(user_id, "common.na"))
+ endpoint = peer.get('endpoint', translate(user_id, "common.na"))
transfer_rx = peer.get('transfer_rx', 0)
transfer_tx = peer.get('transfer_tx', 0)
status_emoji = "✅" if status == 'running' and latest_handshake > 0 else "❌"
- status_text = t(user_id, "peers.status_connected") if status == 'running' and latest_handshake > 0 else t(user_id, "peers.status_disconnected")
+ status_text = translate(user_id, "peers.status_connected") if status == 'running' and latest_handshake > 0 else translate(user_id, "peers.status_disconnected")
last_seen = format_time_ago(latest_handshake, user_id)
@@ -118,10 +118,10 @@ def format_bytes(size):
lines = [
f"{status_emoji} *{name}*",
f"IP: `{allowed_ip}`",
- f"{t(user_id, 'restrictions.status')}: {status_text}",
- f"{t(user_id, 'peers.connected').split(':')[0]}: {last_seen}",
- f"{t(user_id, 'traffic.received')}: {rx_fmt}",
- f"{t(user_id, 'traffic.sent')}: {tx_fmt}",
+ f"{translate(user_id, 'restrictions.status')}: {status_text}",
+ f"{translate(user_id, 'peers.connected').split(':')[0]}: {last_seen}",
+ f"{translate(user_id, 'traffic.received')}: {rx_fmt}",
+ f"{translate(user_id, 'traffic.sent')}: {tx_fmt}",
f"Endpoint: `{endpoint}`"
]
@@ -130,17 +130,17 @@ def format_bytes(size):
def format_peer_for_detail_plain(peer: Dict, user_id: int) -> str:
"""Formatea un peer para vista detallada (texto plano con TODOS los detalles)"""
# Información básica
- name = peer.get('name', t(user_id, "common.no_name"))
+ name = peer.get('name', translate(user_id, "common.no_name"))
status = peer.get('status', 'stopped')
latest_handshake = peer.get('latest_handshake_seconds', 0)
- allowed_ip = peer.get('allowed_ip', t(user_id, "common.na"))
+ allowed_ip = peer.get('allowed_ip', translate(user_id, "common.na"))
peer_id = peer.get('id', 'N/A')
endpoint = peer.get('endpoint', 'N/A')
remote_endpoint = peer.get('remote_endpoint', 'N/A')
# Estado y tiempo
status_emoji = "✅" if status == 'running' and latest_handshake > 0 else "❌"
- status_text = t(user_id, "peers.status_connected") if status == 'running' and latest_handshake > 0 else t(user_id, "peers.status_disconnected")
+ status_text = translate(user_id, "peers.status_connected") if status == 'running' and latest_handshake > 0 else translate(user_id, "peers.status_disconnected")
last_seen = format_time_ago(latest_handshake, user_id)
# Datos de tráfico
@@ -165,63 +165,63 @@ def format_peer_for_detail_plain(peer: Dict, user_id: int) -> str:
# Construir el mensaje detallado usando traducciones
pub_display = f"{peer_id[:20]}...{peer_id[-10:] if len(peer_id) > 30 else ''}"
- preshared_status = t(user_id, "peers.details_preshared_yes") if preshared_key else t(user_id, "peers.details_preshared_no")
+ preshared_status = translate(user_id, "peers.details_preshared_yes") if preshared_key else translate(user_id, "peers.details_preshared_no")
lines = [
- t(user_id, "peers.details_separator"),
- t(user_id, "peers.details_name", emoji=status_emoji, name=name),
- t(user_id, "peers.details_separator"),
+ translate(user_id, "peers.details_separator"),
+ translate(user_id, "peers.details_name", emoji=status_emoji, name=name),
+ translate(user_id, "peers.details_separator"),
"",
- t(user_id, "peers.details_network_header"),
- t(user_id, "peers.details_assigned_ip", ip=allowed_ip),
- t(user_id, "peers.details_local_endpoint", endpoint=endpoint),
- t(user_id, "peers.details_remote_endpoint", remote=remote_endpoint),
- t(user_id, "peers.details_endpoint_allowed_ip", value=endpoint_allowed_ip),
- t(user_id, "peers.details_dns", dns=dns),
+ translate(user_id, "peers.details_network_header"),
+ translate(user_id, "peers.details_assigned_ip", ip=allowed_ip),
+ translate(user_id, "peers.details_local_endpoint", endpoint=endpoint),
+ translate(user_id, "peers.details_remote_endpoint", remote=remote_endpoint),
+ translate(user_id, "peers.details_endpoint_allowed_ip", value=endpoint_allowed_ip),
+ translate(user_id, "peers.details_dns", dns=dns),
"",
- t(user_id, "peers.details_security_header"),
- t(user_id, "peers.details_public_key", public=pub_display),
- t(user_id, "peers.details_preshared", status=preshared_status),
+ translate(user_id, "peers.details_security_header"),
+ translate(user_id, "peers.details_public_key", public=pub_display),
+ translate(user_id, "peers.details_preshared", status=preshared_status),
"",
- t(user_id, "peers.details_connection_header"),
- t(user_id, "peers.details_status", status=status_text),
- t(user_id, "peers.details_last_handshake", last_seen=last_seen),
- t(user_id, "peers.details_keepalive", keepalive=keepalive),
+ translate(user_id, "peers.details_connection_header"),
+ translate(user_id, "peers.details_status", status=status_text),
+ translate(user_id, "peers.details_last_handshake", last_seen=last_seen),
+ translate(user_id, "peers.details_keepalive", keepalive=keepalive),
"",
- t(user_id, "peers.details_traffic_header"),
- t(user_id, "peers.details_received_current", value=format_bytes_human(total_receive)),
- t(user_id, "peers.details_sent_current", value=format_bytes_human(total_sent)),
- t(user_id, "peers.details_total_current", value=format_bytes_human(total_receive + total_sent)),
- t(user_id, "peers.details_received_cumu", value=format_bytes_human(cumu_receive)),
- t(user_id, "peers.details_sent_cumu", value=format_bytes_human(cumu_sent)),
- (t(user_id, "peers.details_data_limit", value=total_data_limit) if total_data_limit > 0 else t(user_id, "peers.details_data_limit_none")),
+ translate(user_id, "peers.details_traffic_header"),
+ translate(user_id, "peers.details_received_current", value=format_bytes_human(total_receive)),
+ translate(user_id, "peers.details_sent_current", value=format_bytes_human(total_sent)),
+ translate(user_id, "peers.details_total_current", value=format_bytes_human(total_receive + total_sent)),
+ translate(user_id, "peers.details_received_cumu", value=format_bytes_human(cumu_receive)),
+ translate(user_id, "peers.details_sent_cumu", value=format_bytes_human(cumu_sent)),
+ (translate(user_id, "peers.details_data_limit", value=total_data_limit) if total_data_limit > 0 else translate(user_id, "peers.details_data_limit_none")),
"",
- t(user_id, "peers.details_technical_header"),
- t(user_id, "peers.details_mtu", mtu=mtu),
+ translate(user_id, "peers.details_technical_header"),
+ translate(user_id, "peers.details_mtu", mtu=mtu),
"",
]
# Agregar Jobs si existen (usar traducciones)
if jobs:
- lines.append(t(user_id, "peers.details_jobs_header"))
+ lines.append(translate(user_id, "peers.details_jobs_header"))
for i, job in enumerate(jobs, 1):
action = job.get('Action', 'Desconocida')
field = job.get('Field', 'Desconocido')
value = job.get('Value', 'N/A')
- lines.append(t(user_id, "peers.details_job_line", index=i, action=action, field=field, value=value))
+ lines.append(translate(user_id, "peers.details_job_line", index=i, action=action, field=field, value=value))
lines.append("")
# Agregar ShareLinks si existen (usar traducciones)
if share_links:
- lines.append(t(user_id, "peers.details_share_links_header"))
+ lines.append(translate(user_id, "peers.details_share_links_header"))
for i, link in enumerate(share_links, 1):
share_id = link.get('ShareID', 'N/A')
- exp_date = link.get('ExpireDate', t(user_id, "common.na"))
+ exp_date = link.get('ExpireDate', translate(user_id, "common.na"))
short_id = f"{share_id[:15]}..."
- lines.append(t(user_id, "peers.details_share_link_line", index=i, short=short_id, exp_date=exp_date))
+ lines.append(translate(user_id, "peers.details_share_link_line", index=i, short=short_id, exp_date=exp_date))
lines.append("")
- lines.append(t(user_id, "peers.details_separator"))
+ lines.append(translate(user_id, "peers.details_separator"))
return "\n".join(lines)
@@ -315,13 +315,13 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
welcome_text = ""
if is_admin(user_id):
- welcome_text = t(user_id, "welcome.admin_title", name=get_user_name(update)) + "\n\n"
- welcome_text += t(user_id, "welcome.admin_message")
+ welcome_text = translate(user_id, "welcome.admin_title", name=get_user_name(update)) + "\n\n"
+ welcome_text += translate(user_id, "welcome.admin_message")
keyboard = main_menu(is_admin(user_id), is_operator(user_id), user_id)
elif is_operator(user_id):
- welcome_text = t(user_id, "welcome.operator_title", name=get_user_name(update)) + "\n\n"
- welcome_text += t(user_id, "welcome.operator_message")
+ welcome_text = translate(user_id, "welcome.operator_title", name=get_user_name(update)) + "\n\n"
+ welcome_text += translate(user_id, "welcome.operator_message")
keyboard = operator_main_menu(user_id)
await update.message.reply_text(
@@ -339,11 +339,11 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
log_command_with_role(update, "help")
if is_admin(user_id):
- help_text = t(user_id, "help.admin")
+ help_text = translate(user_id, "help.admin")
keyboard = main_menu(is_admin(user_id), is_operator(user_id), user_id)
elif is_operator(user_id):
- help_text = t(user_id, "help.operator")
+ help_text = translate(user_id, "help.operator")
keyboard = operator_main_menu(user_id)
await update.message.reply_text(
@@ -363,24 +363,24 @@ async def language_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
keyboard = InlineKeyboardMarkup([
[
InlineKeyboardButton(
- t(user_id, "language.spanish"),
+ translate(user_id, "language.spanish"),
callback_data="set_language:es"
),
InlineKeyboardButton(
- t(user_id, "language.english"),
+ translate(user_id, "language.english"),
callback_data="set_language:en"
)
],
[
InlineKeyboardButton(
- t(user_id, "menu.back"),
+ translate(user_id, "menu.back"),
callback_data="main_menu"
)
]
])
await update.message.reply_text(
- t(user_id, "language.select"),
+ translate(user_id, "language.select"),
reply_markup=keyboard,
parse_mode="Markdown"
)
@@ -394,7 +394,7 @@ async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
log_command(update, "stats")
await update.message.reply_text(
- t(user_id, "system.getting_stats"),
+ translate(user_id, "system.getting_stats"),
reply_markup=refresh_button("system_status", user_id)
)
@@ -403,7 +403,7 @@ async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not result.get("status"):
await update.message.reply_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("system_status", user_id)
)
return
@@ -426,7 +426,7 @@ async def configs_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
log_command(update, "configs")
await update.message.reply_text(
- t(user_id, "configurations.getting"),
+ translate(user_id, "configurations.getting"),
reply_markup=refresh_button("configs", user_id)
)
@@ -434,7 +434,7 @@ async def configs_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not result.get("status"):
await update.message.reply_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("configs", user_id)
)
return
@@ -443,7 +443,7 @@ async def configs_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not configs:
await update.message.reply_text(
- t(user_id, "errors.no_configs"),
+ translate(user_id, "errors.no_configs"),
reply_markup=refresh_button("configs", user_id)
)
return
@@ -454,10 +454,10 @@ async def configs_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
total_configs = len(configs)
total_pages = (total_configs - 1) // 8 + 1
- message = t(user_id, "configurations.title") + "\n"
- message += t(user_id, "configurations.page", current=1, total=total_pages) + "\n"
- message += t(user_id, "configurations.total", count=total_configs) + "\n\n"
- message += t(user_id, "configurations.select_config")
+ message = translate(user_id, "configurations.title") + "\n"
+ message += translate(user_id, "configurations.page", current=1, total=total_pages) + "\n"
+ message += translate(user_id, "configurations.total", count=total_configs) + "\n\n"
+ message += translate(user_id, "configurations.select_config")
await update.message.reply_text(
message,
@@ -500,7 +500,7 @@ async def callback_handler(update: Update, context: CallbackContext):
for admin_action in admin_only_actions:
if callback_data.startswith(admin_action):
await query.edit_message_text(
- t(user_id, "errors.access_denied_operator"),
+ translate(user_id, "errors.access_denied_operator"),
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
@@ -554,7 +554,7 @@ async def callback_handler(update: Update, context: CallbackContext):
lang = callback_data.split(":")[1]
if set_user_language(user_id, lang):
await query.edit_message_text(
- t(user_id, "language.changed", language="Español" if lang == "es" else "English"),
+ translate(user_id, "language.changed", language="Español" if lang == "es" else "English"),
reply_markup=main_menu(is_admin(user_id), is_operator(user_id), user_id),
parse_mode="Markdown"
)
@@ -831,8 +831,8 @@ async def handle_operator_main_menu(query):
if not username.strip():
username = "Operador"
- welcome_text = t(user_id, "welcome.operator_title", name=username) + "\n\n"
- welcome_text += t(user_id, "welcome.operator_message")
+ welcome_text = translate(user_id, "welcome.operator_title", name=username) + "\n\n"
+ welcome_text += translate(user_id, "welcome.operator_message")
await query.edit_message_text(
welcome_text,
@@ -845,8 +845,8 @@ async def handle_restrictions_menu(query, config_name: str):
"""Muestra el menú de restricciones"""
user_id = query.from_user.id
await query.edit_message_text(
- t(user_id, "restrictions.title", config=config_name) + "\n\n" +
- t(user_id, "restrictions.select_option"),
+ translate(user_id, "restrictions.title", config=config_name) + "\n\n" +
+ translate(user_id, "restrictions.select_option"),
reply_markup=restrictions_menu(config_name, user_id),
parse_mode="Markdown"
)
@@ -854,13 +854,13 @@ async def handle_restrictions_menu(query, config_name: str):
async def handle_restricted_peers_list(query, context: CallbackContext, config_name: str, page: int = 0):
"""Muestra la lista paginada de peers restringidos - VERSIÓN SIMPLIFICADA"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.getting_restricted"))
+ await query.edit_message_text(translate(user_id, "restrictions.getting_restricted"))
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"restrictions:{config_name}", user_id)
)
return
@@ -869,7 +869,7 @@ async def handle_restricted_peers_list(query, context: CallbackContext, config_n
if not restricted_peers:
await query.edit_message_text(
- t(user_id, "errors.no_restricted"),
+ translate(user_id, "errors.no_restricted"),
reply_markup=back_button(f"restrictions:{config_name}", user_id),
parse_mode="Markdown"
)
@@ -886,10 +886,10 @@ async def handle_restricted_peers_list(query, context: CallbackContext, config_n
keyboard = paginated_restricted_peers_menu(restricted_peers, config_name, page, user_id)
- message = t(user_id, "restrictions.restricted_list", config=config_name) + "\n\n"
+ message = translate(user_id, "restrictions.restricted_list", config=config_name) + "\n\n"
message += f"📊 Total: {total_peers}\n"
- message += t(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
- message += t(user_id, "restrictions.select_peer_unrestrict")
+ message += translate(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
+ message += translate(user_id, "restrictions.select_peer_unrestrict")
await query.edit_message_text(
message,
@@ -900,13 +900,13 @@ async def handle_restricted_peers_list(query, context: CallbackContext, config_n
async def handle_unrestricted_peers_list(query, context: CallbackContext, config_name: str, page: int = 0):
"""Muestra la lista paginada de peers NO restringidos - VERSIÓN SIMPLIFICADA"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.getting_unrestricted"))
+ await query.edit_message_text(translate(user_id, "restrictions.getting_unrestricted"))
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"restrictions:{config_name}", user_id)
)
return
@@ -925,7 +925,7 @@ async def handle_unrestricted_peers_list(query, context: CallbackContext, config
if not unrestricted_peers:
await query.edit_message_text(
- t(user_id, "errors.all_restricted"),
+ translate(user_id, "errors.all_restricted"),
reply_markup=back_button(f"restrictions:{config_name}", user_id),
parse_mode="Markdown"
)
@@ -942,10 +942,10 @@ async def handle_unrestricted_peers_list(query, context: CallbackContext, config
keyboard = paginated_unrestricted_peers_menu(unrestricted_peers, config_name, page, user_id)
- message = t(user_id, "restrictions.available_list", config=config_name) + "\n\n"
- message += t(user_id, "restrictions.available_peers", count=total_peers) + "\n"
- message += t(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
- message += t(user_id, "restrictions.select_peer_restrict")
+ message = translate(user_id, "restrictions.available_list", config=config_name) + "\n\n"
+ message += translate(user_id, "restrictions.available_peers", count=total_peers) + "\n"
+ message += translate(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
+ message += translate(user_id, "restrictions.select_peer_restrict")
await query.edit_message_text(
message,
@@ -959,24 +959,24 @@ async def language_command_callback(query, context):
keyboard = InlineKeyboardMarkup([
[
InlineKeyboardButton(
- t(user_id, "language.spanish"),
+ translate(user_id, "language.spanish"),
callback_data="set_language:es"
),
InlineKeyboardButton(
- t(user_id, "language.english"),
+ translate(user_id, "language.english"),
callback_data="set_language:en"
)
],
[
InlineKeyboardButton(
- t(user_id, "menu.back"),
+ translate(user_id, "menu.back"),
callback_data="main_menu"
)
]
])
await query.edit_message_text(
- t(user_id, "language.select"),
+ translate(user_id, "language.select"),
reply_markup=keyboard,
parse_mode="Markdown"
)
@@ -984,14 +984,14 @@ async def language_command_callback(query, context):
async def handle_unrestrict_simple(query, context: CallbackContext, config_name: str, peer_index: int):
"""Quitar restricción de forma simplificada - VERSIÓN SEGURA CON MARKDOWN"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.removing"))
+ await query.edit_message_text(translate(user_id, "restrictions.removing"))
# Obtener peers del contexto
restricted_peers = context.user_data.get(f'restricted_peers_{config_name}', [])
if peer_index < 0 or peer_index >= len(restricted_peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"restricted_peers:{config_name}:0", user_id)
)
return
@@ -1002,7 +1002,7 @@ async def handle_unrestrict_simple(query, context: CallbackContext, config_name:
if not public_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"restricted_peers:{config_name}:0", user_id)
)
return
@@ -1012,10 +1012,10 @@ async def handle_unrestrict_simple(query, context: CallbackContext, config_name:
if result.get("status"):
await query.edit_message_text(
- t(user_id, "success.restriction_removed", peer_name=peer_name),
+ translate(user_id, "success.restriction_removed", peer_name=peer_name),
reply_markup=InlineKeyboardMarkup([
- [InlineKeyboardButton(t(user_id, "actions.update_list"), callback_data=f"restricted_peers:{config_name}:0")],
- [InlineKeyboardButton(t(user_id, "menu.back"), callback_data=f"restrictions:{config_name}")]
+ [InlineKeyboardButton(translate(user_id, "actions.update_list"), callback_data=f"restricted_peers:{config_name}:0")],
+ [InlineKeyboardButton(translate(user_id, "menu.back"), callback_data=f"restrictions:{config_name}")]
]),
parse_mode="Markdown"
)
@@ -1030,14 +1030,14 @@ async def handle_unrestrict_simple(query, context: CallbackContext, config_name:
async def handle_restrict_simple(query, context: CallbackContext, config_name: str, peer_index: int):
"""Restringir peer de forma simplificada - VERSIÓN SEGURA CON MARKDOWN"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.restricting"))
+ await query.edit_message_text(translate(user_id, "restrictions.restricting"))
# Obtener peers del contexto
unrestricted_peers = context.user_data.get(f'unrestricted_peers_{config_name}', [])
if peer_index < 0 or peer_index >= len(unrestricted_peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"restrict_peer_menu:{config_name}:0", user_id)
)
return
@@ -1048,7 +1048,7 @@ async def handle_restrict_simple(query, context: CallbackContext, config_name: s
if not public_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"restrict_peer_menu:{config_name}:0", user_id)
)
return
@@ -1058,10 +1058,10 @@ async def handle_restrict_simple(query, context: CallbackContext, config_name: s
if result.get("status"):
await query.edit_message_text(
- t(user_id, "success.peer_restricted", peer_name=peer_name),
+ translate(user_id, "success.peer_restricted", peer_name=peer_name),
reply_markup=InlineKeyboardMarkup([
- [InlineKeyboardButton(t(user_id, "actions.update_list"), callback_data=f"restrict_peer_menu:{config_name}:0")],
- [InlineKeyboardButton(t(user_id, "menu.back"), callback_data=f"restrictions:{config_name}")]
+ [InlineKeyboardButton(translate(user_id, "actions.update_list"), callback_data=f"restrict_peer_menu:{config_name}:0")],
+ [InlineKeyboardButton(translate(user_id, "menu.back"), callback_data=f"restrictions:{config_name}")]
]),
parse_mode="Markdown"
)
@@ -1111,19 +1111,19 @@ async def handle_unrestrict_confirm(query, context: CallbackContext, config_name
allowed_ip = peer_info.get('allowed_ip', 'N/A')
- message = f"⚠️ *{t(user_id, 'restrictions.confirm_remove')}*\n\n"
- message += f"{t(user_id, 'restrictions.confirm_remove_message')}\n\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name}\n"
- message += f"*{t(user_id, 'schedule.config_label')}* {config_name}\n"
+ message = f"⚠️ *{translate(user_id, 'restrictions.confirm_remove')}*\n\n"
+ message += f"{translate(user_id, 'restrictions.confirm_remove_message')}\n\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name}\n"
+ message += f"*{translate(user_id, 'schedule.config_label')}* {config_name}\n"
message += f"*IP:* `{allowed_ip}`\n"
- message += f"*{t(user_id, 'configurations.public_key_label')}* `{public_key[:30]}...`\n\n"
- message += t(user_id, "restrictions.unrestrict_info")
+ message += f"*{translate(user_id, 'configurations.public_key_label')}* `{public_key[:30]}...`\n\n"
+ message += translate(user_id, "restrictions.unrestrict_info")
# Codificar la clave pública completa
from keyboards import safe_callback_data
safe_public_key = safe_callback_data(public_key)
- keyboard = confirmation_menu(config_name, safe_public_key, "unrestrict", t(user_id, "restrictions.remove_restriction_button", default="Quitar Restricción"))
+ keyboard = confirmation_menu(config_name, safe_public_key, "unrestrict", translate(user_id, "restrictions.remove_restriction_button", default="Quitar Restricción"))
await query.edit_message_text(
message,
@@ -1141,22 +1141,22 @@ async def handle_unrestrict_confirm(query, context: CallbackContext, config_name
async def handle_unrestrict_execute(query, config_name: str, public_key: str):
"""Ejecuta la acción de quitar restricción a un peer"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.removing"))
+ await query.edit_message_text(translate(user_id, "restrictions.removing"))
result = api_client.allow_access_peer(config_name, public_key)
if result.get("status"):
await query.edit_message_text(
- f"{t(user_id, 'restrictions.unrestrict_success')}\n\n"
- f"{t(user_id, 'restrictions.unrestrict_success_msg', config=config_name)}",
+ f"{translate(user_id, 'restrictions.unrestrict_success')}\n\n"
+ f"{translate(user_id, 'restrictions.unrestrict_success_msg', config=config_name)}",
reply_markup=back_button(f"restricted_peers:{config_name}:0", user_id),
parse_mode="Markdown"
)
else:
error_msg = result.get('message', 'Error desconocido')
await query.edit_message_text(
- f"{t(user_id, 'restrictions.unrestrict_error')}\n\n"
- f"*{t(user_id, 'errors.error_label')}:* {error_msg}",
+ f"{translate(user_id, 'restrictions.unrestrict_error')}\n\n"
+ f"*{translate(user_id, 'errors.error_label')}:* {error_msg}",
reply_markup=back_button(f"restricted_peers:{config_name}:0", user_id),
parse_mode="Markdown"
)
@@ -1166,13 +1166,13 @@ async def handle_unrestrict_execute(query, config_name: str, public_key: str):
async def handle_reset_traffic_menu(query, context: CallbackContext, config_name: str, page: int = 0):
"""Muestra el menú para seleccionar peer para resetear tráfico"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "traffic.getting_peers", config=config_name))
+ await query.edit_message_text(translate(user_id, "traffic.getting_peers", config=config_name))
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1181,7 +1181,7 @@ async def handle_reset_traffic_menu(query, context: CallbackContext, config_name
if not peers:
await query.edit_message_text(
- t(user_id, "errors.no_peers", config=config_name),
+ translate(user_id, "errors.no_peers", config=config_name),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1197,10 +1197,10 @@ async def handle_reset_traffic_menu(query, context: CallbackContext, config_name
keyboard = paginated_reset_traffic_menu(peers, config_name, page, user_id)
- message = t(user_id, "traffic.title", config=config_name) + "\n\n"
- message += t(user_id, "traffic.total_peers", count=total_peers) + "\n"
- message += t(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
- message += t(user_id, "traffic.select_peer")
+ message = translate(user_id, "traffic.title", config=config_name) + "\n\n"
+ message += translate(user_id, "traffic.total_peers", count=total_peers) + "\n"
+ message += translate(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n\n"
+ message += translate(user_id, "traffic.select_peer")
await query.edit_message_text(
message,
@@ -1215,7 +1215,7 @@ async def handle_reset_traffic_confirm(query, config_name: str, peer_index: int,
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"reset_traffic:{config_name}:{page}", user_id)
)
return
@@ -1224,7 +1224,7 @@ async def handle_reset_traffic_confirm(query, config_name: str, peer_index: int,
if peer_index < 0 or peer_index >= len(peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"reset_traffic:{config_name}:{page}", user_id)
)
return
@@ -1235,7 +1235,7 @@ async def handle_reset_traffic_confirm(query, config_name: str, peer_index: int,
if not public_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"reset_traffic:{config_name}:{page}", user_id)
)
return
@@ -1254,26 +1254,26 @@ async def handle_reset_traffic_confirm(query, config_name: str, peer_index: int,
config_name_safe = escape_markdown(config_name)
public_key_short_safe = public_key[:30] + "..."
- message = f"⚠️ *{t(user_id, 'traffic.confirm')}*\n\n"
- message += f"{t(user_id, 'traffic.confirm_message')}\n\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name_safe}\n"
- message += f"*{t(user_id, 'schedule.config_label')}* {config_name_safe}\n"
- message += f"*{t(user_id, 'traffic.current_data')}*\n"
- message += f" ⬇️ {t(user_id, 'traffic.received', value=format_bytes_human(total_receive))}\n"
- message += f" ⬆️ {t(user_id, 'traffic.sent', value=format_bytes_human(total_sent))}\n"
- message += f" 📊 {t(user_id, 'traffic.total', value=total_data)}\n\n"
- message += f"*{t(user_id, 'configurations.public_key_label')}:* `{public_key_short_safe}`\n\n"
- message += f"⚠️ *{t(user_id, 'traffic.irreversible')}*"
+ message = f"⚠️ *{translate(user_id, 'traffic.confirm')}*\n\n"
+ message += f"{translate(user_id, 'traffic.confirm_message')}\n\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name_safe}\n"
+ message += f"*{translate(user_id, 'schedule.config_label')}* {config_name_safe}\n"
+ message += f"*{translate(user_id, 'traffic.current_data')}*\n"
+ message += f" ⬇️ {translate(user_id, 'traffic.received', value=format_bytes_human(total_receive))}\n"
+ message += f" ⬆️ {translate(user_id, 'traffic.sent', value=format_bytes_human(total_sent))}\n"
+ message += f" 📊 {ranslatet(user_id, 'traffic.total', value=total_data)}\n\n"
+ message += f"*{translate(user_id, 'configurations.public_key_label')}:* `{public_key_short_safe}`\n\n"
+ message += f"⚠️ *{translate(user_id, 'traffic.irreversible')}*"
# Crear teclado directamente sin usar confirmation_menu
keyboard = InlineKeyboardMarkup([
[
InlineKeyboardButton(
- t(user_id, "actions.yes") + ", " + t(user_id, "actions.clean_traffic"),
+ translate(user_id, "actions.yes") + ", " + translate(user_id, "actions.clean_traffic"),
callback_data=f"reset_traffic_final:{config_name}:{peer_index}:{page}"
),
InlineKeyboardButton(
- t(user_id, "actions.cancel"),
+ translate(user_id, "actions.cancel"),
callback_data=f"reset_traffic:{config_name}:{page}"
)
]
@@ -1293,13 +1293,13 @@ async def handle_reset_traffic_final(query, context: CallbackContext, config_nam
logger.info(f"Reset traffic - Config: {config_name}, Peer idx: {peer_idx}, Page: {page_num}")
- await query.edit_message_text(t(user_id, "traffic.resetting"))
+ await query.edit_message_text(translate(user_id, "traffic.resetting"))
# Obtener información del peer para obtener su clave pública
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"reset_traffic:{config_name}:{page_num}", user_id)
)
return
@@ -1308,7 +1308,7 @@ async def handle_reset_traffic_final(query, context: CallbackContext, config_nam
if peer_idx < 0 or peer_idx >= len(peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"reset_traffic:{config_name}:{page_num}", user_id)
)
return
@@ -1319,7 +1319,7 @@ async def handle_reset_traffic_final(query, context: CallbackContext, config_nam
if not public_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"reset_traffic:{config_name}:{page_num}", user_id)
)
return
@@ -1337,17 +1337,17 @@ async def handle_reset_traffic_final(query, context: CallbackContext, config_nam
config_name_safe = escape_markdown(config_name)
await query.edit_message_text(
- t(user_id, "success.data_reset", peer_name=peer_name_safe, config_name=config_name_safe),
+ translate(user_id, "success.data_reset", peer_name=peer_name_safe, config_name=config_name_safe),
reply_markup=InlineKeyboardMarkup([
- [InlineKeyboardButton(t(user_id, "actions.update_list"), callback_data=f"reset_traffic:{config_name}:{page_num}")],
- [InlineKeyboardButton(t(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}")]
+ [InlineKeyboardButton(translate(user_id, "actions.update_list"), callback_data=f"reset_traffic:{config_name}:{page_num}")],
+ [InlineKeyboardButton(translate(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}")]
]),
parse_mode="Markdown"
)
else:
error_msg = escape_markdown(result.get('message', 'Error desconocido'))
await query.edit_message_text(
- f"❌ *{t(user_id, 'errors.api_error', message=error_msg)}*\n\n"
+ f"❌ *{translate(user_id, 'errors.api_error', message=error_msg)}*\n\n"
f"*Endpoint usado:* /resetPeerData/{config_name}\n"
f"*Public key:* `{public_key[:30]}...`",
reply_markup=back_button(f"reset_traffic:{config_name}:{page_num}", user_id),
@@ -1474,24 +1474,24 @@ async def handle_restrict_confirm(query, context: CallbackContext, config_name:
allowed_ip = peer_info.get('allowed_ip', 'N/A')
status = peer_info.get('status', 'stopped')
- status_text = t(user_id, "status.connected") if status == 'running' else t(user_id, "status.disconnected")
+ status_text = translate(user_id, "status.connected") if status == 'running' else translate(user_id, "status.disconnected")
# Construir mensaje de confirmación
- message = f"{t(user_id, 'restrictions.confirm_title')}\n\n"
- message += f"{t(user_id, 'restrictions.confirm_message')}\n\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name}\n"
- message += f"*{t(user_id, 'schedule.config_label')}* {config_name}\n"
- message += f"*{t(user_id, 'restrictions.ip')}* `{allowed_ip}`\n"
- message += f"*{t(user_id, 'restrictions.status')}* {status_text}\n"
- message += f"*{t(user_id, 'restrictions.public_key')}* `{public_key[:30]}...`\n\n"
- message += t(user_id, 'restrictions.warning')
+ message = f"{translate(user_id, 'restrictions.confirm_title')}\n\n"
+ message += f"{translate(user_id, 'restrictions.confirm_message')}\n\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name}\n"
+ message += f"*{translate(user_id, 'schedule.config_label')}* {config_name}\n"
+ message += f"*{translate(user_id, 'restrictions.ip')}* `{allowed_ip}`\n"
+ message += f"*{translate(user_id, 'restrictions.status')}* {status_text}\n"
+ message += f"*{translate(user_id, 'restrictions.public_key')}* `{public_key[:30]}...`\n\n"
+ message += translate(user_id, 'restrictions.warning')
# Codificar la clave pública completa
from keyboards import safe_callback_data
safe_public_key = safe_callback_data(public_key)
# Teclado de confirmación
- keyboard = confirmation_menu(config_name, safe_public_key, "restrict", t(user_id, "actions.restrict"), user_id=user_id)
+ keyboard = confirmation_menu(config_name, safe_public_key, "restrict", translate(user_id, "actions.restrict"), user_id=user_id)
await query.edit_message_text(
message,
@@ -1502,29 +1502,29 @@ async def handle_restrict_confirm(query, context: CallbackContext, config_name:
except Exception as e:
logger.error(f"Error en confirmación de restricción: {str(e)}")
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.request_processing_error', message=str(e))}",
+ f"❌ {translate(user_id, 'errors.request_processing_error', message=str(e))}",
reply_markup=back_button(f"restrict_peer_menu:{config_name}:0", user_id)
)
async def handle_restrict_execute(query, config_name: str, public_key: str):
"""Ejecuta la acción de restringir un peer"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "restrictions.restricting"))
+ await query.edit_message_text(translate(user_id, "restrictions.restricting"))
result = api_client.restrict_peer(config_name, public_key)
if result.get("status"):
await query.edit_message_text(
- f"{t(user_id, 'restrictions.success')}\n\n"
- f"{t(user_id, 'restrictions.success_msg', config=config_name)}",
+ f"{translate(user_id, 'restrictions.success')}\n\n"
+ f"{translate(user_id, 'restrictions.success_msg', config=config_name)}",
reply_markup=back_button(f"restrict_peer_menu:{config_name}:0", user_id),
parse_mode="Markdown"
)
else:
error_msg = result.get('message', 'Error desconocido')
await query.edit_message_text(
- f"{t(user_id, 'restrictions.error')}\n\n"
- f"{t(user_id, 'restrictions.error_msg', message=error_msg)}",
+ f"{translate(user_id, 'restrictions.error')}\n\n"
+ f"{translate(user_id, 'restrictions.error_msg', message=error_msg)}",
reply_markup=back_button(f"restrict_peer_menu:{config_name}:0", user_id),
parse_mode="Markdown"
)
@@ -1534,7 +1534,7 @@ async def handle_main_menu(query):
"""Muestra el menú principal para administradores"""
user_id = query.from_user.id
await query.edit_message_text(
- t(user_id, "menu.main") + "\n" + t(user_id, "menu.select_option"),
+ translate(user_id, "menu.main") + "\n" + translate(user_id, "menu.select_option"),
reply_markup=main_menu(is_admin(user_id), is_operator(user_id), user_id),
parse_mode="Markdown"
)
@@ -1542,31 +1542,31 @@ async def handle_main_menu(query):
async def handle_handshake(query):
"""Verifica la conexión con la API"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "system.connecting"))
+ await query.edit_message_text(translate(user_id, "system.connecting"))
result = api_client.handshake()
if result.get("status"):
await query.edit_message_text(
- t(user_id, "success.connection_ok"),
+ translate(user_id, "success.connection_ok"),
reply_markup=refresh_button("handshake", user_id)
)
else:
await query.edit_message_text(
- t(user_id, "errors.connection_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.connection_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("handshake", user_id)
)
async def handle_configs(query, page: int = 0):
"""Muestra la lista de configuraciones"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "configurations.getting"))
+ await query.edit_message_text(translate(user_id, "configurations.getting"))
result = api_client.get_configurations()
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("configs", user_id)
)
return
@@ -1575,7 +1575,7 @@ async def handle_configs(query, page: int = 0):
if not configs:
await query.edit_message_text(
- t(user_id, "errors.no_configs"),
+ translate(user_id, "errors.no_configs"),
reply_markup=refresh_button("configs", user_id)
)
return
@@ -1586,10 +1586,10 @@ async def handle_configs(query, page: int = 0):
total_configs = len(configs)
total_pages = (total_configs - 1) // 8 + 1
- message = t(user_id, "configurations.title") + "\n"
- message += t(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n"
- message += t(user_id, "configurations.total", count=total_configs) + "\n\n"
- message += t(user_id, "configurations.select_config")
+ message = translate(user_id, "configurations.title") + "\n"
+ message += translate(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n"
+ message += translate(user_id, "configurations.total", count=total_configs) + "\n\n"
+ message += translate(user_id, "configurations.select_config")
await query.edit_message_text(
message,
@@ -1600,13 +1600,13 @@ async def handle_configs(query, page: int = 0):
async def handle_configs_summary(query):
"""Muestra un resumen de todas las configuraciones"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "configurations.getting_summary"))
+ await query.edit_message_text(translate(user_id, "configurations.getting_summary"))
result = api_client.get_configurations()
if not result.get("status"):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}",
+ f"❌ {translate(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}",
reply_markup=refresh_button("configs_summary", user_id)
)
return
@@ -1624,7 +1624,7 @@ async def show_configurations(update: Update, context: ContextTypes.DEFAULT_TYPE
"""Función auxiliar para mostrar menú de configuraciones"""
user_id = update.from_user.id if hasattr(update, 'message') else update.callback_query.from_user.id
if hasattr(update, 'message'):
- await update.message.reply_text(t(user_id, "configurations.getting"))
+ await update.message.reply_text(translate(user_id, "configurations.getting"))
query = update
is_message = True
else:
@@ -1634,7 +1634,7 @@ async def show_configurations(update: Update, context: ContextTypes.DEFAULT_TYPE
result = api_client.get_configurations()
if not result.get("status"):
- error_msg = f"❌ {t(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}"
+ error_msg = f"❌ {translate(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}"
if is_message:
await update.message.reply_text(error_msg)
else:
@@ -1644,7 +1644,7 @@ async def show_configurations(update: Update, context: ContextTypes.DEFAULT_TYPE
configs = result.get("data", [])
if not configs:
- no_configs_msg = t(user_id, "errors.no_configs_available")
+ no_configs_msg = translate(user_id, "errors.no_configs_available")
if is_message:
await update.message.reply_text(no_configs_msg)
else:
@@ -1656,10 +1656,10 @@ async def show_configurations(update: Update, context: ContextTypes.DEFAULT_TYPE
total_configs = len(configs)
total_pages = (total_configs - 1) // 8 + 1
- message = f"{t(user_id, 'configurations.available_configs')}\n"
- message += f"{t(user_id, 'configurations.page', current=page + 1, total=total_pages)}\n"
- message += f"{t(user_id, 'configurations.total_count', count=total_configs)}\n\n"
- message += t(user_id, "configurations.select_config_options")
+ message = f"{translate(user_id, 'configurations.available_configs')}\n"
+ message += f"{translate(user_id, 'configurations.page', current=page + 1, total=total_pages)}\n"
+ message += f"{translate(user_id, 'configurations.total_count', count=total_configs)}\n\n"
+ message += translate(user_id, "configurations.select_config_options")
if is_message:
await update.message.reply_text(message, reply_markup=keyboard, parse_mode="Markdown")
@@ -1669,13 +1669,13 @@ async def show_configurations(update: Update, context: ContextTypes.DEFAULT_TYPE
async def handle_config_detail(query, config_name: str):
"""Muestra el menú de una configuración específica"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "configurations.getting_info", name=config_name))
+ await query.edit_message_text(translate(user_id, "configurations.getting_info", name=config_name))
result = api_client.get_configuration_detail(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button("configs", user_id)
)
return
@@ -1695,18 +1695,18 @@ async def handle_config_detail(query, config_name: str):
if peers_result.get("status"):
restricted_count = peers_result.get("metadata", {}).get("restricted", 0)
- message = t(user_id, "configurations.detail", name=config_name) + "\n\n"
- message += t(user_id, "configurations.port", port=listen_port) + "\n"
- message += f"🔑 {t(user_id, 'configurations.public_key_label')}: `{public_key[:30]}...`\n"
- message += t(user_id, "configurations.peers", connected=connected_peers, total=total_peers) + "\n"
- message += t(user_id, "configurations.restricted", count=restricted_count) + "\n\n"
- message += t(user_id, "configurations.options") + "\n"
- message += t(user_id, "configurations.full_details") + "\n"
- message += t(user_id, "configurations.delete_peer") + "\n"
- message += t(user_id, "configurations.add_peer") + "\n"
- message += t(user_id, "configurations.schedule_jobs") + "\n"
- message += t(user_id, "configurations.restrictions") + "\n"
- message += t(user_id, "configurations.refresh")
+ message = translate(user_id, "configurations.detail", name=config_name) + "\n\n"
+ message += translate(user_id, "configurations.port", port=listen_port) + "\n"
+ message += f"🔑 {ranslatet(user_id, 'configurations.public_key_label')}: `{public_key[:30]}...`\n"
+ message += translate(user_id, "configurations.peers", connected=connected_peers, total=total_peers) + "\n"
+ message += translate(user_id, "configurations.restricted", count=restricted_count) + "\n\n"
+ message += translate(user_id, "configurations.options") + "\n"
+ message += translate(user_id, "configurations.full_details") + "\n"
+ message += translate(user_id, "configurations.delete_peer") + "\n"
+ message += translate(user_id, "configurations.add_peer") + "\n"
+ message += translate(user_id, "configurations.schedule_jobs") + "\n"
+ message += translate(user_id, "configurations.restrictions") + "\n"
+ message += translate(user_id, "configurations.refresh")
await query.edit_message_text(
message,
@@ -1718,17 +1718,17 @@ async def handle_operator_download_template(query, context: CallbackContext, con
"""Descarga plantilla para operadores (sin claves privadas)"""
try:
# Obtener información del servidor por defecto
- from config import WG_API_BASE_URL
+ from config import WGD_API_BASE_URL
import urllib.parse
- parsed_url = urllib.parse.urlparse(WG_API_BASE_URL)
+ parsed_url = urllib.parse.urlparse(WGD_API_BASE_URL)
server_host = parsed_url.hostname
# Obtener información de la configuración
config_result = api_client.get_configuration_detail(config_name)
if not config_result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.config_info_error"),
+ translate(user_id, "errors.config_info_error"),
reply_markup=operator_main_menu(user_id)
)
return
@@ -1744,10 +1744,10 @@ async def handle_operator_download_template(query, context: CallbackContext, con
allowed_ip = peer_data.get('allowed_ip', '10.21.0.2/32') if peer_data else "10.21.0.x/32"
# Usar endpoint personalizado si existe
- from config import WG_API_BASE_URL
+ from config import WGD_API_BASE_URL
import urllib.parse
- parsed_url = urllib.parse.urlparse(WG_API_BASE_URL)
+ parsed_url = urllib.parse.urlparse(WGD_API_BASE_URL)
server_host = parsed_url.hostname
if endpoint:
@@ -1757,9 +1757,9 @@ async def handle_operator_download_template(query, context: CallbackContext, con
endpoint_port = listen_port
# Construir plantilla internacionalizada
- template = f"{t(user_id, 'operator.template_header', peer_name=peer_name)}"
- template += f"\n[Interface]\nPrivateKey = {t(user_id, 'operator.template_private_key_placeholder')}\nAddress = {allowed_ip}\nDNS = {dns}\n\n[Peer]\nPublicKey = {server_public_key}\nAllowedIPs = 0.0.0.0/0\nEndpoint = {endpoint_host}:{endpoint_port}\nPersistentKeepalive = 21"
- template += f"{t(user_id, 'operator.template_limits_notice')}"
+ template = f"{translate(user_id, 'operator.template_header', peer_name=peer_name)}"
+ template += f"\n[Interface]\nPrivateKey = {translate(user_id, 'operator.template_private_key_placeholder')}\nAddress = {allowed_ip}\nDNS = {dns}\n\n[Peer]\nPublicKey = {server_public_key}\nAllowedIPs = 0.0.0.0/0\nEndpoint = {endpoint_host}:{endpoint_port}\nPersistentKeepalive = 21"
+ template += f"{translate(user_id, 'operator.template_limits_notice')}"
# Nombre del archivo
filename = f"{peer_name}_TEMPLATE.conf"
@@ -1772,17 +1772,17 @@ async def handle_operator_download_template(query, context: CallbackContext, con
# Enviar archivo
await query.message.reply_document(
document=InputFile(file_like, filename=filename),
- caption=t(user_id, "operator.template_caption", peer_name=peer_name)
+ caption=translate(user_id, "operator.template_caption", peer_name=peer_name)
)
# Actualizar mensaje con instrucciones
await query.edit_message_text(
- t(user_id, "operator.template_generated_body",
+ translate(user_id, "operator.template_generated_body",
peer_name=peer_name, allowed_ip=allowed_ip, dns=dns,
endpoint_host=endpoint_host, endpoint_port=endpoint_port,
server_public_key=server_public_key[:30]),
reply_markup=InlineKeyboardMarkup([
- [InlineKeyboardButton(t(user_id, "actions.back_to_menu"), callback_data="operator_main_menu")]
+ [InlineKeyboardButton(translate(user_id, "actions.back_to_menu"), callback_data="operator_main_menu")]
]),
parse_mode="Markdown"
)
@@ -1790,8 +1790,8 @@ async def handle_operator_download_template(query, context: CallbackContext, con
except Exception as e:
logger.error(f"Error al descargar plantilla para operador: {str(e)}")
await query.edit_message_text(
- f"❌ {t(user_id, 'operator.template_error')}\n\n"
- f"*{t(user_id, 'errors.error_label')}* {str(e)}",
+ f"❌ {translate(user_id, 'operator.template_error')}\n\n"
+ f"*{translate(user_id, 'errors.error_label')}* {str(e)}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
@@ -1804,13 +1804,13 @@ async def handle_peers_detailed(query, config_name: str):
async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0):
"""Muestra información detallada de peers paginada - VERSIÓN SIN FORMATO"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "peers.preparing_details"))
+ await query.edit_message_text(translate(user_id, "peers.preparing_details"))
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}",
+ f"❌ {translate(user_id, 'errors.api_error', message=result.get('message', 'Error desconocido'))}",
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1819,7 +1819,7 @@ async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0
if not peers:
await query.edit_message_text(
- t(user_id, "peers.no_peers_in_config", config=config_name),
+ translate(user_id, "peers.no_peers_in_config", config=config_name),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1839,9 +1839,9 @@ async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0
page_peers = peers[start_idx:end_idx]
# Construir mensaje para esta página - SIN FORMATO MARKDOWN
- message = f"{t(user_id, 'peers.details_title', config=config_name)}\n\n"
- message += f"{t(user_id, 'peers.page', current=page+1, total=total_pages)}\n"
- message += f"{t(user_id, 'peers.showing', start=start_idx+1, end=min(end_idx, total_peers), total=total_peers)}\n\n"
+ message = f"{translate(user_id, 'peers.details_title', config=config_name)}\n\n"
+ message += f"{translate(user_id, 'peers.page', current=page+1, total=total_pages)}\n"
+ message += f"{translate(user_id, 'peers.showing', start=start_idx+1, end=min(end_idx, total_peers), total=total_peers)}\n\n"
for i, peer in enumerate(page_peers, start_idx + 1):
message += f"Peer #{i}\n"
@@ -1855,12 +1855,12 @@ async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0
nav_buttons = []
if page > 0:
nav_buttons.append(
- InlineKeyboardButton(t(user_id, "peers.nav_prev"), callback_data=f"peers_detailed_paginated:{config_name}:{page-1}")
+ InlineKeyboardButton(translate(user_id, "peers.nav_prev"), callback_data=f"peers_detailed_paginated:{config_name}:{page-1}")
)
if page < total_pages - 1:
nav_buttons.append(
- InlineKeyboardButton(t(user_id, "peers.nav_next"), callback_data=f"peers_detailed_paginated:{config_name}:{page+1}")
+ InlineKeyboardButton(translate(user_id, "peers.nav_next"), callback_data=f"peers_detailed_paginated:{config_name}:{page+1}")
)
if nav_buttons:
@@ -1872,7 +1872,7 @@ async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0
# ])
keyboard.append([
- InlineKeyboardButton(t(user_id, "peers.back_menu"), callback_data=f"cfg:{config_name}")
+ InlineKeyboardButton(translate(user_id, "peers.back_menu"), callback_data=f"cfg:{config_name}")
])
await query.edit_message_text(
@@ -1884,13 +1884,13 @@ async def handle_peers_detailed_paginated(query, config_name: str, page: int = 0
async def handle_delete_peer_menu(query, config_name: str, page: int = 0):
"""Muestra el menú para seleccionar peer a eliminar"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "peers.getting"))
+ await query.edit_message_text(translate(user_id, "peers.getting"))
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1899,7 +1899,7 @@ async def handle_delete_peer_menu(query, config_name: str, page: int = 0):
if not peers:
await query.edit_message_text(
- t(user_id, "peers.no_peers", config=config_name),
+ translate(user_id, "peers.no_peers", config=config_name),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1933,15 +1933,15 @@ async def handle_delete_peer_menu(query, config_name: str, page: int = 0):
keyboard.append(nav_buttons)
# Botón para cancelar
- keyboard.append([InlineKeyboardButton(t(user_id, "actions.cancel"), callback_data=f"cfg:{config_name}")])
+ keyboard.append([InlineKeyboardButton(translate(user_id, "actions.cancel"), callback_data=f"cfg:{config_name}")])
total_peers = len(peers)
total_pages = (total_peers - 1) // 8 + 1
- message = t(user_id, "peers.delete_title", config=config_name) + "\n"
- message += t(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n"
- message += t(user_id, "configurations.total", count=total_peers) + "\n\n"
- message += t(user_id, "peers.delete_select")
+ message = translate(user_id, "peers.delete_title", config=config_name) + "\n"
+ message += translate(user_id, "configurations.page", current=page + 1, total=total_pages) + "\n"
+ message += translate(user_id, "configurations.total", count=total_peers) + "\n\n"
+ message += translate(user_id, "peers.delete_select")
await query.edit_message_text(
message,
@@ -1960,7 +1960,7 @@ async def handle_delete_peer_confirm(query, config_name: str, peer_index: str):
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1969,7 +1969,7 @@ async def handle_delete_peer_confirm(query, config_name: str, peer_index: str):
if idx < 0 or idx >= len(peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -1984,29 +1984,29 @@ async def handle_delete_peer_confirm(query, config_name: str, peer_index: str):
if not peer_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
- message = t(user_id, "peers.confirm_delete") + "\n\n"
- message += t(user_id, "peers.confirm_delete_message") + "\n\n"
- message += t(user_id, "peers.config", config=config_name) + "\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name}\n"
- message += t(user_id, "peers.public_key", key=peer_key[:30]) + "\n\n"
- message += t(user_id, "peers.irreversible")
+ message = translate(user_id, "peers.confirm_delete") + "\n\n"
+ message += translate(user_id, "peers.confirm_delete_message") + "\n\n"
+ message += translate(user_id, "peers.config", config=config_name) + "\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name}\n"
+ message += translate(user_id, "peers.public_key", key=peer_key[:30]) + "\n\n"
+ message += translate(user_id, "peers.irreversible")
# CORREGIDO: Añadir el cuarto parámetro action_text
await query.edit_message_text(
message,
- reply_markup=confirmation_menu(config_name, peer_index, "delete_peer", t(user_id, "actions.delete"), user_id=user_id),
+ reply_markup=confirmation_menu(config_name, peer_index, "delete_peer", translate(user_id, "actions.delete"), user_id=user_id),
parse_mode="Markdown"
)
except ValueError as e:
user_id = query.from_user.id
await query.edit_message_text(
- t(user_id, "errors.invalid_index") + f"\n{str(e)}",
+ translate(user_id, "errors.invalid_index") + f"\n{str(e)}",
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
@@ -2021,7 +2021,7 @@ async def handle_delete_peer_final(query, config_name: str, peer_index: str):
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -2030,7 +2030,7 @@ async def handle_delete_peer_final(query, config_name: str, peer_index: str):
if idx < 0 or idx >= len(peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
@@ -2040,30 +2040,30 @@ async def handle_delete_peer_final(query, config_name: str, peer_index: str):
if not peer_key:
await query.edit_message_text(
- t(user_id, "errors.no_public_key"),
+ translate(user_id, "errors.no_public_key"),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
return
- await query.edit_message_text(t(user_id, "peers.deleting"))
+ await query.edit_message_text(translate(user_id, "peers.deleting"))
result = api_client.delete_peer(config_name, peer_key)
if result.get("status"):
await query.edit_message_text(
- t(user_id, "success.peer_deleted", config_name=config_name),
+ translate(user_id, "success.peer_deleted", config_name=config_name),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
else:
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
except ValueError as e:
user_id = query.from_user.id
await query.edit_message_text(
- t(user_id, "errors.invalid_index") + f"\n{str(e)}",
+ translate(user_id, "errors.invalid_index") + f"\n{str(e)}",
reply_markup=back_button(f"cfg:{config_name}", user_id)
)
@@ -2082,21 +2082,21 @@ async def handle_add_peer(query, context: CallbackContext, config_name: str):
minutes = int((remaining.total_seconds() % 3600) // 60)
if hours > 0:
- time_msg = f"{hours} {t(user_id, 'time.hours')} {t(user_id, 'time.and')} {minutes} {t(user_id, 'time.minutes')}"
+ time_msg = f"{hours} {translate(user_id, 'time.hours')} {translate(user_id, 'time.and')} {minutes} {translate(user_id, 'time.minutes')}"
else:
- time_msg = f"{minutes} {t(user_id, 'time.minutes')}"
+ time_msg = f"{minutes} {translate(user_id, 'time.minutes')}"
await query.edit_message_text(
- f"⏰ {t(user_id, 'operator.limit_reached')}\n\n"
+ f"⏰ {translate(user_id, 'operator.limit_reached')}\n\n"
f"{error_msg}\n\n"
- f"⏳ {t(user_id, 'operator.time_remaining')}: *{time_msg}*\n\n"
- f"{t(user_id, 'operator.create_peer_after_time')}",
+ f"⏳ {translate(user_id, 'operator.time_remaining')}: *{time_msg}*\n\n"
+ f"{translate(user_id, 'operator.create_peer_after_time')}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
else:
await query.edit_message_text(
- f"❌ {t(user_id, 'operator.cannot_create_more_peers')}\n\n"
+ f"❌ {translate(user_id, 'operator.cannot_create_more_peers')}\n\n"
f"{error_msg}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
@@ -2127,24 +2127,24 @@ async def handle_add_peer(query, context: CallbackContext, config_name: str):
address = config_data.get('Address', '10.21.0.0/24')
listen_port = config_data.get('ListenPort', 'N/A')
- add_peer_title = t(user_id, "peer_creation.add_peer", config=config_name)
- current_config = t(user_id, "peer_creation.current_config")
- network = t(user_id, "peer_creation.network", address=address)
- port_label = t(user_id, "peer_creation.port_label", port=listen_port)
+ add_peer_title = translate(user_id, "peer_creation.add_peer", config=config_name)
+ current_config = translate(user_id, "peer_creation.current_config")
+ network = translate(user_id, "peer_creation.network", address=address)
+ port_label = translate(user_id, "peer_creation.port_label", port=listen_port)
message = f"{add_peer_title}\n\n"
message += f"{current_config}\n"
message += f"{network}\n"
message += f"{port_label}\n\n"
else:
- add_peer_title = t(user_id, "peer_creation.add_peer", config=config_name)
+ add_peer_title = translate(user_id, "peer_creation.add_peer", config=config_name)
message = f"{add_peer_title}\n\n"
- send_name = t(user_id, "peer_creation.send_name")
- requirements = t(user_id, "peer_creation.requirements")
- req_chars = t(user_id, "peer_creation.req_chars")
- req_length = t(user_id, "peer_creation.req_length")
- req_example = t(user_id, "peer_creation.req_example")
- send_now = t(user_id, "peer_creation.send_now_or_cancel")
+ send_name = translate(user_id, "peer_creation.send_name")
+ requirements = translate(user_id, "peer_creation.requirements")
+ req_chars = translate(user_id, "peer_creation.req_chars")
+ req_length = translate(user_id, "peer_creation.req_length")
+ req_example = translate(user_id, "peer_creation.req_example")
+ send_now = translate(user_id, "peer_creation.send_now_or_cancel")
message += f"{send_name}\n\n"
message += f"{requirements}\n"
message += f"{req_chars}\n"
@@ -2153,9 +2153,9 @@ async def handle_add_peer(query, context: CallbackContext, config_name: str):
message += send_now
if is_operator(user_id):
- keyboard = [[InlineKeyboardButton(t(user_id, "actions.cancel"), callback_data="operator_main_menu")]]
+ keyboard = [[InlineKeyboardButton(translate(user_id, "actions.cancel"), callback_data="operator_main_menu")]]
else:
- keyboard = [[InlineKeyboardButton(t(user_id, "actions.cancel"), callback_data=f"cfg:{config_name}")]]
+ keyboard = [[InlineKeyboardButton(translate(user_id, "actions.cancel"), callback_data=f"cfg:{config_name}")]]
await query.edit_message_text(
message,
@@ -2167,7 +2167,7 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
"""Descarga la configuración de un peer usando hash"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "operator.downloading"))
+ await query.edit_message_text(translate(user_id, "operator.downloading"))
try:
# Obtener datos del peer desde el contexto
@@ -2189,7 +2189,7 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
if not peer_data:
await query.edit_message_text(
- t(user_id, "errors.no_peer_info"),
+ translate(user_id, "errors.no_peer_info"),
reply_markup=operator_main_menu(user_id) if is_operator(user_id) else main_menu(is_admin(user_id), is_operator(user_id), user_id)
)
return
@@ -2208,17 +2208,17 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
return
# Obtener información del servidor (usar endpoint personalizado si existe)
- from config import WG_API_BASE_URL
+ from config import WGD_API_BASE_URL
import urllib.parse
- parsed_url = urllib.parse.urlparse(WG_API_BASE_URL)
+ parsed_url = urllib.parse.urlparse(WGD_API_BASE_URL)
server_host = parsed_url.hostname
# Obtener información de la configuración
config_result = api_client.get_configuration_detail(config_name)
if not config_result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.config_info_error"),
+ translate(user_id, "errors.config_info_error"),
reply_markup=operator_main_menu(user_id) if is_operator(user_id) else back_button(f"cfg:{config_name}", user_id)
)
return
@@ -2229,7 +2229,7 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
if not server_public_key:
await query.edit_message_text(
- t(user_id, "errors.server_key_error"),
+ translate(user_id, "errors.server_key_error"),
reply_markup=operator_main_menu(user_id) if is_operator(user_id) else back_button(f"cfg:{config_name}", user_id)
)
return
@@ -2269,30 +2269,30 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
# Primero, enviar el archivo
await query.message.reply_document(
document=InputFile(file_like, filename=filename),
- caption=t(user_id, "operator.download_caption", peer_name=peer_name, config_name=config_name)
+ caption=translate(user_id, "operator.download_caption", peer_name=peer_name, config_name=config_name)
)
# Luego actualizar el mensaje original
if is_operator(user_id):
# Para operadores: botón para volver al menú
keyboard = [
- [InlineKeyboardButton(t(user_id, "actions.back_to_menu"), callback_data="operator_main_menu")],
- [InlineKeyboardButton(t(user_id, "actions.download_again"), callback_data=f"download_config:{peer_hash}")]
+ [InlineKeyboardButton(translate(user_id, "actions.back_to_menu"), callback_data="operator_main_menu")],
+ [InlineKeyboardButton(translate(user_id, "actions.download_again"), callback_data=f"download_config:{peer_hash}")]
]
await query.edit_message_text(
- t(user_id, "operator.download_success", filename=filename, host=endpoint_host, port=endpoint_port, ip=allowed_ip),
+ translate(user_id, "operator.download_success", filename=filename, host=endpoint_host, port=endpoint_port, ip=allowed_ip),
reply_markup=InlineKeyboardMarkup(keyboard),
parse_mode="Markdown"
)
else:
# Para admins: botones normales
await query.edit_message_text(
- t(user_id, "operator.download_success_admin", filename=filename, host=endpoint_host, port=endpoint_port, ip=allowed_ip),
+ translate(user_id, "operator.download_success_admin", filename=filename, host=endpoint_host, port=endpoint_port, ip=allowed_ip),
reply_markup=InlineKeyboardMarkup([
[
- InlineKeyboardButton(t(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}"),
- InlineKeyboardButton(t(user_id, "actions.download_again"), callback_data=f"download_config:{peer_hash}")
+ InlineKeyboardButton(translate(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}"),
+ InlineKeyboardButton(translate(user_id, "actions.download_again"), callback_data=f"download_config:{peer_hash}")
]
]),
parse_mode="Markdown"
@@ -2301,7 +2301,7 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
except Exception as e:
logger.error(f"Error al descargar configuración: {str(e)}")
await query.edit_message_text(
- t(user_id, "operator.download_error", message=str(e)),
+ translate(user_id, "operator.download_error", message=str(e)),
reply_markup=operator_main_menu(user_id) if is_operator(user_id) else back_button("main_menu", user_id),
parse_mode="Markdown"
)
@@ -2309,12 +2309,12 @@ async def handle_download_peer_config(query, context: CallbackContext, peer_hash
async def handle_schedule_jobs_menu(query, context: CallbackContext, config_name: str):
"""Muestra el menú inicial de Schedule Jobs con lista de peers"""
user_id = query.from_user.id
- getting_peers_text = t(user_id, "schedule.getting_peers", config=config_name)
+ getting_peers_text = translate(user_id, "schedule.getting_peers", config=config_name)
await query.edit_message_text(getting_peers_text)
result = api_client.get_peers(config_name)
if not result.get("status"):
- error_msg = t(user_id, "errors.api_error", message=result.get('message'))
+ error_msg = translate(user_id, "errors.api_error", message=result.get('message'))
await query.edit_message_text(
error_msg,
reply_markup=back_button(f"cfg:{config_name}", user_id)
@@ -2324,7 +2324,7 @@ async def handle_schedule_jobs_menu(query, context: CallbackContext, config_name
peers = result.get("data", [])
if not peers:
- no_peers_text = t(user_id, "errors.no_peers", config=config_name)
+ no_peers_text = translate(user_id, "errors.no_peers", config=config_name)
await query.edit_message_text(
no_peers_text,
reply_markup=back_button(f"cfg:{config_name}", user_id)
@@ -2343,14 +2343,14 @@ async def handle_schedule_jobs_menu(query, context: CallbackContext, config_name
])
# Botones de navegación
- back_text = t(user_id, "menu.back")
+ back_text = translate(user_id, "menu.back")
keyboard.append([
InlineKeyboardButton(back_text, callback_data=f"cfg:{config_name}")
])
- title = t(user_id, "schedule.title", config=config_name)
- total_peers = t(user_id, "schedule.total_peers", count=len(peers))
- select_peer = t(user_id, "schedule.select_peer")
+ title = translate(user_id, "schedule.title", config=config_name)
+ total_peers = translate(user_id, "schedule.total_peers", count=len(peers))
+ select_peer = translate(user_id, "schedule.select_peer")
message = f"{title}\n\n"
message += f"{total_peers}\n\n"
@@ -2365,12 +2365,12 @@ async def handle_schedule_jobs_menu(query, context: CallbackContext, config_name
async def handle_schedule_job_peer_selected(query, context: CallbackContext, config_name: str, peer_index: int):
"""Muestra el menú de Schedule Jobs para un peer específico"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "schedule.getting_peer_info"))
+ await query.edit_message_text(translate(user_id, "schedule.getting_peer_info"))
# Obtener información del peer usando el índice
result = api_client.get_peers(config_name)
if not result.get("status"):
- error_msg = t(user_id, "errors.api_error", message=result.get('message'))
+ error_msg = translate(user_id, "errors.api_error", message=result.get('message'))
await query.edit_message_text(
error_msg,
reply_markup=back_button(f"schedule_jobs_menu:{config_name}", user_id)
@@ -2381,7 +2381,7 @@ async def handle_schedule_job_peer_selected(query, context: CallbackContext, con
if peer_index < 0 or peer_index >= len(peers):
await query.edit_message_text(
- t(user_id, "errors.invalid_index"),
+ translate(user_id, "errors.invalid_index"),
reply_markup=back_button(f"schedule_jobs_menu:{config_name}", user_id)
)
return
@@ -2401,31 +2401,31 @@ async def handle_schedule_job_peer_selected(query, context: CallbackContext, con
peer_name_html = html_escape(peer_name)
config_name_html = html_escape(config_name)
- title_text = t(user_id, "schedule.for_peer", peer=peer_name_html)
+ title_text = translate(user_id, "schedule.for_peer", peer=peer_name_html)
message = f"{html_escape(title_text)}\n\n"
- message += f"{html_escape(t(user_id, 'schedule.config_label'))}: {config_name_html}\n"
- message += f"{html_escape(t(user_id, 'schedule.peer_label'))}: {peer_name_html}\n\n"
+ message += f"{html_escape(translate(user_id, 'schedule.config_label'))}: {config_name_html}\n"
+ message += f"{html_escape(translate(user_id, 'schedule.peer_label'))}: {peer_name_html}\n\n"
if jobs:
- active_jobs_text = t(user_id, "schedule.active_jobs", count=len(jobs))
+ active_jobs_text = translate(user_id, "schedule.active_jobs", count=len(jobs))
message += f"{html_escape(active_jobs_text)}\n\n"
for i, job in enumerate(jobs, 1):
job_text = format_schedule_job_for_list(job, user_id)
message += f"{i}. {html_escape(job_text)}\n"
message += "\n"
else:
- no_jobs_text = t(user_id, "schedule.no_jobs")
+ no_jobs_text = translate(user_id, "schedule.no_jobs")
message += f"{html_escape(no_jobs_text)}\n"
- add_new_text = t(user_id, "schedule.add_new")
+ add_new_text = translate(user_id, "schedule.add_new")
message += f"{html_escape(add_new_text)}"
# Crear teclado
keyboard = []
# Botones para agregar jobs
- data_limit_text = t(user_id, "schedule.data_limit_button")
- expiry_date_text = t(user_id, "schedule.expiry_date_button")
+ data_limit_text = translate(user_id, "schedule.data_limit_button")
+ expiry_date_text = translate(user_id, "schedule.expiry_date_button")
keyboard.append([
InlineKeyboardButton(data_limit_text, callback_data=f"add_schedule_job_data:{config_name}:{peer_index}"),
InlineKeyboardButton(expiry_date_text, callback_data=f"add_schedule_job_date:{config_name}:{peer_index}")
@@ -2433,15 +2433,15 @@ async def handle_schedule_job_peer_selected(query, context: CallbackContext, con
# Si hay jobs, mostrar botones para eliminarlos
if jobs:
- delete_job_text = t(user_id, "schedule.delete_job")
+ delete_job_text = translate(user_id, "schedule.delete_job")
keyboard.append([
InlineKeyboardButton(delete_job_text, callback_data=f"delete_schedule_job_all:{config_name}:{peer_index}")
])
# Botones de navegación
keyboard.append([
- InlineKeyboardButton(t(user_id, "schedule.back_list"), callback_data=f"schedule_jobs_menu:{config_name}"),
- InlineKeyboardButton(t(user_id, "schedule.update"), callback_data=f"schedule_job_peer:{config_name}:{peer_index}")
+ InlineKeyboardButton(translate(user_id, "schedule.back_list"), callback_data=f"schedule_jobs_menu:{config_name}"),
+ InlineKeyboardButton(translate(user_id, "schedule.update"), callback_data=f"schedule_job_peer:{config_name}:{peer_index}")
])
await query.edit_message_text(
@@ -2461,7 +2461,7 @@ async def handle_add_schedule_job_data(query, context: CallbackContext, config_n
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
+ f"❌ {translate(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{idx}", user_id)
)
return
@@ -2469,7 +2469,7 @@ async def handle_add_schedule_job_data(query, context: CallbackContext, config_n
peers = result.get("data", [])
if idx < 0 or idx >= len(peers):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.invalid_index')}",
+ f"❌ {translate(user_id, 'errors.invalid_index')}",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{idx}", user_id)
)
return
@@ -2492,16 +2492,16 @@ async def handle_add_schedule_job_data(query, context: CallbackContext, config_n
# Usar HTML para evitar problemas de parsing
peer_name_html = html_escape(peer_name)
- message = f"⏰ {html_escape(t(user_id, 'schedule.add_data_title', peer=peer_name_html))}\n\n"
- message += html_escape(t(user_id, "schedule.add_data_msg")) + "\n\n"
- message += html_escape(t(user_id, "schedule.add_data_example")) + "\n\n"
- message += html_escape(t(user_id, "schedule.add_data_auto")) + "\n\n"
- message += html_escape(t(user_id, "schedule.send_cancel"))
+ message = f"⏰ {html_escape(translate(user_id, 'schedule.add_data_title', peer=peer_name_html))}\n\n"
+ message += html_escape(translate(user_id, "schedule.add_data_msg")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.add_data_example")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.add_data_auto")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.send_cancel"))
# Guardar en el contexto que estamos esperando el valor
context.user_data['waiting_for_schedule_job_value'] = True
- keyboard = [[InlineKeyboardButton(t(user_id, "schedule.cancel_button"), callback_data=f"schedule_job_peer:{config_name}:{idx}")]]
+ keyboard = [[InlineKeyboardButton(translate(user_id, "schedule.cancel_button"), callback_data=f"schedule_job_peer:{config_name}:{idx}")]]
try:
await query.edit_message_text(
@@ -2527,7 +2527,7 @@ async def handle_add_schedule_job_date(query, context: CallbackContext, config_n
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
+ f"❌ {translate(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{idx}", user_id)
)
return
@@ -2535,7 +2535,7 @@ async def handle_add_schedule_job_date(query, context: CallbackContext, config_n
peers = result.get("data", [])
if idx < 0 or idx >= len(peers):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.invalid_index')}",
+ f"❌ {translate(user_id, 'errors.invalid_index')}",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{idx}", user_id)
)
return
@@ -2558,16 +2558,16 @@ async def handle_add_schedule_job_date(query, context: CallbackContext, config_n
# Usar HTML para evitar problemas de parsing
peer_name_html = html_escape(peer_name)
- message = f"⏰ {html_escape(t(user_id, 'schedule.add_date_title', peer=peer_name_html))}\n\n"
- message += html_escape(t(user_id, "schedule.add_date_msg")) + "\n\n"
- message += html_escape(t(user_id, "schedule.add_date_format")) + "\n\n"
- message += html_escape(t(user_id, "schedule.add_date_auto")) + "\n\n"
- message += html_escape(t(user_id, "schedule.send_cancel"))
+ message = f"⏰ {html_escape(translate(user_id, 'schedule.add_date_title', peer=peer_name_html))}\n\n"
+ message += html_escape(translate(user_id, "schedule.add_date_msg")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.add_date_format")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.add_date_auto")) + "\n\n"
+ message += html_escape(translate(user_id, "schedule.send_cancel"))
# Guardar en el contexto que estamos esperando el valor
context.user_data['waiting_for_schedule_job_value'] = True
- keyboard = [[InlineKeyboardButton(t(user_id, "schedule.cancel_button"), callback_data=f"schedule_job_peer:{config_name}:{idx}")]]
+ keyboard = [[InlineKeyboardButton(translate(user_id, "schedule.cancel_button"), callback_data=f"schedule_job_peer:{config_name}:{idx}")]]
try:
await query.edit_message_text(
@@ -2585,13 +2585,13 @@ async def handle_add_schedule_job_date(query, context: CallbackContext, config_n
async def handle_schedule_jobs_list(query, context: CallbackContext, config_name: str, peer_index: str, page: int = 0):
"""Muestra la lista paginada de Schedule Jobs"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "schedule.getting_peer_jobs"))
+ await query.edit_message_text(translate(user_id, "schedule.getting_peer_jobs"))
# Obtener información del peer
result = api_client.get_peers(config_name)
if not result.get("status"):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
+ f"❌ {translate(user_id, 'errors.get_peer_info_error', message=result.get('message'))}",
reply_markup=back_button(f"schedule_jobs_menu:{config_name}", user_id)
)
return
@@ -2601,7 +2601,7 @@ async def handle_schedule_jobs_list(query, context: CallbackContext, config_name
idx = int(peer_index)
if idx < 0 or idx >= len(peers):
await query.edit_message_text(
- f"❌ {t(user_id, 'errors.invalid_index')}",
+ f"❌ {translate(user_id, 'errors.invalid_index')}",
reply_markup=back_button(f"schedule_jobs_menu:{config_name}", user_id)
)
return
@@ -2620,12 +2620,12 @@ async def handle_schedule_jobs_list(query, context: CallbackContext, config_name
job_text = format_schedule_job_for_list(job, user_id)
# Información del job
- keyboard.append([InlineKeyboardButton(f"{t(user_id, 'peers.peer_number', number=i+1)}. {job_text}", callback_data="noop")])
+ keyboard.append([InlineKeyboardButton(f"{translate(user_id, 'peers.peer_number', number=i+1)}. {job_text}", callback_data="noop")])
# Botón para eliminar este job - usando el callback_data correcto
keyboard.append([
InlineKeyboardButton(
- t(user_id, "actions.delete_this_job"),
+ translate(user_id, "actions.delete_this_job"),
callback_data=f"delete_schedule_job_confirm:{config_name}:{idx}:{i}"
)
])
@@ -2635,12 +2635,12 @@ async def handle_schedule_jobs_list(query, context: CallbackContext, config_name
if page > 0:
nav_buttons.append(
- InlineKeyboardButton(t(user_id, "menu.previous"), callback_data=f"page_schedule_jobs:{config_name}:{idx}:{page-1}")
+ InlineKeyboardButton(translate(user_id, "menu.previous"), callback_data=f"page_schedule_jobs:{config_name}:{idx}:{page-1}")
)
if end_idx < len(jobs):
nav_buttons.append(
- InlineKeyboardButton(t(user_id, "menu.next"), callback_data=f"page_schedule_jobs:{config_name}:{idx}:{page+1}")
+ InlineKeyboardButton(translate(user_id, "menu.next"), callback_data=f"page_schedule_jobs:{config_name}:{idx}:{page+1}")
)
if nav_buttons:
@@ -2648,11 +2648,11 @@ async def handle_schedule_jobs_list(query, context: CallbackContext, config_name
# Botones de acción
keyboard.append([
- InlineKeyboardButton(t(user_id, "schedule.add_new_button"), callback_data=f"add_schedule_job_data:{config_name}:{idx}"),
- InlineKeyboardButton(t(user_id, "schedule.delete_one_button"), callback_data=f"delete_schedule_job_all:{config_name}:{idx}")
+ InlineKeyboardButton(translate(user_id, "schedule.add_new_button"), callback_data=f"add_schedule_job_data:{config_name}:{idx}"),
+ InlineKeyboardButton(translate(user_id, "schedule.delete_one_button"), callback_data=f"delete_schedule_job_all:{config_name}:{idx}")
])
keyboard.append([
- InlineKeyboardButton(t(user_id, "menu.back"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
+ InlineKeyboardButton(translate(user_id, "menu.back"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
])
total_jobs = len(jobs)
@@ -2677,7 +2677,7 @@ async def handle_delete_schedule_job_confirm(query, context: CallbackContext, co
idx = int(peer_index)
# Obtener información actualizada del peer
- await query.edit_message_text(t(user_id, "schedule.getting_jobs"))
+ await query.edit_message_text(translate(user_id, "schedule.getting_jobs"))
result = api_client.get_peers(config_name)
if not result.get("status"):
@@ -2722,11 +2722,11 @@ async def handle_delete_schedule_job_confirm(query, context: CallbackContext, co
])
keyboard.append([
- InlineKeyboardButton(t(user_id, "schedule.back_list"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
+ InlineKeyboardButton(translate(user_id, "schedule.back_list"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
])
- message = t(user_id, "schedule.delete_confirm_title", peer=peer_name) + "\n\n"
- message += t(user_id, "schedule.delete_select") + "\n\n"
+ message = translate(user_id, "schedule.delete_confirm_title", peer=peer_name) + "\n\n"
+ message += translate(user_id, "schedule.delete_select") + "\n\n"
await query.edit_message_text(
message,
@@ -2760,25 +2760,25 @@ async def handle_delete_schedule_job_confirm(query, context: CallbackContext, co
value = job.get('Value', 'N/A')
if field == "total_data":
- field_display = t(user_id, "schedule.data_limit_label", value=value)
+ field_display = translate(user_id, "schedule.data_limit_label", value=value)
elif field == "date":
- field_display = t(user_id, "schedule.expiry_date_label", value=value)
+ field_display = translate(user_id, "schedule.expiry_date_label", value=value)
else:
field_display = f"{field}: {value}"
- message = f"{t(user_id, 'schedule.delete_confirm_single')}\n\n"
- message += f"{t(user_id, 'schedule.delete_confirm_msg')}\n\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name}\n"
- message += f"*{t(user_id, 'schedule.config_label')}* {config_name}\n"
- message += f"*{t(user_id, 'schedule.action_label')}* {action.upper()}\n"
+ message = f"{translate(user_id, 'schedule.delete_confirm_single')}\n\n"
+ message += f"{translate(user_id, 'schedule.delete_confirm_msg')}\n\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name}\n"
+ message += f"*{translate(user_id, 'schedule.config_label')}* {config_name}\n"
+ message += f"*{translate(user_id, 'schedule.action_label')}* {action.upper()}\n"
message += f"*{field_display}*\n\n"
- message += t(user_id, "schedule.irreversible")
+ message += translate(user_id, "schedule.irreversible")
# CORREGIDO: Usar el formato simplificado en lugar de confirmation_menu
keyboard = [
[
- InlineKeyboardButton(t(user_id, "schedule.yes_delete"), callback_data=f"delete_schedule_job_execute:{config_name}:{idx}:{job_idx}"),
- InlineKeyboardButton(t(user_id, "schedule.no_cancel"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
+ InlineKeyboardButton(translate(user_id, "schedule.yes_delete"), callback_data=f"delete_schedule_job_execute:{config_name}:{idx}:{job_idx}"),
+ InlineKeyboardButton(translate(user_id, "schedule.no_cancel"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
]
]
@@ -2803,7 +2803,7 @@ async def handle_delete_schedule_job_execute(query, context: CallbackContext, co
idx = int(peer_index)
job_idx = int(job_index)
- await query.edit_message_text(t(user_id, "schedule.deleting"))
+ await query.edit_message_text(translate(user_id, "schedule.deleting"))
# Obtener información actualizada del peer
result = api_client.get_peers(config_name)
@@ -2857,10 +2857,10 @@ async def handle_delete_schedule_job_execute(query, context: CallbackContext, co
config_name_escaped = config_name
await query.edit_message_text(
- f"{t(user_id, 'schedule.delete_success_title')}\n\n"
- f"*{t(user_id, 'schedule.peer_label')}* {peer_name_escaped}\n"
- f"*{t(user_id, 'schedule.job_label')}* {job_info_escaped}\n\n"
- f"{t(user_id, 'schedule.delete_permanent')}",
+ f"{translate(user_id, 'schedule.delete_success_title')}\n\n"
+ f"*{translate(user_id, 'schedule.peer_label')}* {peer_name_escaped}\n"
+ f"*{translate(user_id, 'schedule.job_label')}* {job_info_escaped}\n\n"
+ f"{translate(user_id, 'schedule.delete_permanent')}",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{idx}", user_id),
parse_mode="Markdown" # Markdown format
)
@@ -2870,20 +2870,20 @@ async def handle_delete_schedule_job_execute(query, context: CallbackContext, co
# Ofrecer opciones alternativas
keyboard = [
[
- InlineKeyboardButton(t(user_id, "actions.try_again"), callback_data=f"delete_schedule_job_execute:{config_name}:{idx}:{job_idx}"),
- InlineKeyboardButton(t(user_id, "actions.view_list"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
+ InlineKeyboardButton(translate(user_id, "actions.try_again"), callback_data=f"delete_schedule_job_execute:{config_name}:{idx}:{job_idx}"),
+ InlineKeyboardButton(translate(user_id, "actions.view_list"), callback_data=f"schedule_job_peer:{config_name}:{idx}")
],
[
- InlineKeyboardButton(t(user_id, "menu.help"), callback_data="help"),
- InlineKeyboardButton(t(user_id, "menu.main"), callback_data="main_menu")
+ InlineKeyboardButton(translate(user_id, "menu.help"), callback_data="help"),
+ InlineKeyboardButton(translate(user_id, "menu.main"), callback_data="main_menu")
]
]
await query.edit_message_text(
- f"❌ *{t(user_id, 'schedule.delete_error_title')}*\n\n"
- f"*{t(user_id, 'schedule.job_label')}* {job_info_escaped}\n"
+ f"❌ *{translate(user_id, 'schedule.delete_error_title')}*\n\n"
+ f"*{translate(user_id, 'schedule.job_label')}* {job_info_escaped}\n"
f"*Error:* {error_msg}\n\n"
- f"{t(user_id, 'schedule.solutions')}",
+ f"{translate(user_id, 'schedule.solutions')}",
reply_markup=InlineKeyboardMarkup(keyboard),
parse_mode="Markdown" # Markdown format
)
@@ -2892,7 +2892,7 @@ async def handle_delete_schedule_job_execute(query, context: CallbackContext, co
logger.error(f"Error al eliminar schedule job: {str(e)}", exc_info=True)
error_msg = str(e)
await query.edit_message_text(
- f"❌ *{t(user_id, 'schedule.delete_error_title')}*\n\n"
+ f"❌ *{translate(user_id, 'schedule.delete_error_title')}*\n\n"
f"*Error:* {error_msg}\n\n",
reply_markup=back_button(f"schedule_job_peer:{config_name}:{peer_index}", user_id),
parse_mode="Markdown" # Markdown format
@@ -2901,13 +2901,13 @@ async def handle_delete_schedule_job_execute(query, context: CallbackContext, co
async def handle_system_status(query):
"""Muestra el estado del sistema"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "system.getting_stats"))
+ await query.edit_message_text(translate(user_id, "system.getting_stats"))
result = api_client.get_system_status()
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("system_status", user_id)
)
return
@@ -2924,13 +2924,13 @@ async def handle_system_status(query):
async def handle_protocols(query):
"""Muestra los protocolos habilitados"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "protocols.getting"))
+ await query.edit_message_text(translate(user_id, "protocols.getting"))
result = api_client.get_protocols()
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')),
reply_markup=refresh_button("protocols", user_id)
)
return
@@ -2939,21 +2939,21 @@ async def handle_protocols(query):
if not protocols:
await query.edit_message_text(
- t(user_id, "errors.no_configs"),
+ translate(user_id, "errors.no_configs"),
reply_markup=refresh_button("protocols", user_id)
)
return
- message = t(user_id, "protocols.title") + "\n\n"
+ message = translate(user_id, "protocols.title") + "\n\n"
for protocol in protocols:
if protocol in ["wg", "awg"]:
- message += t(user_id, f"protocols.{protocol}") + "\n"
+ message += translate(user_id, f"protocols.{protocol}") + "\n"
else:
emoji = "✅" if protocol in ["wg", "awg"] else "⚙️"
message += f"{emoji} {protocol.upper()}\n"
- message += "\n" + t(user_id, "protocols.notes")
+ message += "\n" + translate(user_id, "protocols.notes")
await query.edit_message_text(
message,
@@ -2964,14 +2964,14 @@ async def handle_protocols(query):
async def handle_stats(query):
"""Muestra estadísticas específicas de WireGuard sin datos de transferencia"""
user_id = query.from_user.id
- await query.edit_message_text(t(user_id, "system.getting_stats"))
+ await query.edit_message_text(translate(user_id, "system.getting_stats"))
# Obtener todas las configuraciones para calcular estadísticas
configs_result = api_client.get_configurations()
if not configs_result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=configs_result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=configs_result.get('message', 'Error desconocido')),
reply_markup=refresh_button("stats", user_id)
)
return
@@ -2980,13 +2980,13 @@ async def handle_stats(query):
if not configs:
await query.edit_message_text(
- t(user_id, "errors.no_configs"),
+ translate(user_id, "errors.no_configs"),
reply_markup=refresh_button("stats", user_id)
)
return
lines = []
- lines.append(t(user_id, "system.status") + "\n")
+ lines.append(translate(user_id, "system.status") + "\n")
total_peers = 0
total_connected = 0
@@ -3001,18 +3001,18 @@ async def handle_stats(query):
total_connected += config_connected
# Estadísticas generales
- lines.append(t(user_id, "system.total_configs", count=len(configs)))
- lines.append(t(user_id, "system.total_peers", count=total_peers))
- lines.append(t(user_id, "system.connected_peers", count=total_connected))
+ lines.append(translate(user_id, "system.total_configs", count=len(configs)))
+ lines.append(translate(user_id, "system.total_peers", count=total_peers))
+ lines.append(translate(user_id, "system.connected_peers", count=total_connected))
if total_peers > 0:
connection_rate = (total_connected / total_peers) * 100
- lines.append(t(user_id, "system.connection_rate", percent=connection_rate) + "\n")
+ lines.append(translate(user_id, "system.connection_rate", percent=connection_rate) + "\n")
else:
lines.append("\n")
# Configuraciones individuales (mostrar solo las principales)
- lines.append(t(user_id, "system.active_configs"))
+ lines.append(translate(user_id, "system.active_configs"))
# Ordenar configuraciones por número de peers conectados (más activas primero)
sorted_configs = sorted(configs, key=lambda x: x.get('ConnectedPeers', 0), reverse=True)
@@ -3024,15 +3024,15 @@ async def handle_stats(query):
listen_port = config.get('ListenPort', 'N/A')
status_emoji = "✅" if connected > 0 else "⚠️"
- lines.append(f" {status_emoji} **{name}** ({t(user_id, 'system.port', port=listen_port)})")
- lines.append(f" 👥 {t(user_id, 'system.connected', connected=connected, peers=peers)}")
+ lines.append(f" {status_emoji} **{name}** ({translate(user_id, 'system.port', port=listen_port)})")
+ lines.append(f" 👥 {ranslatet(user_id, 'system.connected', connected=connected, peers=peers)}")
if len(configs) > 5:
- lines.append(t(user_id, "system.and_more", count=len(configs) - 5))
+ lines.append(translate(user_id, "system.and_more", count=len(configs) - 5))
# Agregar timestamp de la última actualización
from datetime import datetime
- lines.append(t(user_id, "system.last_update", date=datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
+ lines.append(translate(user_id, "system.last_update", date=datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
formatted_text = "\n".join(lines)
@@ -3047,7 +3047,7 @@ async def handle_help(query):
user_id = query.from_user.id
if is_operator(user_id):
- help_text = t(user_id, "help.operator")
+ help_text = translate(user_id, "help.operator")
await query.edit_message_text(
help_text,
@@ -3055,7 +3055,7 @@ async def handle_help(query):
parse_mode="Markdown"
)
else:
- help_text = t(user_id, "help.admin")
+ help_text = translate(user_id, "help.admin")
await query.edit_message_text(
help_text,
@@ -3069,13 +3069,13 @@ async def handle_operators_list(query, context: CallbackContext):
if not is_admin(user_id):
await query.edit_message_text(
- t(user_id, "errors.access_denied"),
+ translate(user_id, "errors.access_denied"),
reply_markup=main_menu(is_admin(user_id), is_operator(user_id), user_id),
parse_mode="Markdown"
)
return
- await query.edit_message_text(t(user_id, "operators.getting_info"))
+ await query.edit_message_text(translate(user_id, "operators.getting_info"))
# Obtener información de todos los operadores
operators_info = []
@@ -3086,14 +3086,14 @@ async def handle_operators_list(query, context: CallbackContext):
if not operator_users:
await query.edit_message_text(
- t(user_id, "errors.no_operators"),
+ translate(user_id, "errors.no_operators"),
reply_markup=refresh_button("operators_list", user_id),
parse_mode="Markdown"
)
return
# Construir mensaje con información detallada
- message_lines = [t(user_id, "operators.title") + "\n"]
+ message_lines = [translate(user_id, "operators.title") + "\n"]
total_peers_created = 0
@@ -3105,7 +3105,7 @@ async def handle_operators_list(query, context: CallbackContext):
match = re.search(r'(?:Operator|Operador)\s+(\d+)', user_name, re.IGNORECASE)
if match:
op_num = match.group(1)
- user_name = t(user_id, "operators.operator_name", number=op_num)
+ user_name = translate(user_id, "operators.operator_name", number=op_num)
user_name = escape_markdown(user_name)
@@ -3120,7 +3120,7 @@ async def handle_operators_list(query, context: CallbackContext):
# Formatear información del operador
message_lines.append(f"\n**{user_name}**")
message_lines.append(f" 👤 ID: `{uid}`")
- message_lines.append(f" 📊 {t(user_id, 'operators.peers_created')}: {num_peers}")
+ message_lines.append(f" 📊 {ranslatet(user_id, 'operators.peers_created')}: {num_peers}")
# Información del último peer creado
if user_peers:
@@ -3132,21 +3132,21 @@ async def handle_operators_list(query, context: CallbackContext):
last_created = datetime.fromisoformat(last_peer.get('created_at', ''))
last_created_str = last_created.strftime("%d/%m/%Y %H:%M")
except:
- last_created_str = t(user_id, "errors.unknown_date")
+ last_created_str = translate(user_id, "errors.unknown_date")
peer_name = escape_markdown(last_peer.get('peer_name', ''))
config_name = escape_markdown(last_peer.get('config_name', ''))
- message_lines.append(f" {t(user_id, 'operators.last_peer', name=peer_name, date=last_created_str)}")
- message_lines.append(f" {t(user_id, 'operators.config', name=config_name)}")
+ message_lines.append(f" {translate(user_id, 'operators.last_peer', name=peer_name, date=last_created_str)}")
+ message_lines.append(f" {translate(user_id, 'operators.config', name=config_name)}")
# Mostrar endpoint si está disponible
endpoint = last_peer.get('endpoint')
if endpoint:
- message_lines.append(f" 🌐 {t(user_id, 'operators.endpoint_label')}: `{endpoint}`")
+ message_lines.append(f" 🌐 {ranslatet(user_id, 'operators.endpoint_label')}: `{endpoint}`")
# Estado de creación
if can_create:
- message_lines.append(f" {t(user_id, 'operators.can_create_now')}")
+ message_lines.append(f" {translate(user_id, 'operators.can_create_now')}")
else:
if next_allowed:
now = datetime.now()
@@ -3159,27 +3159,27 @@ async def handle_operators_list(query, context: CallbackContext):
else:
time_msg = f"{minutes}m"
- message_lines.append(f" {t(user_id, 'operators.can_create_in', time=time_msg)}")
+ message_lines.append(f" {translate(user_id, 'operators.can_create_in', time=time_msg)}")
else:
- message_lines.append(f" {t(user_id, 'operators.cannot_create')}")
+ message_lines.append(f" {translate(user_id, 'operators.cannot_create')}")
# Resumen general
- message_lines.append(f"\n{t(user_id, 'operators.summary')}")
- message_lines.append(t(user_id, "operators.total_operators", count=len(operator_users)))
- message_lines.append(t(user_id, "operators.total_peers", count=total_peers_created))
+ message_lines.append(f"\n{translate(user_id, 'operators.summary')}")
+ message_lines.append(translate(user_id, "operators.total_operators", count=len(operator_users)))
+ message_lines.append(translate(user_id, "operators.total_peers", count=total_peers_created))
# Usamos datetime ya importado al principio del archivo
- message_lines.append(t(user_id, "operators.last_update", date=datetime.now().strftime('%d/%m/%Y %H:%M:%S')))
+ message_lines.append(translate(user_id, "operators.last_update", date=datetime.now().strftime('%d/%m/%Y %H:%M:%S')))
message = "\n".join(message_lines)
# Crear teclado con acciones adicionales
keyboard = [
[
- InlineKeyboardButton(t(user_id, "menu.refresh"), callback_data="operators_list"),
- InlineKeyboardButton(t(user_id, "operators.detailed_button"), callback_data="operators_detailed")
+ InlineKeyboardButton(translate(user_id, "menu.refresh"), callback_data="operators_list"),
+ InlineKeyboardButton(translate(user_id, "operators.detailed_button"), callback_data="operators_detailed")
],
[
- InlineKeyboardButton(t(user_id, "menu.main"), callback_data="main_menu")
+ InlineKeyboardButton(translate(user_id, "menu.main"), callback_data="main_menu")
]
]
@@ -3209,7 +3209,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
del context.user_data[key]
await update.message.reply_text(
- t(user_id, "input.creation_cancel"),
+ translate(user_id, "input.creation_cancel"),
reply_markup=operator_main_menu() if is_operator(user_id) else main_menu(is_admin(user_id), is_operator(user_id))
)
return
@@ -3223,7 +3223,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
del context.user_data[key]
await update.message.reply_text(
- t(user_id, "input.schedule_cancel"),
+ translate(user_id, "input.schedule_cancel"),
reply_markup=operator_main_menu() if is_operator(user_id) else main_menu(is_admin(user_id), is_operator(user_id))
)
return
@@ -3235,12 +3235,12 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
if is_operator(user_id):
await update.message.reply_text(
- t(user_id, "input.operation_cancel_user"),
+ translate(user_id, "input.operation_cancel_user"),
reply_markup=operator_main_menu()
)
else:
await update.message.reply_text(
- t(user_id, "input.operation_cancel_admin"),
+ translate(user_id, "input.operation_cancel_admin"),
reply_markup=main_menu(is_admin(user_id), is_operator(user_id))
)
return
@@ -3269,7 +3269,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
else:
# Si no está en un flujo permitido, ignorar el mensaje
await update.message.reply_text(
- t(user_id, "errors.operators_only_menu"),
+ translate(user_id, "errors.operators_only_menu"),
reply_markup=operator_main_menu(),
parse_mode=None
)
@@ -3283,7 +3283,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
if not all([config_name, peer_index, job_type]):
await update.message.reply_text(
- t(user_id, "errors.schedule_config_error"),
+ translate(user_id, "errors.schedule_config_error"),
parse_mode=None
)
return
@@ -3294,7 +3294,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
peer_data = context.user_data.get(f'schedule_peer_{config_name}_{peer_index}')
if not peer_data:
await update.message.reply_text(
- t(user_id, "errors.peer_not_found"),
+ translate(user_id, "errors.peer_not_found"),
parse_mode=None
)
return
@@ -3307,13 +3307,13 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Validar que sea un número
if not value.isdigit():
await update.message.reply_text(
- t(user_id, "schedule.invalid_number"),
+ translate(user_id, "schedule.invalid_number"),
parse_mode=None
)
return
field = "total_data"
- field_display = t(user_id, "schedule.data_limit")
+ field_display = translate(user_id, "schedule.data_limit")
processed_value = value # El número tal cual
value_display = f"{value} GB"
@@ -3324,7 +3324,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
if not re.match(r'^\d{1,2}/\d{1,2}/\d{4}$', value):
await update.message.reply_text(
- t(user_id, "schedule.invalid_date_format"),
+ translate(user_id, "schedule.invalid_date_format"),
parse_mode=None
)
return
@@ -3336,13 +3336,13 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Convertir a formato YYYY-MM-DD HH:MM:SS
field = "date"
- field_display = t(user_id, "schedule.expiry_date")
+ field_display = translate(user_id, "schedule.expiry_date")
processed_value = f"{year:04d}-{month:02d}-{day:02d} 00:00:00"
value_display = value
except ValueError:
await update.message.reply_text(
- t(user_id, "schedule.invalid_date_value"),
+ translate(user_id, "schedule.invalid_date_value"),
parse_mode=None
)
return
@@ -3355,7 +3355,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
}
# Mensaje de progreso
- status_msg = await update.message.reply_text(t(user_id, "input.creating_job"))
+ status_msg = await update.message.reply_text(translate(user_id, "input.creating_job"))
# Enviar a la API
result = api_client.create_schedule_job(config_name, public_key, job_data)
@@ -3373,22 +3373,22 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
field_display_escaped = field_display
value_display_escaped = value_display
- message = t(user_id, "input.job_created_title") + "\n\n"
- message += f"*{t(user_id, 'schedule.peer_label')}* {peer_name_escaped}\n"
- message += f"*{t(user_id, 'schedule.config_label')}* {config_name_escaped}\n"
- message += f"*{t(user_id, 'schedule.action_label')}* RESTRICT\n"
- message += f"*{t(user_id, 'input.field')}* {field_display_escaped}\n"
- message += f"*{t(user_id, 'input.value')}* {value_display_escaped}\n\n"
- message += t(user_id, "input.job_created_msg")
+ message = translate(user_id, "input.job_created_title") + "\n\n"
+ message += f"*{translate(user_id, 'schedule.peer_label')}* {peer_name_escaped}\n"
+ message += f"*{translate(user_id, 'schedule.config_label')}* {config_name_escaped}\n"
+ message += f"*{translate(user_id, 'schedule.action_label')}* RESTRICT\n"
+ message += f"*{translate(user_id, 'input.field')}* {field_display_escaped}\n"
+ message += f"*{translate(user_id, 'input.value')}* {value_display_escaped}\n\n"
+ message += translate(user_id, "input.job_created_msg")
keyboard = [
[
- InlineKeyboardButton(t(user_id, "input.back_schedule"),
+ InlineKeyboardButton(translate(user_id, "input.back_schedule"),
callback_data=f"schedule_job_peer:{config_name}:{peer_index}"),
- InlineKeyboardButton(t(user_id, "input.add_another"),
+ InlineKeyboardButton(translate(user_id, "input.add_another"),
callback_data=f"schedule_job_peer:{config_name}:{peer_index}")
],
- [InlineKeyboardButton(t(user_id, "menu.main"), callback_data="main_menu")]
+ [InlineKeyboardButton(translate(user_id, "menu.main"), callback_data="main_menu")]
]
await status_msg.edit_text(
@@ -3407,7 +3407,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Validar el nombre
if not message_text or len(message_text) > 32:
await update.message.reply_text(
- t(user_id, "peer_creation.invalid_name_length"),
+ translate(user_id, "peer_creation.invalid_name_length"),
parse_mode=None
)
return
@@ -3430,10 +3430,10 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
context.user_data['waiting_for_operator_peer_name'] = False
await update.message.reply_text(
- t(user_id, "peer_creation.name_accepted", name=peer_name) + "\n\n" +
- t(user_id, "peer_creation.send_endpoint") + "\n\n" +
- t(user_id, "peer_creation.endpoint_format") + "\n" +
- t(user_id, "peer_creation.endpoint_examples") + "\n\n" +
+ translate(user_id, "peer_creation.name_accepted", name=peer_name) + "\n\n" +
+ translate(user_id, "peer_creation.send_endpoint") + "\n\n" +
+ translate(user_id, "peer_creation.endpoint_format") + "\n" +
+ translate(user_id, "peer_creation.endpoint_examples") + "\n\n" +
"Envía el endpoint ahora o escribe */cancel* para cancelar.",
parse_mode="Markdown"
)
@@ -3447,7 +3447,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
import re
if not re.match(r'^[a-zA-Z0-9\.\-]+:\d+$', endpoint):
await update.message.reply_text(
- t(user_id, "peer_creation.invalid_endpoint_format"),
+ translate(user_id, "peer_creation.invalid_endpoint_format"),
parse_mode=None
)
return
@@ -3460,7 +3460,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
raise ValueError
except:
await update.message.reply_text(
- t(user_id, "peer_creation.invalid_port"),
+ translate(user_id, "peer_creation.invalid_port"),
parse_mode=None
)
return
@@ -3493,7 +3493,7 @@ async def text_message_handler(update: Update, context: ContextTypes.DEFAULT_TYP
# Validar el nombre
if not message_text or len(message_text) > 32:
await update.message.reply_text(
- t(user_id, "peer_creation.invalid_name_length"),
+ translate(user_id, "peer_creation.invalid_name_length"),
parse_mode=None
)
return
@@ -3551,27 +3551,27 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
hours = int(remaining.total_seconds() // 3600)
minutes = int((remaining.total_seconds() % 3600) // 60)
- time_msg = t(user_id, "rate_limit.hours_minutes", hours=hours, minutes=minutes) if hours > 0 else t(user_id, "rate_limit.minutes", minutes=minutes)
+ time_msg = translate(user_id, "rate_limit.hours_minutes", hours=hours, minutes=minutes) if hours > 0 else translate(user_id, "rate_limit.minutes", minutes=minutes)
await update.message.reply_text(
- f"{t(user_id, 'rate_limit.title')}\n\n"
- f"{t(user_id, error_key, time=time_msg)}\n\n"
- f"{t(user_id, 'rate_limit.remaining', time=time_msg)}\n\n"
- f"{t(user_id, 'rate_limit.can_create_later')}",
+ f"{translate(user_id, 'rate_limit.title')}\n\n"
+ f"{translate(user_id, error_key, time=time_msg)}\n\n"
+ f"{translate(user_id, 'rate_limit.remaining', time=time_msg)}\n\n"
+ f"{translate(user_id, 'rate_limit.can_create_later')}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
return
else:
await update.message.reply_text(
- f"{t(user_id, 'rate_limit.no_more_peers')}\n\n"
- f"{t(user_id, error_key or 'errors.access_denied')}",
+ f"{translate(user_id, 'rate_limit.no_more_peers')}\n\n"
+ f"{translate(user_id, error_key or 'errors.access_denied')}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
return
- await update.message.reply_text(t(user_id, "peer_creation.generating", name=peer_name, config=config_name))
+ await update.message.reply_text(translate(user_id, "peer_creation.generating", name=peer_name, config=config_name))
# 1. Generar claves WireGuard y pre-shared key
private_key, public_key = generate_wireguard_keys()
@@ -3601,7 +3601,7 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
config_result = api_client.get_configuration_detail(config_name)
if not config_result.get("status"):
await update.message.reply_text(
- t(user_id, "errors.api_error", message=config_result.get('message', 'Error desconocido')),
+ translate(user_id, "errors.api_error", message=config_result.get('message', 'Error desconocido')),
parse_mode=None
)
return
@@ -3657,7 +3657,7 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
}
# 6. Enviar a la API
- await update.message.reply_text(t(user_id, "peer_creation.sending_data"))
+ await update.message.reply_text(translate(user_id, "peer_creation.sending_data"))
result = api_client.add_peer(config_name, peer_data)
@@ -3697,26 +3697,26 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
}
# Enviar jobs a la API
- await update.message.reply_text(t(user_id, "peer_creation.limits_configured"))
+ await update.message.reply_text(translate(user_id, "peer_creation.limits_configured"))
result_gb = api_client.create_schedule_job(config_name, public_key, job_data_gb)
result_date = api_client.create_schedule_job(config_name, public_key, job_data_date)
jobs_status = ""
if result_gb.get("status") and result_date.get("status"):
- jobs_status = t(user_id, "peer_creation.creation_success_limit")
+ jobs_status = translate(user_id, "peer_creation.creation_success_limit")
else:
- jobs_status = t(user_id, "peer_creation.creation_warn_limit")
+ jobs_status = translate(user_id, "peer_creation.creation_warn_limit")
if not result_gb.get("status"):
- jobs_status += t(user_id, "peer_creation.limit_data_error", message=result_gb.get('message'))
+ jobs_status += translate(user_id, "peer_creation.limit_data_error", message=result_gb.get('message'))
if not result_date.get("status"):
- jobs_status += t(user_id, "peer_creation.limit_time_error", message=result_date.get('message'))
+ jobs_status += translate(user_id, "peer_creation.limit_time_error", message=result_date.get('message'))
jobs_status += "\n"
# ================= MENSAJE FINAL SEGÚN ROL ================= #
if is_operator(user_id):
# Para operadores: mostrar información específica con límites
- message = t(user_id, "peer_creation.operator_success_msg",
+ message = translate(user_id, "peer_creation.operator_success_msg",
name=peer_name, config=config_name, ip=allowed_ip,
endpoint=endpoint, limits_status=jobs_status,
public_key=public_key[:30], private_key=private_key[:30],
@@ -3724,12 +3724,12 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
# Para operadores, mostrar solo botón de descarga
keyboard = [
- [InlineKeyboardButton(t(user_id, "actions.download"), callback_data=f"download_config:{peer_hash}")],
- [InlineKeyboardButton(t(user_id, "menu.back"), callback_data="operator_main_menu")]
+ [InlineKeyboardButton(translate(user_id, "actions.download"), callback_data=f"download_config:{peer_hash}")],
+ [InlineKeyboardButton(translate(user_id, "menu.back"), callback_data="operator_main_menu")]
]
else: # Admin
- message = t(user_id, "peer_creation.admin_success_msg",
+ message = translate(user_id, "peer_creation.admin_success_msg",
name=peer_name, config=config_name, ip=allowed_ip,
endpoint=endpoint, public_key=public_key,
private_key=private_key, preshared_key=preshared_key)
@@ -3737,8 +3737,8 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
# Para admins, mostrar botones normales
keyboard = [
[
- InlineKeyboardButton(t(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}"),
- InlineKeyboardButton(t(user_id, "actions.download"), callback_data=f"download_config:{peer_hash}")
+ InlineKeyboardButton(translate(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}"),
+ InlineKeyboardButton(translate(user_id, "actions.download"), callback_data=f"download_config:{peer_hash}")
]
]
@@ -3753,10 +3753,10 @@ async def generate_peer_automatically(update: Update, context: ContextTypes.DEFA
error_msg = result.get('message', 'Error desconocido')
if is_operator(user_id):
- keyboard = [[InlineKeyboardButton(t(user_id, "menu.back"), callback_data="operator_main_menu")]]
- text = t(user_id, "peer_creation.creation_error_operator", message=error_msg)
+ keyboard = [[InlineKeyboardButton(translate(user_id, "menu.back"), callback_data="operator_main_menu")]]
+ text = translate(user_id, "peer_creation.creation_error_operator", message=error_msg)
else:
- keyboard = [[InlineKeyboardButton(t(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}")]]
+ keyboard = [[InlineKeyboardButton(translate(user_id, "actions.back_to_config"), callback_data=f"cfg:{config_name}")]]
text = f"❌ *Error al agregar peer*\n\n*Error:* {error_msg}\n\nIntenta nuevamente o contacta al administrador."
reply_markup = InlineKeyboardMarkup(keyboard)
@@ -3791,13 +3791,13 @@ async def handle_operator_create_peer(query, context: CallbackContext):
hours = int(remaining.total_seconds() // 3600)
minutes = int((remaining.total_seconds() % 3600) // 60)
- time_msg = t(user_id, "rate_limit.hours_minutes", hours=hours, minutes=minutes) if hours > 0 else t(user_id, "rate_limit.minutes", minutes=minutes)
+ time_msg = translate(user_id, "rate_limit.hours_minutes", hours=hours, minutes=minutes) if hours > 0 else translate(user_id, "rate_limit.minutes", minutes=minutes)
# Use t() for localized title and message
- message_text = f"{t(user_id, 'rate_limit.title')}\n\n" \
- f"{t(user_id, error_msg, time=time_msg)}\n\n" \
- f"{t(user_id, 'rate_limit.remaining', time=time_msg)}\n\n" \
- f"{t(user_id, 'rate_limit.can_create_later')} ({timestamp})"
+ message_text = f"{translate(user_id, 'rate_limit.title')}\n\n" \
+ f"{translate(user_id, error_msg, time=time_msg)}\n\n" \
+ f"{translate(user_id, 'rate_limit.remaining', time=time_msg)}\n\n" \
+ f"{translate(user_id, 'rate_limit.can_create_later')} ({timestamp})"
await query.edit_message_text(
message_text,
@@ -3807,8 +3807,8 @@ async def handle_operator_create_peer(query, context: CallbackContext):
return
else:
await query.edit_message_text(
- f"{t(user_id, 'rate_limit.no_more_peers')} ({timestamp})\n\n"
- f"{t(user_id, error_msg or 'errors.access_denied')}",
+ f"{translate(user_id, 'rate_limit.no_more_peers')} ({timestamp})\n\n"
+ f"{translate(user_id, error_msg or 'errors.access_denied')}",
reply_markup=operator_main_menu(user_id),
parse_mode="Markdown"
)
@@ -3818,7 +3818,7 @@ async def handle_operator_create_peer(query, context: CallbackContext):
result = api_client.get_configurations()
if not result.get("status"):
await query.edit_message_text(
- t(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')) + f" ({timestamp})",
+ translate(user_id, "errors.api_error", message=result.get('message', 'Error desconocido')) + f" ({timestamp})",
reply_markup=operator_main_menu(user_id)
)
return
@@ -3826,7 +3826,7 @@ async def handle_operator_create_peer(query, context: CallbackContext):
configs = result.get("data", [])
if not configs:
await query.edit_message_text(
- t(user_id, "errors.no_configs") + f" ({timestamp})",
+ translate(user_id, "errors.no_configs") + f" ({timestamp})",
reply_markup=operator_main_menu(user_id)
)
return
@@ -3839,10 +3839,10 @@ async def handle_operator_create_peer(query, context: CallbackContext):
context.user_data['config_name_for_operator_peer'] = config_name
await query.edit_message_text(
- t(user_id, "peer_creation.add_peer", config=config_name) + f" ({timestamp})\n\n" +
- t(user_id, "peer_creation.send_name"),
+ translate(user_id, "peer_creation.add_peer", config=config_name) + f" ({timestamp})\n\n" +
+ translate(user_id, "peer_creation.send_name"),
reply_markup=InlineKeyboardMarkup([
- [InlineKeyboardButton(t(user_id, "actions.cancel"), callback_data="operator_main_menu")]
+ [InlineKeyboardButton(translate(user_id, "actions.cancel"), callback_data="operator_main_menu")]
]),
parse_mode="Markdown"
)
@@ -3853,13 +3853,13 @@ async def handle_operators_detailed(query, context: CallbackContext):
if not is_admin(user_id):
await query.edit_message_text(
- t(user_id, "errors.access_denied"),
+ translate(user_id, "errors.access_denied"),
reply_markup=main_menu(is_admin(user_id), is_operator(user_id), user_id),
parse_mode="Markdown"
)
return
- await query.edit_message_text(t(user_id, "operators.getting_detailed"))
+ await query.edit_message_text(translate(user_id, "operators.getting_detailed"))
# Obtener información de todos los operadores
operator_users = {uid: info for uid, info in ALLOWED_USERS.items()
@@ -3867,13 +3867,13 @@ async def handle_operators_detailed(query, context: CallbackContext):
if not operator_users:
await query.edit_message_text(
- t(user_id, "errors.no_operators"),
+ translate(user_id, "errors.no_operators"),
reply_markup=refresh_button("operators_list", user_id),
parse_mode="Markdown"
)
return
- message_lines = [t(user_id, "operators.detailed") + "\n"]
+ message_lines = [translate(user_id, "operators.detailed") + "\n"]
all_peers = []
@@ -3885,14 +3885,14 @@ async def handle_operators_detailed(query, context: CallbackContext):
match = re.search(r'(?:Operator|Operador)\s+(\d+)', user_name, re.IGNORECASE)
if match:
op_num = match.group(1)
- user_name = t(user_id, "operators.operator_name", number=op_num)
+ user_name = translate(user_id, "operators.operator_name", number=op_num)
user_name = escape_markdown(user_name)
user_peers = operators_db.get_user_peers(uid)
message_lines.append(f"\n**{user_name}** (ID: `{uid}`) ")
- message_lines.append(t(user_id, "operators.total_peers_count", count=len(user_peers)))
+ message_lines.append(translate(user_id, "operators.total_peers_count", count=len(user_peers)))
if user_peers:
# Ordenar por fecha descendente
@@ -3910,23 +3910,23 @@ async def handle_operators_detailed(query, context: CallbackContext):
dt = datetime.fromisoformat(created_at)
created_str = dt.strftime("%d/%m/%Y %H:%M")
except:
- created_str = t(user_id, "errors.unknown_date")
+ created_str = translate(user_id, "errors.unknown_date")
message_lines.append(f" {i}. **{peer_name}**")
- message_lines.append(f" {t(user_id, 'operators.created_at', date=created_str)}")
- message_lines.append(f" {t(user_id, 'operators.config', name=config_name)}")
- message_lines.append(f" {t(user_id, 'operators.endpoint', endpoint=endpoint)}")
- message_lines.append(f" {t(user_id, 'operators.key', key=public_key_short)}")
+ message_lines.append(f" {translate(user_id, 'operators.created_at', date=created_str)}")
+ message_lines.append(f" {translate(user_id, 'operators.config', name=config_name)}")
+ message_lines.append(f" {translate(user_id, 'operators.endpoint', endpoint=endpoint)}")
+ message_lines.append(f" {translate(user_id, 'operators.key', key=public_key_short)}")
if len(user_peers) > 5:
- message_lines.append(f" {t(user_id, 'operators.and_more', count=len(user_peers) - 5)}")
+ message_lines.append(f" {translate(user_id, 'operators.and_more', count=len(user_peers) - 5)}")
all_peers.extend(user_peers)
# Resumen
- message_lines.append(f"\n{t(user_id, 'operators.summary')}")
- message_lines.append(t(user_id, "operators.total_operators", count=len(operator_users)))
- message_lines.append(t(user_id, "operators.total_peers", count=len(all_peers)))
+ message_lines.append(f"\n{translate(user_id, 'operators.summary')}")
+ message_lines.append(translate(user_id, "operators.total_operators", count=len(operator_users)))
+ message_lines.append(translate(user_id, "operators.total_peers", count=len(all_peers)))
# Encontrar el peer más reciente
if all_peers:
@@ -3947,10 +3947,10 @@ async def handle_operators_detailed(query, context: CallbackContext):
latest_date = datetime.fromisoformat(latest_peer.get('created_at', ''))
latest_date_str = latest_date.strftime("%d/%m/%Y %H:%M")
except:
- latest_date_str = t(user_id, "errors.unknown_date")
+ latest_date_str = translate(user_id, "errors.unknown_date")
latest_peer_name = escape_markdown(latest_peer_name)
- message_lines.append(t(user_id, "operators.latest_created_by", peer=latest_peer_name, operator=latest_operator_id, date=latest_date_str))
+ message_lines.append(translate(user_id, "operators.latest_created_by", peer=latest_peer_name, operator=latest_operator_id, date=latest_date_str))
message = "\n".join(message_lines)
@@ -3973,20 +3973,20 @@ async def handle_operators_detailed(query, context: CallbackContext):
# Agregar teclado al último mensaje
keyboard = [
- [InlineKeyboardButton(t(user_id, "menu.back"), callback_data="operators_list")],
- [InlineKeyboardButton(t(user_id, "menu.main"), callback_data="main_menu")]
+ [InlineKeyboardButton(translate(user_id, "menu.back"), callback_data="operators_list")],
+ [InlineKeyboardButton(translate(user_id, "menu.main"), callback_data="main_menu")]
]
await query.message.reply_text(
- t(user_id, "operators.end_report"),
+ translate(user_id, "operators.end_report"),
reply_markup=InlineKeyboardMarkup(keyboard),
parse_mode="Markdown"
)
else:
# Enviar mensaje completo
keyboard = [
- [InlineKeyboardButton(t(user_id, "menu.back"), callback_data="operators_list")],
- [InlineKeyboardButton(t(user_id, "menu.main"), callback_data="main_menu")]
+ [InlineKeyboardButton(translate(user_id, "menu.back"), callback_data="operators_list")],
+ [InlineKeyboardButton(translate(user_id, "menu.main"), callback_data="main_menu")]
]
await query.edit_message_text(
diff --git a/src/i18n.py b/src/modules/i18n.py
similarity index 99%
rename from src/i18n.py
rename to src/modules/i18n.py
index 4d27827..9f09850 100644
--- a/src/i18n.py
+++ b/src/modules/i18n.py
@@ -5,7 +5,7 @@
import json
import os
from typing import Dict, Any, Optional
-from config import DATA_DIR
+from .config import DATA_DIR
class I18n:
"""Gestor de traducciones multiidioma completo"""
diff --git a/src/keyboards.py b/src/modules/keyboards.py
similarity index 99%
rename from src/keyboards.py
rename to src/modules/keyboards.py
index bb6de97..6887141 100644
--- a/src/keyboards.py
+++ b/src/modules/keyboards.py
@@ -6,8 +6,8 @@
from typing import List, Dict, Any, Optional
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
-from config import ITEMS_PER_PAGE
-from utils import t
+from .config import ITEMS_PER_PAGE
+from .utils import translate
def safe_callback_data(text: str) -> str:
"""Codifica texto para hacerlo seguro para callback_data"""
diff --git a/src/operators.py b/src/modules/operators.py
similarity index 98%
rename from src/operators.py
rename to src/modules/operators.py
index b89d44d..3ed4dae 100644
--- a/src/operators.py
+++ b/src/modules/operators.py
@@ -8,7 +8,7 @@
from typing import Dict, List, Any, Optional, Tuple
import logging
-from config import OPERATORS_DB, OPERATOR_LIMIT_HOURS, OPERATOR_DATA_LIMIT_GB, OPERATOR_TIME_LIMIT_HOURS
+from .config import OPERATORS_DB, OPERATOR_LIMIT_HOURS, OPERATOR_DATA_LIMIT_GB, OPERATOR_TIME_LIMIT_HOURS
logger = logging.getLogger(__name__)
diff --git a/src/regenerate_translations.py b/src/modules/regenerate_translations.py
similarity index 100%
rename from src/regenerate_translations.py
rename to src/modules/regenerate_translations.py
diff --git a/src/setup_logging.py b/src/modules/setup_logging.py
similarity index 96%
rename from src/setup_logging.py
rename to src/modules/setup_logging.py
index 8f37d8b..dfff735 100644
--- a/src/setup_logging.py
+++ b/src/modules/setup_logging.py
@@ -5,7 +5,7 @@
import logging
import logging.handlers
import os
-from config import LOG_FILE, LOG_LEVEL, LOG_MAX_SIZE, LOG_BACKUP_COUNT
+from .config import LOG_FILE, LOG_LEVEL, LOG_MAX_SIZE, LOG_BACKUP_COUNT
def setup_logging():
"""Configura el sistema de logging"""
diff --git a/src/update_translations.py b/src/modules/update_translations.py
similarity index 100%
rename from src/update_translations.py
rename to src/modules/update_translations.py
diff --git a/src/utils.py b/src/modules/utils.py
similarity index 79%
rename from src/utils.py
rename to src/modules/utils.py
index 8cd6525..8c50099 100644
--- a/src/utils.py
+++ b/src/modules/utils.py
@@ -12,8 +12,8 @@
# Eliminado import circular directo
# from i18n import i18n
-from config import ALLOWED_USERS, MAX_PEERS_DISPLAY, ROLE_ADMIN, ROLE_OPERATOR
-from operators import operators_db
+from .config import ALLOWED_USERS, MAX_PEERS_DISPLAY, ROLE_ADMIN, ROLE_OPERATOR
+from .operators import operators_db
logger = logging.getLogger(__name__)
@@ -90,16 +90,16 @@ def format_size(bytes_size: int) -> str:
def format_handshake_time(seconds: Optional[int], user_id: int) -> str:
"""Formatea el tiempo desde último handshake"""
if not seconds:
- return t(user_id, "time.never")
+ return translate(user_id, "time.never")
if seconds < 60:
- return t(user_id, "time.seconds", count=seconds)
+ return translate(user_id, "time.seconds", count=seconds)
elif seconds < 3600:
- return t(user_id, "time.minutes", count=seconds // 60)
+ return translate(user_id, "time.minutes", count=seconds // 60)
elif seconds < 86400:
- return t(user_id, "time.hours", count=seconds // 3600)
+ return translate(user_id, "time.hours", count=seconds // 3600)
else:
- return t(user_id, "time.days", count=seconds // 86400)
+ return translate(user_id, "time.days", count=seconds // 86400)
def format_bytes_human(bytes_size: float) -> str:
"""Formatea bytes a formato humano legible (recibe MB)"""
@@ -122,39 +122,39 @@ def format_bytes_human(bytes_size: float) -> str:
def format_time_ago(seconds: int, user_id: int) -> str:
"""Formatea segundos a 'hace X tiempo'"""
if seconds <= 0:
- return t(user_id, "time.never")
+ return translate(user_id, "time.never")
if seconds < 60:
- return t(user_id, "time.ago_seconds", count=int(seconds))
+ return translate(user_id, "time.ago_seconds", count=int(seconds))
elif seconds < 3600:
minutes = seconds // 60
- return t(user_id, "time.ago_minutes", count=int(minutes))
+ return translate(user_id, "time.ago_minutes", count=int(minutes))
elif seconds < 86400:
hours = seconds // 3600
- return t(user_id, "time.ago_hours", count=int(hours))
+ return translate(user_id, "time.ago_hours", count=int(hours))
else:
days = seconds // 86400
- return t(user_id, "time.ago_days", count=int(days))
+ return translate(user_id, "time.ago_days", count=int(days))
def format_time_remaining(seconds: int, user_id: int) -> str:
"""Formatea segundos restantes en formato legible"""
if seconds <= 0:
- return t(user_id, "time.now")
+ return translate(user_id, "time.now")
hours = seconds // 3600
minutes = (seconds % 3600) // 60
if hours > 0:
- return t(user_id, "time.hm_format", hours=hours, minutes=minutes)
+ return translate(user_id, "time.hm_format", hours=hours, minutes=minutes)
else:
- return t(user_id, "time.m_format", minutes=minutes)
+ return translate(user_id, "time.m_format", minutes=minutes)
def format_peer_info(peer: Dict, user_id: int) -> str:
"""Formatea la información de un peer"""
lines = []
# Información básica
- name = peer.get('name', t(user_id, "common.no_name"))
+ name = peer.get('name', translate(user_id, "common.no_name"))
public_key = peer.get('id', '') # En esta API, la clave pública está en 'id'
lines.append(f"👤 **{name}**")
@@ -163,22 +163,22 @@ def format_peer_info(peer: Dict, user_id: int) -> str:
latest_handshake = peer.get('latest_handshake_seconds', 0)
if status == 'running' and latest_handshake > 0:
- lines.append(f" 🔗 {t(user_id, 'peers.connected').split(':')[0]}: {format_handshake_time(latest_handshake, user_id)}")
- status_text = t(user_id, "peers.status_connected")
+ lines.append(f" 🔗 {ranslatet(user_id, 'peers.connected').split(':')[0]}: {format_handshake_time(latest_handshake, user_id)}")
+ status_text = translate(user_id, "peers.status_connected")
else:
- status_text = t(user_id, "peers.status_disconnected")
- lines.append(f" 📊 {t(user_id, 'system.status')}: {status_text}")
+ status_text = translate(user_id, "peers.status_disconnected")
+ lines.append(f" 📊 {ranslatet(user_id, 'system.status')}: {status_text}")
# Transferencia de datos (en MB)
total_receive = peer.get('total_receive', 0) # MB
total_sent = peer.get('total_sent', 0) # MB
- lines.append(f" ⬇️ {t(user_id, 'traffic.received', value=format_bytes_human(total_receive))}")
- lines.append(f" ⬆️ {t(user_id, 'traffic.sent', value=format_bytes_human(total_sent))}")
+ lines.append(f" ⬇️ {translate(user_id, 'traffic.received', value=format_bytes_human(total_receive))}")
+ lines.append(f" ⬆️ {translate(user_id, 'traffic.sent', value=format_bytes_human(total_sent))}")
# IPs
- allowed_ip = peer.get('allowed_ip', t(user_id, "common.na"))
- endpoint = peer.get('endpoint', t(user_id, "common.na"))
+ allowed_ip = peer.get('allowed_ip', translate(user_id, "common.na"))
+ endpoint = peer.get('endpoint', translate(user_id, "common.na"))
lines.append(f" 📍 IP: `{allowed_ip}`")
lines.append(f" 🌐 Endpoint: `{endpoint}`")
@@ -190,51 +190,51 @@ def format_peer_info(peer: Dict, user_id: int) -> str:
def format_system_status(status_data: Dict, user_id: int) -> str:
"""Formatea el estado del sistema"""
- lines = [t(user_id, "system.title") + "\n"]
+ lines = [translate(user_id, "system.title") + "\n"]
# CPU
cpu = status_data.get('CPU', {})
cpu_percent = float(cpu.get('cpu_percent', 0))
- lines.append(t(user_id, "system.cpu", percent=cpu_percent))
+ lines.append(translate(user_id, "system.cpu", percent=cpu_percent))
# Memoria
memory = status_data.get('Memory', {}).get('VirtualMemory', {})
mem_percent = float(memory.get('percent', 0))
mem_total = format_size(memory.get('total', 0))
mem_available = format_size(memory.get('available', 0))
- lines.append(t(user_id, "system.memory", percent=f"{float(mem_percent):.1f}"))
- lines.append(t(user_id, "system.memory_details", total=mem_total, available=mem_available))
+ lines.append(translate(user_id, "system.memory", percent=f"{float(mem_percent):.1f}"))
+ lines.append(translate(user_id, "system.memory_details", total=mem_total, available=mem_available))
# Swap
swap = status_data.get('Memory', {}).get('SwapMemory', {})
if swap.get('total', 0) > 0:
swap_percent = float(swap.get('percent', 0))
swap_total = format_size(swap.get('total', 0))
- lines.append(t(user_id, "system.swap", percent=swap_percent, total=swap_total))
+ lines.append(translate(user_id, "system.swap", percent=swap_percent, total=swap_total))
# Discos (solo los principales)
disks = status_data.get('Disks', [])
if disks:
- lines.append(t(user_id, "system.disks"))
+ lines.append(translate(user_id, "system.disks"))
for disk in disks[:3]: # Mostrar solo 3 discos
mount = disk.get('mountPoint', 'N/A')
percent = disk.get('percent', 0)
free = format_size(disk.get('free', 0))
- lines.append(t(user_id, "system.disk_details", mount=mount, percent=percent, free=free))
+ lines.append(translate(user_id, "system.disk_details", mount=mount, percent=percent, free=free))
# Interfaces de red - MODIFICADO PARA MOSTRAR TODAS LAS INTERFACES
interfaces = status_data.get('NetworkInterfaces', {})
if interfaces:
- lines.append(t(user_id, "system.interfaces"))
+ lines.append(translate(user_id, "system.interfaces"))
# Ordenar interfaces para mostrar primero las importantes
interface_order = ['lo', 'ens3', 'eth0', 'eth1'] # Interfaces principales
- wg_interfaces = []
+ wgd_interfaces = []
other_interfaces = []
for iface_name, iface_data in interfaces.items():
if iface_name.startswith('wg'):
- wg_interfaces.append((iface_name, iface_data))
+ wgd_interfaces.append((iface_name, iface_data))
elif iface_name not in interface_order:
other_interfaces.append((iface_name, iface_data))
@@ -244,35 +244,35 @@ def format_system_status(status_data: Dict, user_id: int) -> str:
iface_data = interfaces[iface_name]
sent = format_size(iface_data.get('bytes_sent', 0))
recv = format_size(iface_data.get('bytes_recv', 0))
- lines.append(t(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
+ lines.append(translate(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
# Mostrar todas las interfaces WireGuard
- if wg_interfaces:
- lines.append(t(user_id, "system.wg_interfaces"))
- for iface_name, iface_data in sorted(wg_interfaces):
+ if wgd_interfaces:
+ lines.append(translate(user_id, "system.wgd_interfaces"))
+ for iface_name, iface_data in sorted(wgd_interfaces):
sent = format_size(iface_data.get('bytes_sent', 0))
recv = format_size(iface_data.get('bytes_recv', 0))
- lines.append(t(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
+ lines.append(translate(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
# Mostrar otras interfaces (limitado a 5 para no hacer muy largo el mensaje)
if other_interfaces:
- lines.append(t(user_id, "system.other_interfaces"))
+ lines.append(translate(user_id, "system.other_interfaces"))
for iface_name, iface_data in sorted(other_interfaces)[:5]: # Máximo 5
sent = format_size(iface_data.get('bytes_sent', 0))
recv = format_size(iface_data.get('bytes_recv', 0))
- lines.append(t(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
+ lines.append(translate(user_id, "system.interface_details", name=iface_name, sent=sent, recv=recv))
if len(other_interfaces) > 5:
- lines.append(t(user_id, "operators.and_more", count=len(other_interfaces) - 5))
+ lines.append(translate(user_id, "operators.and_more", count=len(other_interfaces) - 5))
return "\n".join(lines)
def format_config_summary(configs: List[Dict], user_id: int) -> str:
"""Formatea un resumen de todas las configuraciones"""
if not configs:
- return t(user_id, "summary.no_configs")
+ return translate(user_id, "summary.no_configs")
- lines = [t(user_id, "summary.title") + "\n"]
+ lines = [translate(user_id, "summary.title") + "\n"]
total_peers = 0
total_connected = 0
@@ -287,10 +287,10 @@ def format_config_summary(configs: List[Dict], user_id: int) -> str:
total_connected += connected
status_emoji = "✅" if connected > 0 else "⚠️"
- lines.append(t(user_id, "summary.config_line", status=status_emoji, name=name, port=listen_port))
- lines.append(t(user_id, "summary.peers_line", connected=connected, total=peers))
+ lines.append(translate(user_id, "summary.config_line", status=status_emoji, name=name, port=listen_port))
+ lines.append(translate(user_id, "summary.peers_line", connected=connected, total=peers))
- lines.append(f"\n" + t(user_id, "summary.totals", connected=total_connected, total=total_peers))
+ lines.append(f"\n" + translate(user_id, "summary.totals", connected=total_connected, total=total_peers))
return "\n".join(lines)
@@ -426,7 +426,7 @@ def safe_message(text: str, parse_mode: str = "HTML", **kwargs) -> dict:
return {"text": formatted_text, "parse_mode": parse_mode}
-def t(user_id: int, key_path: str, **kwargs) -> str:
+def translate(user_id: int, key_path: str, **kwargs) -> str:
"""Función helper para obtener traducciones"""
# Importar aquí para evitar dependencias circulares
from i18n import i18n
diff --git a/src/wg_api.py b/src/modules/wgd_api.py
similarity index 98%
rename from src/wg_api.py
rename to src/modules/wgd_api.py
index 2a9efa9..acea0b5 100644
--- a/src/wg_api.py
+++ b/src/modules/wgd_api.py
@@ -12,7 +12,7 @@
import psutil
import platform
-from config import WG_API_BASE_URL, WG_API_KEY, API_TIMEOUT, WG_API_PREFIX
+from .config import WGD_API_BASE_URL, WGD_API_KEY, API_TIMEOUT, WGD_API_PREFIX
logger = logging.getLogger(__name__)
@@ -24,12 +24,12 @@ class WGApiClient:
"""Cliente para interactuar con la API de WGDashboard"""
def __init__(self):
- self.base_url = WG_API_BASE_URL.rstrip('/')
- if WG_API_PREFIX:
- self.base_url = f"{self.base_url.rstrip('/')}/{WG_API_PREFIX.lstrip('/')}"
+ self.base_url = WGD_API_BASE_URL.rstrip('/')
+ if WGD_API_PREFIX:
+ self.base_url = f"{self.base_url.rstrip('/')}/{WGD_API_PREFIX.lstrip('/')}"
self.headers = {
- "wg-dashboard-apikey": WG_API_KEY,
+ "wg-dashboard-apikey": WGD_API_KEY,
"Content-Type": "application/json",
"User-Agent": "WGDashboard-Bot/1.0"
}
diff --git a/src/requirements.txt b/src/requirements.txt
index a292fe4..6e53113 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,4 +1,5 @@
-python-telegram-bot>=20.0
-requests>=2.28.0
-psutil>=5.9.0
-cryptography>=41.0.0
+cryptography==46.0.4
+dotenv==0.9.9
+psutil==7.2.2
+python-telegram-bot==22.6
+requests==2.32.5
\ No newline at end of file