Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a0a2dccff | |||
| 66a6f32d4c | |||
| 02b3f61953 | |||
| dc08526255 | |||
| 2ca339610b | |||
| ffe65937c1 | |||
| 4b6f03eef5 | |||
| d2f530a7fe | |||
| 44ea4b6b13 | |||
| ebc8421a70 |
+4
-1
@@ -10,7 +10,6 @@ REFERRAL_PERCENTAGE=0.2
|
||||
|
||||
# RemnaWave API Configuration
|
||||
REMNAWAVE_URL=https://example.com
|
||||
REMNAWAVE_MODE=remote
|
||||
REMNAWAVE_TOKEN=
|
||||
|
||||
# Admin Configuration (comma-separated list of Telegram user IDs)
|
||||
@@ -24,6 +23,9 @@ TRIAL_DURATION_DAYS=3 # Дней триала
|
||||
TRIAL_TRAFFIC_GB=2 # Лимит трафика у триал подписки
|
||||
TRIAL_SQUAD_UUID= # UUID сквада из панели remnawave /dashboard/management/internal-squads
|
||||
TRIAL_PRICE=0.0 # Оставить 0!
|
||||
TRIAL_NOTIFICATION_ENABLED=true # Уведомление об истекшей триальной подписке
|
||||
TRIAL_NOTIFICATION_HOURS_AFTER=1 # Через сколько отсылать сообщение
|
||||
TRIAL_NOTIFICATION_HOURS_WINDOW=23 # Через сколько выслать повторно
|
||||
|
||||
# Monitor Service Settings (дополнительные настройки)
|
||||
MONITOR_CHECK_INTERVAL=21600 # Промежуток проверики (3600 - будет раз в час проверять и слать уведомления)
|
||||
@@ -33,6 +35,7 @@ DELETE_EXPIRED_TRIAL_DAYS=1 # Через сколько дней после и
|
||||
DELETE_EXPIRED_REGULAR_DAYS=7 # Через сколько дней после истечения удалять обычные подписки
|
||||
AUTO_DELETE_ENABLED=true # Включить автоматическое удаление при ежедневной проверке
|
||||
|
||||
|
||||
LUCKY_GAME_ENABLED=true # Включить/выключить игру удачи
|
||||
LUCKY_GAME_REWARD=50.0 # Размер награды за выигрыш в рублях
|
||||
LUCKY_GAME_NUMBERS=30 # Общее количество чисел для выбора (1-30)
|
||||
|
||||
+200
-56
@@ -101,18 +101,23 @@ create_project_structure() {
|
||||
create_docker_compose() {
|
||||
log "Создание docker-compose.yml..."
|
||||
|
||||
echo "Выберите конфигурацию Docker Compose:"
|
||||
echo "1) Минимальная (рекомендуется) - только PostgreSQL и бот"
|
||||
echo "2) Расширенная - с Redis и Nginx"
|
||||
echo "Выберите конфигурацию установки:"
|
||||
echo "1) Только бот (панель RemnaWave на другом сервере)"
|
||||
echo "2) Панель + бот на одном сервере (рекомендуется)"
|
||||
echo "3) Расширенная - с Redis и Nginx"
|
||||
|
||||
while true; do
|
||||
read -p "Ваш выбор (1-2): " choice
|
||||
read -p "Ваш выбор (1-3): " choice
|
||||
case $choice in
|
||||
1)
|
||||
create_minimal_compose
|
||||
create_standalone_compose
|
||||
break
|
||||
;;
|
||||
2)
|
||||
create_panel_bot_compose
|
||||
break
|
||||
;;
|
||||
3)
|
||||
create_full_compose
|
||||
break
|
||||
;;
|
||||
@@ -123,7 +128,8 @@ create_docker_compose() {
|
||||
done
|
||||
}
|
||||
|
||||
create_minimal_compose() {
|
||||
# Создание конфигурации только для бота (внешняя панель)
|
||||
create_standalone_compose() {
|
||||
cat > "$COMPOSE_FILE" << 'EOF'
|
||||
services:
|
||||
# PostgreSQL Database
|
||||
@@ -180,7 +186,72 @@ networks:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
log "Минимальная конфигурация Docker Compose создана"
|
||||
log "Конфигурация только для бота создана"
|
||||
export COMPOSE_TYPE="standalone"
|
||||
}
|
||||
|
||||
# Создание конфигурации панель + бот
|
||||
create_panel_bot_compose() {
|
||||
cat > "$COMPOSE_FILE" << 'EOF'
|
||||
services:
|
||||
# PostgreSQL Database
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: remnawave_bot_db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: remnawave_bot
|
||||
POSTGRES_USER: remnawave_user
|
||||
POSTGRES_PASSWORD: secure_password_123
|
||||
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- remnawave-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U remnawave_user -d remnawave_bot"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
# RemnaWave Bot
|
||||
bot:
|
||||
image: fr1ngg/remnawave-bedolaga-telegram-bot:latest
|
||||
container_name: remnawave_bot
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+asyncpg://remnawave_user:secure_password_123@postgres:5432/remnawave_bot
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
- ./data:/app/data
|
||||
networks:
|
||||
- remnawave-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "python -c 'print(\"Bot is running\")'"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
remnawave-network:
|
||||
name: remnawave-network
|
||||
external: true
|
||||
EOF
|
||||
|
||||
log "Конфигурация панель + бот на одном сервере создана"
|
||||
log "ВАЖНО: Убедитесь что панель RemnaWave уже установлена и создала сеть remnawave-network"
|
||||
export COMPOSE_TYPE="panel_bot"
|
||||
}
|
||||
|
||||
create_full_compose() {
|
||||
@@ -287,6 +358,7 @@ networks:
|
||||
EOF
|
||||
|
||||
log "Расширенная конфигурация Docker Compose создана"
|
||||
export COMPOSE_TYPE="full"
|
||||
}
|
||||
|
||||
# Создание .env файла
|
||||
@@ -298,29 +370,17 @@ create_env_file() {
|
||||
read -p "Введите BOT_USERNAME (без @): " BOT_USERNAME
|
||||
read -p "Введите ADMIN_IDS (через запятую): " ADMIN_IDS
|
||||
|
||||
# Настройки RemnaWave
|
||||
echo "Выберите режим RemnaWave:"
|
||||
echo "1) remote"
|
||||
echo "2) local"
|
||||
while true; do
|
||||
read -p "Ваш выбор (1-2): " remna_choice
|
||||
case $remna_choice in
|
||||
1)
|
||||
REMNAWAVE_MODE="remote"
|
||||
break
|
||||
;;
|
||||
2)
|
||||
REMNAWAVE_MODE="local"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
error "Неверный выбор. Попробуйте снова."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Настройки RemnaWave в зависимости от типа установки
|
||||
if [ "$COMPOSE_TYPE" = "panel_bot" ]; then
|
||||
log "Настройка для панель + бот на одном сервере"
|
||||
REMNAWAVE_URL="http://remnawave:3000"
|
||||
echo "URL панели будет: $REMNAWAVE_URL (внутренний адрес контейнера)"
|
||||
else
|
||||
read -p "Введите REMNAWAVE_URL (например: https://your-panel.com): " REMNAWAVE_URL
|
||||
fi
|
||||
|
||||
read -p "Введите REMNAWAVE_URL: " REMNAWAVE_URL
|
||||
read -p "Введите REMNAWAVE_TOKEN: " REMNAWAVE_TOKEN
|
||||
read -p "Введите SUBSCRIPTION_BASE_URL (например: https://sub.your-domain.com): " SUBSCRIPTION_BASE_URL
|
||||
|
||||
# Настройки триала
|
||||
while true; do
|
||||
@@ -394,12 +454,20 @@ create_env_file() {
|
||||
|
||||
# Создание .env файла
|
||||
cat > "$ENV_FILE" << EOF
|
||||
# Bot Configuration
|
||||
BOT_TOKEN=$BOT_TOKEN
|
||||
BOT_USERNAME=$BOT_USERNAME
|
||||
ADMIN_IDS=$ADMIN_IDS
|
||||
REMNAWAVE_MODE=$REMNAWAVE_MODE
|
||||
|
||||
# RemnaWave API Configuration
|
||||
REMNAWAVE_URL=$REMNAWAVE_URL
|
||||
REMNAWAVE_TOKEN=$REMNAWAVE_TOKEN
|
||||
SUBSCRIPTION_BASE_URL=$SUBSCRIPTION_BASE_URL
|
||||
|
||||
# Admin Configuration
|
||||
ADMIN_IDS=$ADMIN_IDS
|
||||
SUPPORT_USERNAME=support
|
||||
|
||||
# Trial Configuration
|
||||
TRIAL_ENABLED=$TRIAL_ENABLED
|
||||
EOF
|
||||
|
||||
@@ -412,51 +480,31 @@ EOF
|
||||
fi
|
||||
|
||||
cat >> "$ENV_FILE" << EOF
|
||||
TRIAL_PRICE=0.0
|
||||
|
||||
# Referral Configuration
|
||||
REFERRAL_FIRST_REWARD=$REFERRAL_FIRST_REWARD
|
||||
REFERRAL_REFERRED_BONUS=$REFERRAL_REFERRED_BONUS
|
||||
REFERRAL_THRESHOLD=$REFERRAL_THRESHOLD
|
||||
REFERRAL_PERCENTAGE=$REFERRAL_PERCENTAGE
|
||||
|
||||
# Telegram Stars Payment Settings
|
||||
# Telegram Stars Configuration
|
||||
STARS_ENABLED=$STARS_ENABLED
|
||||
EOF
|
||||
|
||||
# Добавляем курсы звезд только если включена оплата звездами
|
||||
if [ "$STARS_ENABLED" = "true" ]; then
|
||||
cat >> "$ENV_FILE" << EOF
|
||||
# Курсы обмена звезд на рубли
|
||||
# 100 звёзд
|
||||
STARS_100_RATE=$STARS_100_RATE
|
||||
# 150 звёзд
|
||||
STARS_150_RATE=$STARS_150_RATE
|
||||
# 250 звёзд
|
||||
STARS_250_RATE=$STARS_250_RATE
|
||||
# 350 звёзд
|
||||
STARS_350_RATE=$STARS_350_RATE
|
||||
# 500 звёзд
|
||||
STARS_500_RATE=$STARS_500_RATE
|
||||
EOF
|
||||
else
|
||||
cat >> "$ENV_FILE" << EOF
|
||||
# Курсы обмена звезд на рубли (отключено)
|
||||
# 100 звёзд
|
||||
STARS_100_RATE=
|
||||
# 150 звёзд
|
||||
STARS_150_RATE=
|
||||
# 250 звёзд
|
||||
STARS_250_RATE=
|
||||
# 350 звёзд
|
||||
STARS_350_RATE=
|
||||
# 500 звёзд
|
||||
STARS_500_RATE=
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat >> "$ENV_FILE" << EOF
|
||||
|
||||
# Monitor Service Settings
|
||||
# Monitor Configuration
|
||||
MONITOR_ENABLED=true
|
||||
MONITOR_CHECK_INTERVAL=21600
|
||||
MONITOR_DAILY_CHECK_HOUR=12
|
||||
@@ -464,6 +512,8 @@ MONITOR_WARNING_DAYS=2
|
||||
DELETE_EXPIRED_TRIAL_DAYS=$DELETE_EXPIRED_TRIAL_DAYS
|
||||
DELETE_EXPIRED_REGULAR_DAYS=$DELETE_EXPIRED_REGULAR_DAYS
|
||||
AUTO_DELETE_ENABLED=true
|
||||
|
||||
# Lucky Game Configuration
|
||||
LUCKY_GAME_ENABLED=true
|
||||
LUCKY_GAME_REWARD=50.0
|
||||
LUCKY_GAME_NUMBERS=30
|
||||
@@ -471,6 +521,17 @@ LUCKY_GAME_WINNING_COUNT=5
|
||||
EOF
|
||||
|
||||
log ".env файл создан успешно"
|
||||
|
||||
# Показываем специальные инструкции для панель + бот
|
||||
if [ "$COMPOSE_TYPE" = "panel_bot" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}=== ВАЖНЫЕ ИНСТРУКЦИИ ДЛЯ ПАНЕЛЬ + БОТ ===${NC}"
|
||||
echo -e "${GREEN}1. Убедитесь что панель RemnaWave уже запущена${NC}"
|
||||
echo -e "${GREEN}2. URL панели установлен как: $REMNAWAVE_URL${NC}"
|
||||
echo -e "${GREEN}3. Бот будет подключаться к панели через внутреннюю Docker сеть${NC}"
|
||||
echo -e "${YELLOW}4. Если панель не запущена, сначала запустите её!${NC}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Создание службы systemd
|
||||
@@ -529,10 +590,23 @@ check_remnawave_connection() {
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
source "$ENV_FILE"
|
||||
if [ ! -z "$REMNAWAVE_URL" ]; then
|
||||
if curl -s --connect-timeout 5 "$REMNAWAVE_URL/api/auth/status" > /dev/null; then
|
||||
return 0 # Подключен
|
||||
# Для внутренних URL (панель+бот) проверка API отключена
|
||||
# так как панель может блокировать HTTP запросы через ProxyCheckMiddleware
|
||||
if [[ "$REMNAWAVE_URL" == *"remnawave:3000"* ]]; then
|
||||
# Для локальной установки просто проверяем что бот запущен
|
||||
# Если бот работает - значит скорее всего API тоже доступен
|
||||
if docker compose -f "$COMPOSE_FILE" ps bot | grep -q "Up"; then
|
||||
return 0 # Считаем что подключен если бот запущен
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 1 # Не подключен
|
||||
# Внешний URL - проверяем напрямую
|
||||
if curl -s --connect-timeout 5 "$REMNAWAVE_URL/api/system/stats" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
@@ -546,8 +620,36 @@ check_remnawave_connection() {
|
||||
start_bot() {
|
||||
log "Запуск бота..."
|
||||
cd "$BOT_DIR"
|
||||
|
||||
# Проверяем тип установки
|
||||
if grep -q "remnawave-network" "$COMPOSE_FILE"; then
|
||||
log "Обнаружена конфигурация панель + бот"
|
||||
|
||||
# Проверяем существование сети
|
||||
if ! docker network ls | grep -q "remnawave-network"; then
|
||||
error "Сеть remnawave-network не найдена!"
|
||||
error "Убедитесь что панель RemnaWave запущена и создала сеть"
|
||||
echo ""
|
||||
echo "Для проверки выполните:"
|
||||
echo " docker network ls | grep remnawave"
|
||||
echo ""
|
||||
echo "Если сети нет, сначала запустите панель RemnaWave"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Сеть remnawave-network найдена ✓"
|
||||
fi
|
||||
|
||||
docker compose up -d
|
||||
log "Бот запущен"
|
||||
|
||||
# Ждем немного и проверяем статус
|
||||
sleep 5
|
||||
if check_bot_status; then
|
||||
log "✅ Бот успешно запущен и работает"
|
||||
else
|
||||
warn "⚠️ Бот запущен но возможны проблемы. Проверьте логи: docker compose logs bot"
|
||||
fi
|
||||
}
|
||||
|
||||
# Остановка бота
|
||||
@@ -1157,12 +1259,33 @@ remove_bot() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Проверка статуса сети RemnaWave
|
||||
check_remnawave_network() {
|
||||
if docker network ls | grep -q "remnawave-network"; then
|
||||
return 0 # Сеть существует
|
||||
else
|
||||
return 1 # Сеть не существует
|
||||
fi
|
||||
}
|
||||
|
||||
# Главное меню
|
||||
show_menu() {
|
||||
clear
|
||||
echo -e "${BLUE}=== RemnaWave Bedolaga Bot Management ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Определяем тип установки
|
||||
INSTALLATION_TYPE="Неизвестно"
|
||||
if [ -f "$COMPOSE_FILE" ]; then
|
||||
if grep -q "remnawave-network" "$COMPOSE_FILE" && grep -q "external: true" "$COMPOSE_FILE"; then
|
||||
INSTALLATION_TYPE="Панель + Бот на одном сервере"
|
||||
elif grep -q "bot_network" "$COMPOSE_FILE"; then
|
||||
INSTALLATION_TYPE="Только бот (внешняя панель)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Тип установки: ${NC}$INSTALLATION_TYPE"
|
||||
|
||||
# Показать статус бота
|
||||
echo -e "${YELLOW}Статус бота:${NC}"
|
||||
if check_bot_status; then
|
||||
@@ -1171,6 +1294,16 @@ show_menu() {
|
||||
echo -e "🔴 Бот: ${RED}ОСТАНОВЛЕН${NC}"
|
||||
fi
|
||||
|
||||
# Показать статус сети (для панель + бот)
|
||||
if [[ "$INSTALLATION_TYPE" == *"Панель + Бот"* ]]; then
|
||||
echo -e "${YELLOW}Статус сети RemnaWave:${NC}"
|
||||
if check_remnawave_network; then
|
||||
echo -e "🟢 Сеть: ${GREEN}СОЗДАНА${NC}"
|
||||
else
|
||||
echo -e "🔴 Сеть: ${RED}НЕ НАЙДЕНА${NC} (запустите панель RemnaWave)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Показать статус подключения к API
|
||||
echo -e "${YELLOW}Подключение к RemnaWave API:${NC}"
|
||||
if check_remnawave_connection; then
|
||||
@@ -1264,6 +1397,17 @@ install_bot() {
|
||||
|
||||
log "Установка завершена!"
|
||||
log "Бот установлен в: $BOT_DIR"
|
||||
|
||||
if [ "$COMPOSE_TYPE" = "panel_bot" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}=== ВАЖНО ДЛЯ ПАНЕЛЬ + БОТ УСТАНОВКИ ===${NC}"
|
||||
echo -e "${GREEN}1. Перед запуском бота убедитесь что панель RemnaWave запущена${NC}"
|
||||
echo -e "${GREEN}2. Панель должна создать сеть 'remnawave-network'${NC}"
|
||||
echo -e "${GREEN}3. Проверить сеть: docker network ls | grep remnawave${NC}"
|
||||
echo -e "${YELLOW}4. Если сети нет - сначала запустите панель RemnaWave!${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
log "Для управления ботом используйте это меню или systemctl"
|
||||
|
||||
read -p "Нажмите Enter для перехода в меню управления..."
|
||||
|
||||
@@ -28,6 +28,9 @@ class Config:
|
||||
DELETE_EXPIRED_TRIAL_DAYS: int = 1
|
||||
DELETE_EXPIRED_REGULAR_DAYS: int = 7
|
||||
AUTO_DELETE_ENABLED: bool = False
|
||||
TRIAL_NOTIFICATION_ENABLED: bool = True
|
||||
TRIAL_NOTIFICATION_HOURS_AFTER: int = 2
|
||||
TRIAL_NOTIFICATION_HOURS_WINDOW: int = 22
|
||||
|
||||
LUCKY_GAME_ENABLED: bool = True
|
||||
LUCKY_GAME_REWARD: float = 50.0
|
||||
@@ -112,6 +115,9 @@ def load_config() -> Config:
|
||||
DELETE_EXPIRED_TRIAL_DAYS=get_int('DELETE_EXPIRED_TRIAL_DAYS', 1),
|
||||
DELETE_EXPIRED_REGULAR_DAYS=get_int('DELETE_EXPIRED_REGULAR_DAYS', 7),
|
||||
AUTO_DELETE_ENABLED=get_bool('AUTO_DELETE_ENABLED', False),
|
||||
TRIAL_NOTIFICATION_ENABLED=get_bool('TRIAL_NOTIFICATION_ENABLED', True),
|
||||
TRIAL_NOTIFICATION_HOURS_AFTER=get_int('TRIAL_NOTIFICATION_HOURS_AFTER', 2),
|
||||
TRIAL_NOTIFICATION_HOURS_WINDOW=get_int('TRIAL_NOTIFICATION_HOURS_WINDOW', 22),
|
||||
LUCKY_GAME_ENABLED=get_bool('LUCKY_GAME_ENABLED', True),
|
||||
LUCKY_GAME_REWARD=get_float('LUCKY_GAME_REWARD', 50.0),
|
||||
LUCKY_GAME_NUMBERS=get_int('LUCKY_GAME_NUMBERS', 30),
|
||||
|
||||
+127
-15
@@ -86,7 +86,7 @@ class SubscriptionMonitorService:
|
||||
logger.info("Subscription monitor service stopped")
|
||||
|
||||
async def _monitor_loop(self):
|
||||
logger.info("🔄 Starting monitor loop")
|
||||
logger.info("🔥 Starting monitor loop")
|
||||
|
||||
logger.info("⏰ Initial check in 10 seconds...")
|
||||
await asyncio.sleep(10)
|
||||
@@ -96,8 +96,10 @@ class SubscriptionMonitorService:
|
||||
logger.info("🔍 Running periodic subscription check...")
|
||||
warnings_sent = await self._check_expiring_subscriptions()
|
||||
|
||||
if warnings_sent > 0:
|
||||
logger.info(f"✅ Monitor check completed: {warnings_sent} warnings sent")
|
||||
trial_notifications = await self._check_expired_trial_subscriptions()
|
||||
|
||||
if warnings_sent > 0 or trial_notifications > 0:
|
||||
logger.info(f"✅ Monitor check completed: {warnings_sent} warnings sent, {trial_notifications} trial notifications sent")
|
||||
else:
|
||||
logger.info("✅ Monitor check completed: no warnings needed")
|
||||
|
||||
@@ -147,7 +149,94 @@ class SubscriptionMonitorService:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in daily loop: {e}", exc_info=True)
|
||||
await asyncio.sleep(3600)
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
async def _check_expired_trial_subscriptions(self) -> int:
|
||||
try:
|
||||
logger.info("🆓 Checking for expired trial subscriptions...")
|
||||
|
||||
notifications_sent = 0
|
||||
now_utc = datetime.utcnow()
|
||||
|
||||
all_users = await self.db.get_all_users()
|
||||
|
||||
for user in all_users:
|
||||
try:
|
||||
user_subs = await self.db.get_user_subscriptions(user.telegram_id)
|
||||
|
||||
for user_sub in user_subs:
|
||||
try:
|
||||
subscription = await self.db.get_subscription_by_id(user_sub.subscription_id)
|
||||
if not subscription:
|
||||
continue
|
||||
|
||||
if not subscription.is_trial:
|
||||
continue
|
||||
|
||||
expires_at_utc = user_sub.expires_at
|
||||
if expires_at_utc.tzinfo is None:
|
||||
expires_at_utc = expires_at_utc.replace(tzinfo=None)
|
||||
else:
|
||||
expires_at_utc = expires_at_utc.astimezone(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
time_diff = expires_at_utc - now_utc
|
||||
hours_since_expiry = -time_diff.total_seconds() / 3600
|
||||
|
||||
if 1 <= hours_since_expiry <= 24 and user_sub.is_active:
|
||||
logger.info(f"🆓 Sending trial expiry notification to user {user.telegram_id}: "
|
||||
f"trial '{subscription.name}' expired {hours_since_expiry:.1f} hours ago")
|
||||
|
||||
try:
|
||||
await self._send_trial_expiry_notification(user, subscription)
|
||||
notifications_sent += 1
|
||||
logger.info(f"✅ Trial expiry notification sent to user {user.telegram_id}")
|
||||
except Exception as notification_error:
|
||||
logger.error(f"❌ Failed to send trial notification to user {user.telegram_id}: {notification_error}")
|
||||
|
||||
except Exception as sub_error:
|
||||
logger.error(f"❌ Error checking trial subscription {user_sub.id}: {sub_error}")
|
||||
|
||||
except Exception as user_error:
|
||||
logger.error(f"❌ Error checking trial subscriptions for user {user.telegram_id}: {user_error}")
|
||||
|
||||
if notifications_sent > 0:
|
||||
logger.info(f"🆓 Trial expiry check completed: {notifications_sent} notifications sent")
|
||||
|
||||
return notifications_sent
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Critical error in check_expired_trial_subscriptions: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
async def _send_trial_expiry_notification(self, user, subscription):
|
||||
try:
|
||||
if not self.bot:
|
||||
logger.error("❌ Bot instance is None, cannot send trial notification")
|
||||
return
|
||||
|
||||
from translations import t
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
message = t('trial_subscription_expired', user.language, name=subscription.name)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(
|
||||
text=t('buy_subscription_btn', user.language),
|
||||
callback_data="buy_subscription"
|
||||
)],
|
||||
[InlineKeyboardButton(
|
||||
text=t('my_subscriptions_btn', user.language),
|
||||
callback_data="my_subscriptions"
|
||||
)]
|
||||
])
|
||||
|
||||
await self.bot.send_message(user.telegram_id, message, reply_markup=keyboard)
|
||||
|
||||
logger.info(f"✅ Trial expiry notification sent to user {user.telegram_id} for subscription '{subscription.name}'")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error sending trial expiry notification to user {user.telegram_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def delete_expired_trial_subscriptions(self, force: bool = False) -> Dict[str, Any]:
|
||||
try:
|
||||
@@ -388,11 +477,11 @@ class SubscriptionMonitorService:
|
||||
f"threshold={self.config.MONITOR_WARNING_DAYS}")
|
||||
|
||||
if subscription.is_trial:
|
||||
logger.debug(f"⏭️ Skipping trial subscription '{subscription.name}'")
|
||||
logger.debug(f"⭐️ Skipping trial subscription '{subscription.name}'")
|
||||
continue
|
||||
|
||||
if getattr(subscription, 'is_imported', False) or subscription.name == "Старая подписка":
|
||||
logger.debug(f"⏭️ Skipping imported subscription '{subscription.name}'")
|
||||
logger.debug(f"⭐️ Skipping imported subscription '{subscription.name}'")
|
||||
continue
|
||||
|
||||
should_warn = (
|
||||
@@ -517,9 +606,13 @@ class SubscriptionMonitorService:
|
||||
warnings_sent = await self._check_expiring_subscriptions()
|
||||
logger.info(f"📢 Sent {warnings_sent} expiry warnings")
|
||||
|
||||
logger.info("🔄 Deactivating expired subscriptions...")
|
||||
logger.info("🆓 Checking expired trial subscriptions...")
|
||||
trial_notifications = await self._check_expired_trial_subscriptions()
|
||||
logger.info(f"🆓 Sent {trial_notifications} trial expiry notifications")
|
||||
|
||||
logger.info("🔥 Deactivating expired subscriptions...")
|
||||
deactivated_count = await self.deactivate_expired_subscriptions()
|
||||
logger.info(f"🔄 Deactivated {deactivated_count} expired subscriptions")
|
||||
logger.info(f"🔥 Deactivated {deactivated_count} expired subscriptions")
|
||||
|
||||
deleted_trials = 0
|
||||
deleted_regular = 0
|
||||
@@ -538,7 +631,7 @@ class SubscriptionMonitorService:
|
||||
await self._send_final_expiry_notifications()
|
||||
logger.info("📩 Final notifications sent")
|
||||
|
||||
logger.info(f"✅ Daily check completed successfully. Warnings: {warnings_sent}, Deactivated: {deactivated_count}, "
|
||||
logger.info(f"✅ Daily check completed successfully. Warnings: {warnings_sent}, Trial notifications: {trial_notifications}, Deactivated: {deactivated_count}, "
|
||||
f"Deleted trials: {deleted_trials}, Deleted regular: {deleted_regular}")
|
||||
return deactivated_count
|
||||
|
||||
@@ -590,7 +683,7 @@ class SubscriptionMonitorService:
|
||||
user_data = await self.api.get_user_by_short_uuid(user_sub.short_uuid)
|
||||
if user_data and user_data.get('uuid'):
|
||||
await self.api.update_user(user_data['uuid'], {'status': 'EXPIRED'})
|
||||
logger.debug(f"🔄 Also deactivated user {user_data['uuid']} in RemnaWave")
|
||||
logger.debug(f"🔥 Also deactivated user {user_data['uuid']} in RemnaWave")
|
||||
except Exception as api_error:
|
||||
logger.warning(f"⚠️ Could not deactivate user in RemnaWave: {api_error}")
|
||||
else:
|
||||
@@ -704,11 +797,30 @@ class SubscriptionMonitorService:
|
||||
days_left = int(hours_left / 24)
|
||||
|
||||
if subscription.is_trial:
|
||||
results.append({
|
||||
'success': True,
|
||||
'message': f'Trial subscription "{subscription.name}" skipped (no warnings for trials)',
|
||||
'error': None
|
||||
})
|
||||
hours_since_expiry = -hours_left
|
||||
if 1 <= hours_since_expiry <= 24 and user_sub.is_active:
|
||||
test_message = f"🧪 [ТЕСТОВОЕ УВЕДОМЛЕНИЕ]\n\n🆓 Ваша триальная подписка '{subscription.name}' истекла! Купите новый тариф чтобы продолжить использование VPN."
|
||||
|
||||
if self.bot:
|
||||
try:
|
||||
await self.bot.send_message(user_id, test_message)
|
||||
results.append({
|
||||
'success': True,
|
||||
'message': f'✅ Sent test trial expiry notification for "{subscription.name}" (expired {hours_since_expiry:.1f} hours ago)',
|
||||
'error': None
|
||||
})
|
||||
except Exception as send_error:
|
||||
results.append({
|
||||
'success': False,
|
||||
'message': f'❌ Failed to send test trial notification for "{subscription.name}"',
|
||||
'error': str(send_error)
|
||||
})
|
||||
else:
|
||||
results.append({
|
||||
'success': True,
|
||||
'message': f'Trial subscription "{subscription.name}" - no notification needed (expired {hours_since_expiry:.1f} hours ago)',
|
||||
'error': None
|
||||
})
|
||||
continue
|
||||
|
||||
if getattr(subscription, 'is_imported', False) or subscription.name == "Старая подписка":
|
||||
|
||||
+7
-1
@@ -194,7 +194,10 @@ TRANSLATIONS = {
|
||||
'lucky_game_games_played': 'Игр сыграно: {count}',
|
||||
'lucky_game_wins': 'Выигрышей: {count}',
|
||||
'lucky_game_total_won': 'Всего выиграно: {amount}₽',
|
||||
'lucky_game_win_rate': 'Процент побед: {rate}%'
|
||||
'lucky_game_win_rate': 'Процент побед: {rate}%',
|
||||
'trial_subscription_expired': '🆓 Ваша триальная подписка "{name}" истекла!\n\n'
|
||||
'💡 Чтобы продолжить использование VPN, купите полный тариф.\n\n'
|
||||
'✨ Доступны различные планы подписки с выгодными ценами!'
|
||||
},
|
||||
|
||||
'en': {
|
||||
@@ -246,6 +249,9 @@ TRANSLATIONS = {
|
||||
'subscription_expires_day_after_tomorrow': '⏰ Your subscription \'{name}\' expires in {days} days!\n\nWe recommend renewing it in advance in \'My Subscriptions\'.\n\n💰 Check your balance - you might want to top it up.',
|
||||
'subscription_expires_in_days': '⏳ Your subscription \'{name}\' expires in {days} days!\n\nYou can renew it in \'My Subscriptions\'.\n\n💡 Early renewal extends from current expiration date.',
|
||||
'extend_subscription_btn': '🔄 Extend Subscription',
|
||||
'trial_subscription_expired': '🆓 Your trial subscription "{name}" has expired!\n\n'
|
||||
'💡 To continue using VPN, please purchase a full plan.\n\n'
|
||||
'✨ Various subscription plans with great prices are available!',
|
||||
'my_subscriptions_btn': '📋 My Subscriptions',
|
||||
'buy_new_subscription_btn': '🛒 Buy New Subscription',
|
||||
'restore_subscription_btn': '🔄 Restore Subscription',
|
||||
|
||||
Reference in New Issue
Block a user