Compare commits

...

10 Commits

Author SHA1 Message Date
Egor 2a0a2dccff Update README.md 2025-08-13 06:55:22 +03:00
Egor 66a6f32d4c Update .env.example 2025-08-13 06:51:35 +03:00
Egor 02b3f61953 Update translations.py 2025-08-13 06:37:10 +03:00
Egor dc08526255 Update config.py 2025-08-13 06:36:52 +03:00
Egor 2ca339610b Update subscription_monitor.py 2025-08-13 06:36:32 +03:00
Egor ffe65937c1 Update README.md 2025-08-12 19:16:27 +03:00
Egor 4b6f03eef5 Update README.md 2025-08-12 19:15:02 +03:00
Egor d2f530a7fe Update bedolaga.sh 2025-08-12 19:10:37 +03:00
Egor 44ea4b6b13 Update .env.example 2025-08-12 18:55:22 +03:00
Egor ebc8421a70 Update README.md 2025-08-12 18:52:20 +03:00
6 changed files with 788 additions and 723 deletions
+4 -1
View File
@@ -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)
+444 -650
View File
File diff suppressed because it is too large Load Diff
+200 -56
View File
@@ -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 для перехода в меню управления..."
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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',