fix(env): stabilize .env parsing and add feature toggles

- Handle dotenv placeholders like 'KEY=  # comment' without validation crashes

- Add REQUIRED_CHANNEL_SUBSCRIBE_TO_USE to explicitly enable channel gate

- Add REFERRAL_ENABLED to fully disable referral flow and bonuses

- Hide referral UI/actions when disabled and ignore ref start params

- Update .env.example defaults and README docs for new flags
This commit is contained in:
kavore
2026-02-06 23:49:46 +03:00
parent 31e59f6339
commit 0d637340f5
7 changed files with 109 additions and 35 deletions
+12
View File
@@ -43,6 +43,15 @@ async def referral_command_handler(event: Union[types.Message,
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
if not settings.REFERRAL_ENABLED:
await target_message_obj.answer(
_("referral_no_bonuses_configured"),
reply_markup=get_back_to_main_menu_markup(current_lang, i18n),
)
if isinstance(event, types.CallbackQuery):
await event.answer()
return
try:
bot_info = await bot.get_me()
bot_username = bot_info.username
@@ -136,6 +145,9 @@ async def referral_action_handler(callback: types.CallbackQuery, settings: Setti
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
if action == "share_message":
if not settings.REFERRAL_ENABLED:
await callback.answer(_("referral_no_bonuses_configured"), show_alert=True)
return
try:
bot_info = await bot.get_me()
bot_username = bot_info.username
+21 -4
View File
@@ -134,8 +134,15 @@ async def ensure_required_channel_subscription(
Verify that the user is a member of the required channel (if configured).
Returns True when access can proceed, False when user must subscribe first.
"""
if not settings.REQUIRED_CHANNEL_SUBSCRIBE_TO_USE:
return True
required_channel_id = settings.REQUIRED_CHANNEL_ID
if not required_channel_id:
logging.warning(
"REQUIRED_CHANNEL_SUBSCRIBE_TO_USE is enabled but REQUIRED_CHANNEL_ID is not set. "
"Channel gate is skipped."
)
return True
if isinstance(event, types.CallbackQuery):
@@ -327,7 +334,7 @@ async def start_command_handler(message: types.Message,
promo_code_to_apply: Optional[str] = None
ad_start_param: Optional[str] = None
if ref_match:
if ref_match and settings.REFERRAL_ENABLED:
raw_ref_value = ref_match.group(1)
if raw_ref_value.isdigit():
if settings.LEGACY_REFS:
@@ -345,6 +352,11 @@ async def start_command_handler(message: types.Message,
session, normalized_code)
if ref_user and ref_user.user_id != user_id:
referred_by_user_id = ref_user.user_id
elif ref_match and not settings.REFERRAL_ENABLED:
logging.info(
"User %s started with referral parameter while referral system is disabled.",
user_id,
)
elif promo_match:
promo_code_to_apply = promo_match.group(1)
logging.info(f"User {user_id} started with promo code: {promo_code_to_apply}")
@@ -663,6 +675,10 @@ async def main_action_callback_handler(
promo_code_service: PromoCodeService, session: AsyncSession):
action = callback.data.split(":")[1]
user_id = callback.from_user.id
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs
) if i18n else key
from . import subscription as user_subscription_handlers
from . import referral as user_referral_handlers
@@ -685,6 +701,10 @@ async def main_action_callback_handler(
callback, i18n_data, settings, panel_service, subscription_service,
session, bot)
elif action == "referral":
if not settings.REFERRAL_ENABLED:
await callback.answer(_("referral_no_bonuses_configured"),
show_alert=True)
return
await user_referral_handlers.referral_command_handler(
callback, settings, i18n_data, referral_service, bot, session)
elif action == "apply_promo":
@@ -711,7 +731,4 @@ async def main_action_callback_handler(
session,
is_edit=False)
else:
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
_ = lambda key, **kwargs: i18n.gettext(
i18n_data.get("current_language"), key, **kw) if i18n else key
await callback.answer(_("main_menu_unknown_action"), show_alert=True)
+7 -4
View File
@@ -28,13 +28,16 @@ def get_main_menu_inline_keyboard(
)
)
referral_button = InlineKeyboardButton(
text=_(key="menu_referral_inline"),
callback_data="main_action:referral")
promo_button = InlineKeyboardButton(
text=_(key="menu_apply_promo_button"),
callback_data="main_action:apply_promo")
builder.row(referral_button, promo_button)
if settings.REFERRAL_ENABLED:
referral_button = InlineKeyboardButton(
text=_(key="menu_referral_inline"),
callback_data="main_action:referral")
builder.row(referral_button, promo_button)
else:
builder.row(promo_button)
language_button = InlineKeyboardButton(
text=_(key="menu_language_settings_inline"),
+10
View File
@@ -32,6 +32,13 @@ class ReferralService:
current_payment_db_id: Optional[int] = None,
skip_if_active_before_payment: bool = True) -> Dict[str, Any]:
if not getattr(self.settings, "REFERRAL_ENABLED", True):
return {
"referee_bonus_applied_days": None,
"referee_new_end_date": None,
"inviter_bonus_applied_flag": False,
}
referee_final_end_date: Optional[datetime] = None
referee_bonus_applied_days: Optional[int] = None
inviter_bonus_successfully_applied = False
@@ -260,6 +267,9 @@ class ReferralService:
async def generate_referral_link(self, session: AsyncSession,
bot_username: str,
inviter_user_id: int) -> Optional[str]:
if not getattr(self.settings, "REFERRAL_ENABLED", True):
return None
try:
user = await user_dal.get_user_by_id(session, inviter_user_id)
if not user: