Compare commits

...

7 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
5 changed files with 588 additions and 832 deletions
+4
View File
@@ -23,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 - будет раз в час проверять и слать уведомления)
@@ -32,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 -816
View File
File diff suppressed because it is too large Load Diff
+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',