Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8afe613451 | |||
| 8de9c6e532 | |||
| b69fcbde11 | |||
| 44d6b6b266 | |||
| 4234769e92 | |||
| c2cabbee09 | |||
| 067b1b6716 | |||
| 5b64046137 | |||
| 085a61721a | |||
| 4c2e11e64b | |||
| 7c5f35b1cf | |||
| 561708b777 | |||
| 806a959662 | |||
| 966a599c2c | |||
| 0ed98c39b6 | |||
| 095bc00b33 | |||
| 43762ce8f4 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "3.4.0"
|
||||
".": "3.5.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## [3.5.0](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/compare/v3.4.0...v3.5.0) (2026-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add tariff reorder API endpoint ([4c2e11e](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/4c2e11e64bed41592f5a12061dcca74ce43e0806))
|
||||
* pass platform-level fields from RemnaWave config to frontend ([095bc00](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/095bc00b33d7082558a8b7252906db2850dce9da))
|
||||
* serve original RemnaWave config from app-config endpoint ([43762ce](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/43762ce8f4fa7142a1ca62a92b97a027dab2564d))
|
||||
* tariff reorder API endpoint ([085a617](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/085a61721a8175b3f4fd744614c446d73346f2b7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enforce blacklist via middleware ([561708b](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/561708b7772ec5b84d6ee049aeba26dc70675583))
|
||||
* enforce blacklist via middleware instead of per-handler checks ([966a599](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/966a599c2c778dce9eea3c61adf6067fb33119f6))
|
||||
* exclude signature field from Telegram initData HMAC validation ([5b64046](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/5b6404613772610c595e55bde1249cdf6ec3269d))
|
||||
* improve button URL resolution and pass uiConfig to frontend ([0ed98c3](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/0ed98c39b6c95911a38a26a32d0ffbcf9cfd7c80))
|
||||
* restore unquote for user data parsing in telegram auth ([c2cabbe](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/c2cabbee097a41a95d16c34d43ab7e70d076c4dc))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* remove signature pop from HMAC validation ([4234769](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot/commit/4234769e92104a6c4f8f1d522e1fca25bc7b20d0))
|
||||
@@ -61,6 +61,7 @@ from app.handlers.admin import (
|
||||
)
|
||||
from app.handlers.stars_payments import register_stars_handlers
|
||||
from app.middlewares.auth import AuthMiddleware
|
||||
from app.middlewares.blacklist import BlacklistMiddleware
|
||||
from app.middlewares.button_stats import ButtonStatsMiddleware
|
||||
from app.middlewares.display_name_restriction import DisplayNameRestrictionMiddleware
|
||||
from app.middlewares.global_error import GlobalErrorMiddleware
|
||||
@@ -119,6 +120,10 @@ async def setup_bot() -> tuple[Bot, Dispatcher]:
|
||||
dp.callback_query.middleware(LoggingMiddleware())
|
||||
dp.message.middleware(MaintenanceMiddleware())
|
||||
dp.callback_query.middleware(MaintenanceMiddleware())
|
||||
blacklist_middleware = BlacklistMiddleware()
|
||||
dp.message.middleware(blacklist_middleware)
|
||||
dp.callback_query.middleware(blacklist_middleware)
|
||||
dp.pre_checkout_query.middleware(blacklist_middleware)
|
||||
display_name_middleware = DisplayNameRestrictionMiddleware()
|
||||
dp.message.middleware(display_name_middleware)
|
||||
dp.callback_query.middleware(display_name_middleware)
|
||||
|
||||
@@ -12,6 +12,7 @@ from app.config import settings
|
||||
from app.database.crud.user import get_user_by_id
|
||||
from app.database.database import AsyncSessionLocal
|
||||
from app.database.models import User
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.maintenance_service import maintenance_service
|
||||
|
||||
from .auth.jwt_handler import get_token_payload
|
||||
@@ -104,6 +105,18 @@ async def get_current_cabinet_user(
|
||||
detail='User account is not active',
|
||||
)
|
||||
|
||||
# Check blacklist
|
||||
if user.telegram_id is not None:
|
||||
is_blacklisted, reason = await blacklist_service.is_user_blacklisted(user.telegram_id, user.username)
|
||||
if is_blacklisted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail={
|
||||
'code': 'blacklisted',
|
||||
'message': reason or 'Доступ запрещен',
|
||||
},
|
||||
)
|
||||
|
||||
# Check maintenance mode (allow admins to pass)
|
||||
if maintenance_service.is_maintenance_active():
|
||||
# Проверяем админа по telegram_id ИЛИ email
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.database.crud.tariff import (
|
||||
get_tariff_by_id,
|
||||
get_tariff_subscriptions_count,
|
||||
load_period_prices_from_db,
|
||||
reorder_tariffs,
|
||||
set_tariff_promo_groups,
|
||||
update_tariff,
|
||||
)
|
||||
@@ -29,6 +30,7 @@ from ..schemas.tariffs import (
|
||||
TariffDetailResponse,
|
||||
TariffListItem,
|
||||
TariffListResponse,
|
||||
TariffSortOrderRequest,
|
||||
TariffStatsResponse,
|
||||
TariffToggleResponse,
|
||||
TariffTrialResponse,
|
||||
@@ -157,6 +159,21 @@ async def get_available_servers(
|
||||
]
|
||||
|
||||
|
||||
@router.put('/order')
|
||||
async def update_tariff_order(
|
||||
request: TariffSortOrderRequest,
|
||||
admin: User = Depends(get_current_admin_user),
|
||||
db: AsyncSession = Depends(get_cabinet_db),
|
||||
):
|
||||
"""Update the display order of tariffs."""
|
||||
await reorder_tariffs(db, request.tariff_ids)
|
||||
await db.commit()
|
||||
|
||||
logger.info(f'Admin {admin.id} updated tariff order: {request.tariff_ids}')
|
||||
|
||||
return {'message': 'Tariff order updated successfully'}
|
||||
|
||||
|
||||
@router.get('/{tariff_id}', response_model=TariffDetailResponse)
|
||||
async def get_tariff(
|
||||
tariff_id: int,
|
||||
|
||||
@@ -2917,7 +2917,12 @@ def _convert_remnawave_config_to_cabinet(config: dict[str, Any]) -> dict[str, An
|
||||
|
||||
|
||||
async def _load_app_config_async() -> dict[str, Any]:
|
||||
"""Load app config from RemnaWave (if configured) or local file."""
|
||||
"""Load app config from RemnaWave (if configured) or local file.
|
||||
|
||||
When config comes from RemnaWave, returns the original format with
|
||||
``_isRemnawave`` flag so the caller can serve it as-is (enriched with
|
||||
deep links) instead of converting to the legacy step-based format.
|
||||
"""
|
||||
remnawave_uuid = _get_remnawave_config_uuid()
|
||||
|
||||
if remnawave_uuid:
|
||||
@@ -2927,22 +2932,9 @@ async def _load_app_config_async() -> dict[str, Any]:
|
||||
config = await api.get_subscription_page_config(remnawave_uuid)
|
||||
if config and config.config:
|
||||
logger.debug(f'Loaded app config from RemnaWave: {remnawave_uuid}')
|
||||
# Debug: log raw RemnaWave config structure
|
||||
import json
|
||||
|
||||
logger.debug(
|
||||
f'RemnaWave raw config: {json.dumps(config.config, ensure_ascii=False, indent=2)[:2000]}'
|
||||
)
|
||||
converted = _convert_remnawave_config_to_cabinet(config.config)
|
||||
logger.debug(f'Converted config platforms: {list(converted.get("platforms", {}).keys())}')
|
||||
# Log first app from each platform
|
||||
for platform, apps in converted.get('platforms', {}).items():
|
||||
if apps:
|
||||
first_app = apps[0]
|
||||
logger.debug(
|
||||
f'Platform {platform} first app: name={first_app.get("name")}, urlScheme={first_app.get("urlScheme")}'
|
||||
)
|
||||
return converted
|
||||
raw = dict(config.config)
|
||||
raw['_isRemnawave'] = True
|
||||
return raw
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to load RemnaWave config, falling back to file: {e}')
|
||||
|
||||
@@ -3329,6 +3321,29 @@ async def get_happ_downloads(
|
||||
}
|
||||
|
||||
|
||||
def _resolve_button_url(
|
||||
url: str,
|
||||
subscription_url: str | None,
|
||||
subscription_crypto_link: str | None,
|
||||
) -> str:
|
||||
"""Resolve template variables in button URLs.
|
||||
|
||||
Matches remnawave/subscription-page frontend TemplateEngine:
|
||||
- {{SUBSCRIPTION_LINK}} -> plain subscription URL
|
||||
- {{HAPP_CRYPT3_LINK}} -> crypto link
|
||||
- {{HAPP_CRYPT4_LINK}} -> crypto link
|
||||
"""
|
||||
if not url:
|
||||
return url
|
||||
result = url
|
||||
if subscription_url:
|
||||
result = result.replace('{{SUBSCRIPTION_LINK}}', subscription_url)
|
||||
if subscription_crypto_link:
|
||||
result = result.replace('{{HAPP_CRYPT3_LINK}}', subscription_crypto_link)
|
||||
result = result.replace('{{HAPP_CRYPT4_LINK}}', subscription_crypto_link)
|
||||
return result
|
||||
|
||||
|
||||
@router.get('/app-config')
|
||||
async def get_app_config(
|
||||
user: User = Depends(get_current_cabinet_user),
|
||||
@@ -3345,13 +3360,97 @@ async def get_app_config(
|
||||
|
||||
# Load config from RemnaWave (if configured) or local file
|
||||
config = await _load_app_config_async()
|
||||
platforms_raw = config.get('platforms', {})
|
||||
|
||||
is_remnawave = config.pop('_isRemnawave', False)
|
||||
hide_link = settings.should_hide_subscription_link()
|
||||
|
||||
# Строим platformNames из displayName каждой платформы RemnaWave
|
||||
platform_names: dict[str, Any] = {}
|
||||
for pk, pd in config.get('platforms', {}).items():
|
||||
if isinstance(pd, dict) and 'displayName' in pd:
|
||||
platform_names[pk] = pd['displayName']
|
||||
# Фоллбэк для платформ без displayName (en достаточно)
|
||||
fallback_names = {
|
||||
'ios': {'en': 'iPhone/iPad'},
|
||||
'android': {'en': 'Android'},
|
||||
'macos': {'en': 'macOS'},
|
||||
'windows': {'en': 'Windows'},
|
||||
'linux': {'en': 'Linux'},
|
||||
'androidTV': {'en': 'Android TV'},
|
||||
'appleTV': {'en': 'Apple TV'},
|
||||
}
|
||||
for k, v in fallback_names.items():
|
||||
if k not in platform_names:
|
||||
platform_names[k] = v
|
||||
|
||||
if is_remnawave:
|
||||
# ── RemnaWave original format ──
|
||||
# Serve original blocks/svgLibrary enriched with deep links and resolved URLs.
|
||||
platforms: dict[str, Any] = {}
|
||||
for platform_key, platform_data in config.get('platforms', {}).items():
|
||||
if not isinstance(platform_data, dict):
|
||||
continue
|
||||
apps = platform_data.get('apps', [])
|
||||
if not isinstance(apps, list):
|
||||
continue
|
||||
|
||||
enriched_apps = []
|
||||
for app in apps:
|
||||
if not isinstance(app, dict):
|
||||
continue
|
||||
|
||||
# Generate deep link
|
||||
deep_link = None
|
||||
if subscription_url or subscription_crypto_link:
|
||||
deep_link = _create_deep_link(app, subscription_url, subscription_crypto_link)
|
||||
app['deepLink'] = deep_link
|
||||
|
||||
# Resolve templates only for subscriptionLink and copyButton (not external)
|
||||
for block in app.get('blocks', []):
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
for btn in block.get('buttons', []):
|
||||
if not isinstance(btn, dict):
|
||||
continue
|
||||
btn_type = btn.get('type', '')
|
||||
if btn_type in ('subscriptionLink', 'copyButton'):
|
||||
url = btn.get('url', '') or btn.get('link', '')
|
||||
if url and '{{' in url:
|
||||
btn['resolvedUrl'] = _resolve_button_url(
|
||||
url,
|
||||
subscription_url,
|
||||
subscription_crypto_link,
|
||||
)
|
||||
|
||||
enriched_apps.append(app)
|
||||
|
||||
if enriched_apps:
|
||||
# Сохраняем platform-level поля (svgIconKey, displayName и т.д.)
|
||||
platform_output = {k: v for k, v in platform_data.items() if k != 'apps'}
|
||||
platform_output['apps'] = enriched_apps
|
||||
platforms[platform_key] = platform_output
|
||||
|
||||
return {
|
||||
'isRemnawave': True,
|
||||
'platforms': platforms,
|
||||
'svgLibrary': config.get('svgLibrary', {}),
|
||||
'baseTranslations': config.get('baseTranslations'),
|
||||
'baseSettings': config.get('baseSettings'),
|
||||
'uiConfig': config.get('uiConfig', {}),
|
||||
'platformNames': platform_names,
|
||||
'hasSubscription': bool(subscription_url or subscription_crypto_link),
|
||||
'subscriptionUrl': subscription_url,
|
||||
'subscriptionCryptoLink': subscription_crypto_link,
|
||||
'hideLink': hide_link,
|
||||
'branding': config.get('brandingSettings', {}),
|
||||
}
|
||||
|
||||
# ── Legacy file-based format ──
|
||||
platforms_raw = config.get('platforms', {})
|
||||
if not isinstance(platforms_raw, dict):
|
||||
platforms_raw = {}
|
||||
|
||||
# Build response with deep links
|
||||
platforms = {}
|
||||
platforms_legacy: dict[str, Any] = {}
|
||||
for platform_key, apps in platforms_raw.items():
|
||||
if not isinstance(apps, list):
|
||||
continue
|
||||
@@ -3379,23 +3478,10 @@ async def get_app_config(
|
||||
platform_apps.append(app_data)
|
||||
|
||||
if platform_apps:
|
||||
platforms[platform_key] = platform_apps
|
||||
|
||||
# Platform display names for UI
|
||||
platform_names = {
|
||||
'ios': {'ru': 'iPhone/iPad', 'en': 'iPhone/iPad'},
|
||||
'android': {'ru': 'Android', 'en': 'Android'},
|
||||
'macos': {'ru': 'macOS', 'en': 'macOS'},
|
||||
'windows': {'ru': 'Windows', 'en': 'Windows'},
|
||||
'linux': {'ru': 'Linux', 'en': 'Linux'},
|
||||
'androidTV': {'ru': 'Android TV', 'en': 'Android TV'},
|
||||
'appleTV': {'ru': 'Apple TV', 'en': 'Apple TV'},
|
||||
}
|
||||
|
||||
hide_link = settings.should_hide_subscription_link()
|
||||
platforms_legacy[platform_key] = platform_apps
|
||||
|
||||
return {
|
||||
'platforms': platforms,
|
||||
'platforms': platforms_legacy,
|
||||
'platformNames': platform_names,
|
||||
'hasSubscription': bool(subscription_url or subscription_crypto_link),
|
||||
'subscriptionUrl': subscription_url if not hide_link else None,
|
||||
|
||||
@@ -194,6 +194,12 @@ class TariffUpdateRequest(BaseModel):
|
||||
traffic_reset_mode: str | None = None # DAY, WEEK, MONTH, NO_RESET, None = глобальная настройка
|
||||
|
||||
|
||||
class TariffSortOrderRequest(BaseModel):
|
||||
"""Request to reorder tariffs."""
|
||||
|
||||
tariff_ids: list[int] = Field(..., min_length=1, description='Ordered list of tariff IDs')
|
||||
|
||||
|
||||
class TariffToggleResponse(BaseModel):
|
||||
"""Response after toggling tariff."""
|
||||
|
||||
|
||||
@@ -486,8 +486,6 @@ async def reorder_tariffs(
|
||||
for order, tariff_id in enumerate(tariff_order):
|
||||
await db.execute(update(Tariff).where(Tariff.id == tariff_id).values(display_order=order))
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info('Изменен порядок тарифов: %s', tariff_order)
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from app.config import settings
|
||||
from app.database.models import User
|
||||
from app.keyboards.inline import get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.payment_service import PaymentService
|
||||
from app.states import BalanceStates
|
||||
from app.utils.decorators import error_handler
|
||||
@@ -126,23 +125,6 @@ async def process_cryptobot_payment_amount(
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Оплата невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not settings.is_cryptobot_enabled():
|
||||
|
||||
@@ -8,7 +8,6 @@ from app.database.models import User
|
||||
from app.external.telegram_stars import TelegramStarsService
|
||||
from app.keyboards.inline import get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.payment_service import PaymentService
|
||||
from app.states import BalanceStates
|
||||
from app.utils.decorators import error_handler
|
||||
@@ -94,23 +93,6 @@ async def process_stars_payment_amount(message: types.Message, db_user: User, am
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Оплата невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not settings.TELEGRAM_STARS_ENABLED:
|
||||
|
||||
@@ -10,7 +10,6 @@ from app.config import settings
|
||||
from app.database.models import User
|
||||
from app.keyboards.inline import get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.payment_service import PaymentService
|
||||
from app.states import BalanceStates
|
||||
from app.utils.decorators import error_handler
|
||||
@@ -171,23 +170,6 @@ async def process_yookassa_payment_amount(
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Оплата невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not settings.is_yookassa_enabled():
|
||||
@@ -338,23 +320,6 @@ async def process_yookassa_sbp_payment_amount(
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Оплата невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not settings.is_yookassa_enabled() or not settings.YOOKASSA_SBP_ENABLED:
|
||||
|
||||
@@ -9,7 +9,6 @@ from app.database.models import User
|
||||
from app.keyboards.inline import get_back_keyboard
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.admin_notification_service import AdminNotificationService
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.promocode_service import PromoCodeService
|
||||
from app.states import PromoCodeStates
|
||||
from app.utils.decorators import error_handler
|
||||
@@ -71,23 +70,6 @@ async def activate_promocode_for_registration(db: AsyncSession, user_id: int, co
|
||||
|
||||
@error_handler
|
||||
async def process_promocode(message: types.Message, db_user: User, state: FSMContext, db: AsyncSession):
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Активация промокода невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
code = message.text.strip()
|
||||
|
||||
@@ -35,7 +35,6 @@ from app.middlewares.channel_checker import (
|
||||
get_pending_payload_from_redis,
|
||||
)
|
||||
from app.services.admin_notification_service import AdminNotificationService
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.campaign_service import AdvertisingCampaignService
|
||||
from app.services.main_menu_button_service import MainMenuButtonService
|
||||
from app.services.pinned_message_service import (
|
||||
@@ -993,25 +992,6 @@ async def process_referral_code_skip(callback: types.CallbackQuery, state: FSMCo
|
||||
async def complete_registration_from_callback(callback: types.CallbackQuery, state: FSMContext, db: AsyncSession):
|
||||
logger.info(f'🎯 COMPLETE: Завершение регистрации для пользователя {callback.from_user.id}')
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
callback.from_user.id, callback.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await callback.message.answer(
|
||||
f'🚫 Регистрация невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
existing_user = await get_user_by_telegram_id(db, callback.from_user.id)
|
||||
|
||||
if existing_user and existing_user.status == UserStatus.ACTIVE.value:
|
||||
@@ -1262,25 +1242,6 @@ async def complete_registration_from_callback(callback: types.CallbackQuery, sta
|
||||
async def complete_registration(message: types.Message, state: FSMContext, db: AsyncSession):
|
||||
logger.info(f'🎯 COMPLETE: Завершение регистрации для пользователя {message.from_user.id}')
|
||||
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
message.from_user.id, message.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {message.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await message.answer(
|
||||
f'🚫 Регистрация невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
existing_user = await get_user_by_telegram_id(db, message.from_user.id)
|
||||
|
||||
if existing_user and existing_user.status == UserStatus.ACTIVE.value:
|
||||
|
||||
@@ -39,7 +39,6 @@ from app.keyboards.inline import (
|
||||
)
|
||||
from app.localization.texts import get_texts
|
||||
from app.services.admin_notification_service import AdminNotificationService
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
from app.services.remnawave_service import RemnaWaveConfigurationError
|
||||
from app.services.subscription_checkout_service import (
|
||||
clear_subscription_checkout_draft,
|
||||
@@ -1724,24 +1723,6 @@ async def handle_extend_subscription(callback: types.CallbackQuery, db_user: Use
|
||||
|
||||
|
||||
async def confirm_extend_subscription(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
callback.from_user.id, callback.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await callback.answer(
|
||||
f'🚫 Продление подписки невозможно\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.',
|
||||
show_alert=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
days = int(callback.data.split('_')[2])
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
@@ -2228,24 +2209,6 @@ async def devices_continue(callback: types.CallbackQuery, state: FSMContext, db_
|
||||
|
||||
|
||||
async def confirm_purchase(callback: types.CallbackQuery, state: FSMContext, db_user: User, db: AsyncSession):
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
callback.from_user.id, callback.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await callback.answer(
|
||||
f'🚫 Покупка подписки невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.',
|
||||
show_alert=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
# Проверка ограничения на покупку/продление подписки
|
||||
if getattr(db_user, 'restriction_subscription', False):
|
||||
reason = getattr(db_user, 'restriction_reason', None) or 'Действие ограничено администратором'
|
||||
@@ -4134,24 +4097,6 @@ async def handle_simple_subscription_purchase(
|
||||
db: AsyncSession,
|
||||
):
|
||||
"""Обрабатывает простую покупку подписки."""
|
||||
# Проверяем, находится ли пользователь в черном списке
|
||||
is_blacklisted, blacklist_reason = await blacklist_service.is_user_blacklisted(
|
||||
callback.from_user.id, callback.from_user.username
|
||||
)
|
||||
|
||||
if is_blacklisted:
|
||||
logger.warning(f'🚫 Пользователь {callback.from_user.id} находится в черном списке: {blacklist_reason}')
|
||||
try:
|
||||
await callback.answer(
|
||||
f'🚫 Простая покупка подписки невозможна\n\n'
|
||||
f'Причина: {blacklist_reason}\n\n'
|
||||
f'Если вы считаете, что это ошибка, обратитесь в поддержку.',
|
||||
show_alert=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при отправке сообщения о блокировке: {e}')
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
|
||||
if not settings.SIMPLE_SUBSCRIPTION_ENABLED:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import logging
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import CallbackQuery, Message, PreCheckoutQuery, TelegramObject, User as TgUser
|
||||
|
||||
from app.services.blacklist_service import blacklist_service
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlacklistMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: dict[str, Any],
|
||||
) -> Any:
|
||||
user: TgUser | None = None
|
||||
if isinstance(event, (Message, CallbackQuery, PreCheckoutQuery)):
|
||||
user = event.from_user
|
||||
|
||||
if not user or user.is_bot:
|
||||
return await handler(event, data)
|
||||
|
||||
is_blacklisted, reason = await blacklist_service.is_user_blacklisted(user.id, user.username)
|
||||
|
||||
if not is_blacklisted:
|
||||
return await handler(event, data)
|
||||
|
||||
logger.warning(f'🚫 Пользователь {user.id} (@{user.username}) из черного списка: {reason}')
|
||||
|
||||
block_text = (
|
||||
f'🚫 Доступ запрещен\n\nПричина: {reason}\n\nЕсли вы считаете, что это ошибка, обратитесь в поддержку.'
|
||||
)
|
||||
|
||||
try:
|
||||
if isinstance(event, Message):
|
||||
await event.answer(block_text)
|
||||
elif isinstance(event, CallbackQuery):
|
||||
await event.answer(block_text, show_alert=True)
|
||||
elif isinstance(event, PreCheckoutQuery):
|
||||
await event.answer(ok=False, error_message='Доступ запрещен')
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка отправки сообщения о блокировке пользователю {user.id}: {e}')
|
||||
|
||||
return None
|
||||
@@ -160,8 +160,9 @@ class BlacklistService:
|
||||
|
||||
# Проверяем по username, если он передан
|
||||
if username:
|
||||
username_lower = username.lower().lstrip('@')
|
||||
for bl_id, bl_username, bl_reason in self.blacklist_data:
|
||||
if bl_username and (bl_username == username or bl_username == f'@{username}'):
|
||||
if bl_username and bl_username.lower().lstrip('@') == username_lower:
|
||||
logger.info(
|
||||
f'Пользователь {username} ({telegram_id}) найден в черном списке по username: {bl_reason}'
|
||||
)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = 'remnawave-bedolaga-telegram-bot'
|
||||
version = '3.4.0'
|
||||
version = "3.5.0"
|
||||
description = 'Telegram bot for RemnaWave VPN service'
|
||||
readme = 'README.md'
|
||||
license = { text = 'MIT' }
|
||||
|
||||
Reference in New Issue
Block a user