Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 555894b26c | |||
| bb636c003c | |||
| 40c7b4aebd | |||
| 028bd4888b | |||
| 27d64e361a | |||
| 6fc7dd49dd | |||
| b835ba1d65 | |||
| 811e985087 | |||
| c16733b93d | |||
| 23ab3c5198 | |||
| 9b8f121e69 | |||
| 86b1b95fb9 | |||
| 9418681a00 | |||
| 7ebd937e2c | |||
| 25c404f999 |
+3
-1
@@ -21,8 +21,10 @@ POSTGRES_USER=remnawave_user
|
||||
POSTGRES_PASSWORD=secure_password_123
|
||||
|
||||
# ===== REMNAWAVE API =====
|
||||
REMNAWAVE_API_URL=
|
||||
REMNAWAVE_API_URL=https://panel.example.com
|
||||
REMNAWAVE_API_KEY=
|
||||
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD
|
||||
REMNAWAVE_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# ===== ТРИАЛ ПОДПИСКА =====
|
||||
TRIAL_DURATION_DAYS=3
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
- 📈 **Масштабируемость**
|
||||
- 🔧 **Мониторинг** - Prometheus, Grafana, health checks
|
||||
- 🔧 **Режим технических работ** - Ручное включение + Мониторинг системы, который в случае падении панели Remnawave переведет бота в режим технических работ и обратно - отключит его, если панель поднимется.
|
||||
- Интеграция с системой защиты панели Remnawave через куки-аутентификацию, которая используется в [remnawave-reverse-proxy](https://github.com/eGamesAPI/remnawave-reverse-proxy) для скрытия панели от несанкционированного доступа.
|
||||
|
||||
---
|
||||
|
||||
@@ -113,8 +114,10 @@ POSTGRES_USER=remnawave_user
|
||||
POSTGRES_PASSWORD=secure_password_123
|
||||
|
||||
# ===== REMNAWAVE API =====
|
||||
REMNAWAVE_API_URL=
|
||||
REMNAWAVE_API_URL=https://panel.example.com
|
||||
REMNAWAVE_API_KEY=
|
||||
# Для панелей установленных скриптом eGames прописывать ключ в формате XXXXXXX:DDDDDDDD - https://panel.example.com/auth/login?XXXXXXX=DDDDDDDD
|
||||
REMNAWAVE_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# ===== ТРИАЛ ПОДПИСКА =====
|
||||
TRIAL_DURATION_DAYS=3
|
||||
@@ -431,16 +434,16 @@ your-domain.com {
|
||||
|
||||
```bash
|
||||
# Быстрый старт
|
||||
docker compose up -d --build
|
||||
docker compose up -d
|
||||
|
||||
# Статус сервисов
|
||||
docker compose ps
|
||||
|
||||
# Логи
|
||||
docker compose logs -f bot
|
||||
docker compose logs
|
||||
|
||||
# Перезапуск
|
||||
docker compose restart bot
|
||||
docker compose restart
|
||||
|
||||
# Остановка
|
||||
docker compose down
|
||||
@@ -462,26 +465,30 @@ docker compose down -v --remove-orphans
|
||||
🛒 **Умная покупка подписок**
|
||||
- 📅 Выбор периода (14-360 дней) (С возможностью настройки выводимых периодов 14/30/60/90/180/360 дней)
|
||||
- 📊 Настройка трафика (5GB - безлимит) (Можно откоючить данный шаг, всем подпискам выдавать единый)
|
||||
- 🌍 Выбор стран через сквады
|
||||
- 📱 Количество устройств (1-10)
|
||||
- 🌍 Выбор стран через сквады (Доступно при наличие двух стран(сквадов) в продаже)
|
||||
- 📱 Количество устройств (1-5)
|
||||
|
||||
🧪 **Тестовая подписка**
|
||||
- Получение настраиваемой разовой тестовой подписки
|
||||
- Уведомления об истечении с предложением о переходе н платную версию
|
||||
|
||||
💰 **Удобные платежи**
|
||||
- ⭐ Telegram Stars
|
||||
- 💳 Tribute
|
||||
- 💳 YooKassa
|
||||
- 🎁 Реферальные бонусы
|
||||
- Детальная история транзакций
|
||||
|
||||
📱 **Управление подписками**
|
||||
- 📈 Просмотр статистики использования
|
||||
- 🔄 Автопродление с баланса
|
||||
- 🔄 Сброс/увеличение трафика
|
||||
- 🌍 Смена стран на лету
|
||||
- Устройства с возможностью докупки/сброса устройств)
|
||||
- 🌍 Смена стран на лету (Доступно при наличие двух стран(сквадов) в продаже)
|
||||
- 📱 Настройка устройств с возможностью докупки(до 10ти устройств) + сброса устройств
|
||||
|
||||
🎁 **Бонусная система**
|
||||
- 🎫 Промокоды на деньги/дни
|
||||
- 👥 Реферальная программа
|
||||
- 🆓 Бесплатный триал
|
||||
- 🔔 Ежедневные уведомления
|
||||
|
||||
</td>
|
||||
@@ -503,7 +510,7 @@ docker compose down -v --remove-orphans
|
||||
🎫 **Промо-система**
|
||||
- 🎁 Создание промокодов (деньги/дни/длинный триал)
|
||||
- 📊 Статистика использования
|
||||
- ⚙️ Полное редактирование промокодов
|
||||
- ⚙️ Полное редактирование промокодов (Изминение условий, активация/деактивация/удаление + статистика применения)
|
||||
|
||||
🖥️ **Мониторинг системы**
|
||||
- 💚 Состояние Remnawave панели
|
||||
@@ -517,6 +524,16 @@ docker compose down -v --remove-orphans
|
||||
- 🔔 Автоуведомления о продлении
|
||||
- 💬 Система поддержки
|
||||
- 📝 Настройка правил сервиса
|
||||
- Поддержка HTML разметки
|
||||
|
||||
🚧 **Режим технических работ**
|
||||
- Ручное включение/отключение с указанием причины
|
||||
- Включение/выключение мониторинга состояния панели (проверяет соеденение до панели раз в 30 сек(можно изменить промежуток)
|
||||
- Принудительаная проверка API
|
||||
|
||||
📖 **Правила сервиса**
|
||||
- Настройка правил сервиса
|
||||
- Просмотр текущих правил
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -16,6 +16,7 @@ class Settings(BaseSettings):
|
||||
|
||||
REMNAWAVE_API_URL: str
|
||||
REMNAWAVE_API_KEY: str
|
||||
REMNAWAVE_SECRET_KEY: Optional[str] = None
|
||||
|
||||
TRIAL_DURATION_DAYS: int = 3
|
||||
TRIAL_TRAFFIC_LIMIT_GB: int = 10
|
||||
|
||||
Vendored
+34
-11
@@ -8,6 +8,7 @@ import aiohttp
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,7 +34,7 @@ class RemnaWaveUser:
|
||||
username: str
|
||||
status: UserStatus
|
||||
used_traffic_bytes: int
|
||||
lifetime_used_traffic_bytes: int # Новое поле
|
||||
lifetime_used_traffic_bytes: int
|
||||
traffic_limit_bytes: int
|
||||
traffic_limit_strategy: TrafficLimitStrategy
|
||||
expire_at: datetime
|
||||
@@ -92,10 +93,12 @@ class RemnaWaveAPIError(Exception):
|
||||
|
||||
class RemnaWaveAPI:
|
||||
|
||||
def __init__(self, base_url: str, api_key: str):
|
||||
def __init__(self, base_url: str, api_key: str, secret_key: Optional[str] = None):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.api_key = api_key
|
||||
self.secret_key = secret_key
|
||||
self.session: Optional[aiohttp.ClientSession] = None
|
||||
self.authenticated = False
|
||||
|
||||
def _detect_connection_type(self) -> str:
|
||||
parsed = urlparse(self.base_url)
|
||||
@@ -124,18 +127,29 @@ class RemnaWaveAPI:
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {self.api_key}',
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Forwarded-Proto': 'https',
|
||||
'X-Forwarded-For': '127.0.0.1',
|
||||
'X-Real-IP': '127.0.0.1'
|
||||
}
|
||||
|
||||
cookies = None
|
||||
if self.secret_key:
|
||||
if ':' in self.secret_key:
|
||||
key_name, key_value = self.secret_key.split(':', 1)
|
||||
cookies = {key_name: key_value}
|
||||
logger.debug(f"🍪 Используем куки: {key_name}=***")
|
||||
else:
|
||||
cookies = {self.secret_key: self.secret_key}
|
||||
logger.debug(f"🍪 Используем куки: {self.secret_key}=***")
|
||||
|
||||
connector_kwargs = {}
|
||||
|
||||
if conn_type == "local":
|
||||
logger.debug("🏠 Использую локальные заголовки proxy")
|
||||
headers.update({
|
||||
'X-Forwarded-For': '127.0.0.1',
|
||||
'X-Forwarded-Proto': 'https',
|
||||
'X-Forwarded-Host': 'localhost',
|
||||
'X-Real-IP': '127.0.0.1',
|
||||
'Host': 'localhost'
|
||||
})
|
||||
|
||||
@@ -152,11 +166,18 @@ class RemnaWaveAPI:
|
||||
|
||||
connector = aiohttp.TCPConnector(**connector_kwargs)
|
||||
|
||||
self.session = aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=30),
|
||||
headers=headers,
|
||||
connector=connector
|
||||
)
|
||||
session_kwargs = {
|
||||
'timeout': aiohttp.ClientTimeout(total=30),
|
||||
'headers': headers,
|
||||
'connector': connector
|
||||
}
|
||||
|
||||
if cookies:
|
||||
session_kwargs['cookies'] = cookies
|
||||
|
||||
self.session = aiohttp.ClientSession(**session_kwargs)
|
||||
self.authenticated = True
|
||||
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
@@ -194,6 +215,8 @@ class RemnaWaveAPI:
|
||||
|
||||
if response.status >= 400:
|
||||
error_message = response_data.get('message', f'HTTP {response.status}')
|
||||
logger.error(f"API Error {response.status}: {error_message}")
|
||||
logger.error(f"Response: {response_text[:500]}")
|
||||
raise RemnaWaveAPIError(
|
||||
error_message,
|
||||
response.status,
|
||||
|
||||
@@ -412,11 +412,11 @@ async def get_countries_price_by_uuids_fallback(country_uuids: List[str], db: As
|
||||
total_price += price
|
||||
prices_list.append(price)
|
||||
else:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
except Exception:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
|
||||
@@ -424,7 +424,7 @@ async def get_countries_price_by_uuids_fallback(country_uuids: List[str], db: As
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка fallback функции: {e}")
|
||||
default_prices = [1000] * len(country_uuids)
|
||||
default_prices = [0] * len(country_uuids)
|
||||
return sum(default_prices), default_prices
|
||||
|
||||
async def handle_manage_country(
|
||||
@@ -1885,7 +1885,7 @@ async def _get_available_countries():
|
||||
countries.append({
|
||||
"uuid": squad["uuid"],
|
||||
"name": squad_name,
|
||||
"price_kopeks": 1000,
|
||||
"price_kopeks": 0,
|
||||
"is_available": True
|
||||
})
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ class RemnaWaveService:
|
||||
def __init__(self):
|
||||
self.api = RemnaWaveAPI(
|
||||
base_url=settings.REMNAWAVE_API_URL,
|
||||
api_key=settings.REMNAWAVE_API_KEY
|
||||
api_key=settings.REMNAWAVE_API_KEY,
|
||||
secret_key=settings.REMNAWAVE_SECRET_KEY
|
||||
)
|
||||
|
||||
def _parse_remnawave_date(self, date_str: str) -> datetime:
|
||||
|
||||
@@ -364,7 +364,7 @@ class SubscriptionService:
|
||||
prices_list.append(price)
|
||||
logger.debug(f"🏷️ Страна {server.display_name}: {price/100}₽")
|
||||
else:
|
||||
default_price = 1000
|
||||
default_price = 0
|
||||
total_price += default_price
|
||||
prices_list.append(default_price)
|
||||
logger.warning(f"⚠️ Сервер {country_uuid} недоступен, используем базовую цену: {default_price/100}₽")
|
||||
@@ -374,7 +374,7 @@ class SubscriptionService:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения цен стран: {e}")
|
||||
default_prices = [1000] * len(country_uuids)
|
||||
default_prices = [0] * len(country_uuids)
|
||||
return sum(default_prices), default_prices
|
||||
|
||||
async def _get_countries_price(self, country_uuids: List[str], db: AsyncSession) -> int:
|
||||
|
||||
Reference in New Issue
Block a user