Add button open mode and webapp url

This commit is contained in:
PEDZEO
2025-12-19 04:02:58 +03:00
parent 8c3e71bdfd
commit b81400105f
3 changed files with 49 additions and 2 deletions

View File

@@ -135,6 +135,8 @@ DEFAULT_MENU_CONFIG: Dict[str, Any] = {
"visibility": "subscribers",
"conditions": None,
"dynamic_text": False,
"open_mode": "callback", # "callback" или "direct"
"webapp_url": None, # URL для Mini App при open_mode="direct"
},
"happ_download": {
"type": "builtin",
@@ -308,6 +310,7 @@ BUILTIN_BUTTONS_INFO = [
"callback_data": "subscription_connect",
"default_conditions": {"has_active_subscription": True, "subscription_is_active": True},
"supports_dynamic_text": False,
"supports_direct_open": True, # Может открывать Mini App напрямую
},
{
"id": "happ_download",
@@ -1021,6 +1024,8 @@ class MenuLayoutService:
button_type = button_config.get("type", "builtin")
text_config = button_config.get("text", {})
action = button_config.get("action", "")
open_mode = button_config.get("open_mode", "callback")
webapp_url = button_config.get("webapp_url")
# Получаем текст
text = cls._get_localized_text(text_config, context.language)
@@ -1039,8 +1044,15 @@ class MenuLayoutService:
text=text, web_app=types.WebAppInfo(url=action)
)
else:
# builtin - callback_data
return InlineKeyboardButton(text=text, callback_data=action)
# builtin - проверяем open_mode
if open_mode == "direct" and webapp_url:
# Прямое открытие Mini App через WebAppInfo
return InlineKeyboardButton(
text=text, web_app=types.WebAppInfo(url=webapp_url)
)
else:
# Стандартный callback_data
return InlineKeyboardButton(text=text, callback_data=action)
@classmethod
async def build_keyboard(

View File

@@ -71,6 +71,8 @@ def _serialize_config(config: dict, is_enabled: bool, updated_at) -> MenuLayoutR
if btn_data.get("conditions")
else None,
dynamic_text=btn_data.get("dynamic_text", False),
open_mode=btn_data.get("open_mode", "callback"),
webapp_url=btn_data.get("webapp_url"),
)
return MenuLayoutResponse(
@@ -143,6 +145,7 @@ async def list_builtin_buttons(
if btn_info.get("default_conditions")
else None,
supports_dynamic_text=btn_info.get("supports_dynamic_text", False),
supports_direct_open=btn_info.get("supports_direct_open", False),
)
)
@@ -163,6 +166,10 @@ async def update_button(
if "visibility" in updates and updates["visibility"] is not None:
if hasattr(updates["visibility"], "value"):
updates["visibility"] = updates["visibility"].value
# Конвертируем open_mode в строку если есть
if "open_mode" in updates and updates["open_mode"] is not None:
if hasattr(updates["open_mode"], "value"):
updates["open_mode"] = updates["open_mode"].value
# Конвертируем conditions - убираем None значения если это dict
if "conditions" in updates and updates["conditions"] is not None:
if isinstance(updates["conditions"], dict):
@@ -183,6 +190,8 @@ async def update_button(
if button.get("conditions")
else None,
dynamic_text=button.get("dynamic_text", False),
open_mode=button.get("open_mode", "callback"),
webapp_url=button.get("webapp_url"),
)
except KeyError as e:
raise HTTPException(status.HTTP_404_NOT_FOUND, str(e)) from e
@@ -290,6 +299,8 @@ async def add_custom_button(
if button.get("conditions")
else None,
dynamic_text=button.get("dynamic_text", False),
open_mode=button.get("open_mode", "callback"),
webapp_url=button.get("webapp_url"),
)
except ValueError as e:
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(e)) from e

View File

@@ -26,6 +26,13 @@ class ButtonVisibility(str, Enum):
SUBSCRIBERS = "subscribers" # Только подписчикам
class ButtonOpenMode(str, Enum):
"""Режим открытия кнопки."""
CALLBACK = "callback" # Отправляет callback_data боту (по умолчанию)
DIRECT = "direct" # Сразу открывает Mini App через WebAppInfo
class ButtonConditions(BaseModel):
"""Условия показа кнопки."""
@@ -96,6 +103,14 @@ class MenuButtonConfig(BaseModel):
dynamic_text: bool = Field(
default=False, description="Текст содержит плейсхолдеры ({balance} и т.д.)"
)
open_mode: ButtonOpenMode = Field(
default=ButtonOpenMode.CALLBACK,
description="Режим открытия: callback (через бота) или direct (сразу Mini App)",
)
webapp_url: Optional[str] = Field(
default=None,
description="URL для Mini App при open_mode=direct",
)
model_config = ConfigDict(extra="forbid")
@@ -156,6 +171,9 @@ class BuiltinButtonInfo(BaseModel):
supports_dynamic_text: bool = Field(
default=False, description="Поддерживает ли динамический текст"
)
supports_direct_open: bool = Field(
default=False, description="Поддерживает ли прямое открытие Mini App"
)
class BuiltinButtonsListResponse(BaseModel):
@@ -197,6 +215,12 @@ class ButtonUpdateRequest(BaseModel):
action: Optional[str] = Field(
default=None, description="Новый action (для URL/MiniApp кнопок)"
)
open_mode: Optional[ButtonOpenMode] = Field(
default=None, description="Режим открытия: callback или direct"
)
webapp_url: Optional[str] = Field(
default=None, description="URL для Mini App при open_mode=direct"
)
model_config = ConfigDict(extra="forbid")