mirror of
https://github.com/snoups/remnashop.git
synced 2026-04-25 03:56:23 +00:00
fix: broadcast close button, webhook response, FreeKassa nonce and misc fixes
- added get_close_notification_button helper and CLOSE_BUTTON_ID constant - added close button as a selectable option in broadcast button picker - renamed get_goto_buttons to get_broadcast_buttons - fixed webhook endpoint: moved gateway.build_webhook_response out of finally block - re-enabled YooKassa webhook verification - fixed FreeKassa nonce: switched to time.time_ns() for strict monotonicity - fixed FreeKassa currency: use .upper() instead of .value - fixed system stats getter: use stats.memory.used instead of active, cpu.cores instead of physical_cores - fixed node traffic_limit: pass None instead of 0 to i18n_format_bytes_to_unit - added as_payload to UserDevicesUpdatedEvent and SubscriptionRevokedEvent with user keyboard - added session.close() in UnitOfWork __aexit__ - added python-multipart dependency - added DOCS constant, updated update keyboard links to docs instead of GitHub README - bumped version 0.7.5
This commit is contained in:
@@ -42,7 +42,7 @@ btn-requirement =
|
||||
btn-menu =
|
||||
.trial = 🎁 ПОПРОБОВАТЬ БЕСПЛАТНО
|
||||
.connect = 🚀 Подключиться
|
||||
.devices = 📱 Мои устройства
|
||||
.devices = 📱 Устройства
|
||||
.subscription = 💳 Подписка
|
||||
.invite = 👥 Пригласить
|
||||
.support = 🆘 Поддержка
|
||||
|
||||
@@ -687,10 +687,6 @@ msg-remnawave-main =
|
||||
[one] ядро
|
||||
[few] ядра
|
||||
*[more] ядер
|
||||
} { $cpu_threads } { $cpu_threads ->
|
||||
[one] поток
|
||||
[few] потока
|
||||
*[more] потоков
|
||||
}
|
||||
• <b>ОЗУ</b>: { $ram_used } / { $ram_total } ({ $ram_used_percent }%)
|
||||
• <b>Аптайм</b>: { $uptime }
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"greenlet>=3.2.4",
|
||||
"uvicorn>=0.38.0",
|
||||
"fastapi>=0.120.2",
|
||||
"python-multipart>=0.0.22",
|
||||
"remnapy==2.7.0",
|
||||
#
|
||||
"dishka~=1.7.2",
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.7.4"
|
||||
__version__ = "0.7.5"
|
||||
|
||||
@@ -283,6 +283,17 @@ class UserDevicesUpdatedEvent(UserEvent):
|
||||
os_version: Optional[str]
|
||||
user_agent: Optional[str]
|
||||
|
||||
def as_payload(self) -> "MessagePayloadDto":
|
||||
from src.telegram.keyboards import get_user_keyboard # noqa: PLC0415
|
||||
|
||||
return MessagePayloadDto(
|
||||
i18n_key=self.event_key,
|
||||
i18n_kwargs={**asdict(self)},
|
||||
reply_markup=get_user_keyboard(self.telegram_id),
|
||||
disable_default_markup=False,
|
||||
delete_after=None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class UserDeviceAddedEvent(UserDevicesUpdatedEvent):
|
||||
@@ -442,6 +453,17 @@ class SubscriptionRevokedEvent(UserEvent):
|
||||
device_limit: Any
|
||||
expire_time: Any
|
||||
|
||||
def as_payload(self) -> "MessagePayloadDto":
|
||||
from src.telegram.keyboards import get_user_keyboard # noqa: PLC0415
|
||||
|
||||
return MessagePayloadDto(
|
||||
i18n_key=self.event_key,
|
||||
i18n_kwargs={**asdict(self)},
|
||||
reply_markup=get_user_keyboard(self.telegram_id),
|
||||
disable_default_markup=False,
|
||||
delete_after=None,
|
||||
)
|
||||
|
||||
@property
|
||||
def event_key(self) -> str:
|
||||
return "event-subscription.revoked"
|
||||
|
||||
@@ -37,7 +37,7 @@ from src.core.enums import Locale, Role
|
||||
from src.core.types import AnyKeyboard
|
||||
from src.infrastructure.services import NotificationQueue
|
||||
from src.infrastructure.services.event_bus import on_event
|
||||
from src.telegram.states import Notification
|
||||
from src.telegram.keyboards import get_close_notification_button
|
||||
|
||||
|
||||
class NotificationService(Notifier):
|
||||
@@ -292,10 +292,11 @@ class NotificationService(Notifier):
|
||||
locale: Locale,
|
||||
chat_id: int,
|
||||
) -> Optional[AnyKeyboard]:
|
||||
close_keyboard = self._get_default_keyboard(get_close_notification_button())
|
||||
|
||||
if reply_markup is None:
|
||||
if not disable_default_markup and delete_after is None:
|
||||
close_button = self._get_close_notification_button(locale=locale)
|
||||
return self._get_default_keyboard(close_button)
|
||||
return self._translate_keyboard_text(close_keyboard, locale)
|
||||
return None
|
||||
|
||||
translated_markup = self._translate_keyboard_text(reply_markup, locale)
|
||||
@@ -305,8 +306,8 @@ class NotificationService(Notifier):
|
||||
|
||||
if isinstance(translated_markup, InlineKeyboardMarkup):
|
||||
builder = InlineKeyboardBuilder.from_markup(translated_markup)
|
||||
builder.row(self._get_close_notification_button(locale))
|
||||
return builder.as_markup()
|
||||
builder.row(get_close_notification_button())
|
||||
return self._translate_keyboard_text(builder.as_markup(), locale)
|
||||
|
||||
logger.warning(
|
||||
f"Unsupported reply_markup type '{type(reply_markup).__name__}' "
|
||||
@@ -314,10 +315,6 @@ class NotificationService(Notifier):
|
||||
)
|
||||
return translated_markup
|
||||
|
||||
def _get_close_notification_button(self, locale: Locale) -> InlineKeyboardButton:
|
||||
text = self._get_translated_text(locale, "btn-common.notification-close")
|
||||
return InlineKeyboardButton(text=text, callback_data=Notification.CLOSE.state)
|
||||
|
||||
def _get_default_keyboard(self, button: InlineKeyboardButton) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder([[button]])
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -19,6 +19,7 @@ REMNAWAVE_MIN_VERSION: Final[Version] = Version("2.7.0")
|
||||
REMNAWAVE_MAX_VERSION: Final[Version] = Version("2.8.0")
|
||||
|
||||
REPOSITORY: Final[str] = "https://github.com/snoups/remnashop"
|
||||
DOCS: Final[str] = "https://remnashop.mintlify.app"
|
||||
T_ME: Final[str] = "https://t.me/"
|
||||
API_V1: Final[str] = "/api/v1"
|
||||
BOT_WEBHOOK_PATH: Final[str] = "/telegram"
|
||||
|
||||
@@ -23,6 +23,7 @@ class UnitOfWorkImpl(UnitOfWork):
|
||||
) -> None:
|
||||
if exc_type:
|
||||
await self.rollback()
|
||||
await self.session.close()
|
||||
|
||||
async def commit(self) -> None:
|
||||
await self.session.commit()
|
||||
|
||||
@@ -96,13 +96,13 @@ class FreeKassaGateway(BasePaymentGateway):
|
||||
async def _create_payment_payload(self, amount: str, order_id: str) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {
|
||||
"shopId": self.data.settings.shop_id, # type: ignore[union-attr]
|
||||
"nonce": int(time.time() * 1000), # must be strictly increasing
|
||||
"nonce": time.time_ns(), # must be strictly increasing
|
||||
"paymentId": order_id,
|
||||
"i": self.data.settings.payment_system_id, # type: ignore[union-attr]
|
||||
"email": self.data.settings.customer_email, # type: ignore[union-attr]
|
||||
"ip": self.data.settings.customer_ip, # type: ignore[union-attr]
|
||||
"amount": str(amount),
|
||||
"currency": self.data.currency.value,
|
||||
"currency": self.data.currency.upper(),
|
||||
"success_url": await self._get_bot_redirect_url(),
|
||||
"failure_url": await self._get_bot_redirect_url(),
|
||||
"notification_url": self.config.get_webhook(self.data.type),
|
||||
|
||||
@@ -107,8 +107,8 @@ class YookassaGateway(BasePaymentGateway):
|
||||
async def handle_webhook(self, request: Request) -> tuple[UUID, TransactionStatus]:
|
||||
logger.debug("Received YooKassa webhook request")
|
||||
|
||||
# if not self._verify_webhook(request):
|
||||
# raise PermissionError("Webhook verification failed")
|
||||
if not self._verify_webhook(request):
|
||||
raise PermissionError("Webhook verification failed")
|
||||
|
||||
webhook_data = await self._get_webhook_data(request)
|
||||
payment_object: dict = webhook_data.get("object", {})
|
||||
|
||||
@@ -9,9 +9,9 @@ from aiogram_dialog.widgets.style import Style
|
||||
from aiogram_dialog.widgets.text import Format
|
||||
from magic_filter import F
|
||||
|
||||
from src.core.constants import GOTO_PREFIX, PAYMENT_PREFIX, REPOSITORY, T_ME
|
||||
from src.core.constants import DOCS, GOTO_PREFIX, PAYMENT_PREFIX, REPOSITORY, T_ME
|
||||
from src.core.enums import ButtonType, PurchaseType
|
||||
from src.telegram.states import DashboardUser, MainMenu, Subscription
|
||||
from src.telegram.states import DashboardUser, MainMenu, Notification, Subscription
|
||||
from src.telegram.widgets import I18nFormat
|
||||
|
||||
CALLBACK_CHANNEL_CONFIRM: Final[str] = "channel_confirm"
|
||||
@@ -89,7 +89,17 @@ back_main_menu_button = (
|
||||
)
|
||||
|
||||
|
||||
def get_goto_buttons(support_url: str, is_referral_enable: bool) -> list[InlineKeyboardButton]:
|
||||
CLOSE_BUTTON_ID: Final[int] = -1
|
||||
|
||||
|
||||
def get_close_notification_button() -> InlineKeyboardButton:
|
||||
return InlineKeyboardButton(
|
||||
text="btn-common.notification-close",
|
||||
callback_data=Notification.CLOSE.state,
|
||||
)
|
||||
|
||||
|
||||
def get_broadcast_buttons(support_url: str, is_referral_enable: bool) -> list[InlineKeyboardButton]:
|
||||
buttons = [
|
||||
InlineKeyboardButton(
|
||||
text="btn-goto.contact-support",
|
||||
@@ -113,6 +123,8 @@ def get_goto_buttons(support_url: str, is_referral_enable: bool) -> list[InlineK
|
||||
)
|
||||
)
|
||||
|
||||
buttons.append(get_close_notification_button())
|
||||
|
||||
return buttons
|
||||
|
||||
|
||||
@@ -199,12 +211,12 @@ def get_remnashop_update_keyboard() -> InlineKeyboardMarkup:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="btn-remnashop-info.release-latest",
|
||||
url=f"{REPOSITORY}/releases/latest",
|
||||
url=DOCS,
|
||||
style=ButtonStyle.PRIMARY,
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="btn-remnashop-info.how-upgrade",
|
||||
url=f"{REPOSITORY}?tab=readme-ov-file#step-5--how-to-upgrade",
|
||||
url=f"{DOCS}/docs/ru/install/update",
|
||||
style=ButtonStyle.PRIMARY,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from src.application.common.dao import BroadcastDao, PlanDao, SettingsDao
|
||||
from src.application.dto import PlanDto
|
||||
from src.application.services import BotService
|
||||
from src.core.constants import DATETIME_FORMAT
|
||||
from src.telegram.keyboards import get_goto_buttons
|
||||
from src.telegram.keyboards import CLOSE_BUTTON_ID, get_broadcast_buttons
|
||||
|
||||
|
||||
@inject
|
||||
@@ -61,18 +61,17 @@ async def buttons_getter(
|
||||
settings = await settings_dao.get()
|
||||
|
||||
if not buttons:
|
||||
all_buttons = get_broadcast_buttons(
|
||||
support_url=bot_service.get_support_url(text=i18n.get("message.help")),
|
||||
is_referral_enable=settings.referral.enable,
|
||||
)
|
||||
buttons = [
|
||||
{
|
||||
"id": index,
|
||||
"text": goto_button.text,
|
||||
"selected": False,
|
||||
"id": CLOSE_BUTTON_ID if index == len(all_buttons) - 1 else index,
|
||||
"text": btn.text,
|
||||
"selected": index == len(all_buttons) - 1,
|
||||
}
|
||||
for index, goto_button in enumerate(
|
||||
get_goto_buttons(
|
||||
support_url=bot_service.get_support_url(text=i18n.get("message.help")),
|
||||
is_referral_enable=settings.referral.enable,
|
||||
)
|
||||
)
|
||||
for index, btn in enumerate(all_buttons)
|
||||
]
|
||||
dialog_manager.dialog_data["buttons"] = buttons
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from src.application.use_cases.broadcast.queries.audience import (
|
||||
)
|
||||
from src.core.constants import USER_KEY
|
||||
from src.core.enums import BroadcastAudience, MediaType
|
||||
from src.telegram.keyboards import get_goto_buttons
|
||||
from src.telegram.keyboards import CLOSE_BUTTON_ID, get_broadcast_buttons
|
||||
from src.telegram.states import DashboardBroadcast
|
||||
from src.telegram.utils import is_double_click
|
||||
|
||||
@@ -223,17 +223,22 @@ async def on_button_select(
|
||||
|
||||
settings = await settings_dao.get()
|
||||
|
||||
goto_buttons = get_goto_buttons(
|
||||
all_buttons = get_broadcast_buttons(
|
||||
support_url=bot_service.get_support_url(text=i18n.get("message.help")),
|
||||
is_referral_enable=settings.referral.enable,
|
||||
)
|
||||
goto_buttons = all_buttons[:-1]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for button in buttons:
|
||||
if button.get("selected"):
|
||||
builder.row(goto_buttons[int(button["id"])])
|
||||
if selected_id == CLOSE_BUTTON_ID:
|
||||
close_selected = next((b["selected"] for b in buttons if b["id"] == CLOSE_BUTTON_ID), True)
|
||||
_update_payload(dialog_manager, retort, disable_default_markup=not close_selected)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
for button in buttons:
|
||||
if button.get("selected") and button["id"] != CLOSE_BUTTON_ID:
|
||||
builder.row(goto_buttons[int(button["id"])])
|
||||
_update_payload(dialog_manager, retort, reply_markup=builder.as_markup().model_dump())
|
||||
|
||||
_update_payload(dialog_manager, retort, reply_markup=builder.as_markup().model_dump())
|
||||
logger.debug(f"{user.log} Updated payload keyboard: {buttons}")
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from aiogram_dialog import DialogManager
|
||||
from aiogram_dialog.widgets.common import ManagedScroll
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
from loguru import logger
|
||||
from remnapy import RemnawaveSDK
|
||||
|
||||
from src.application.common import TranslatorRunner
|
||||
@@ -17,20 +18,19 @@ async def system_getter(
|
||||
remnawave_sdk: FromDishka[RemnawaveSDK],
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
result = await remnawave_sdk.system.get_stats()
|
||||
stats = await remnawave_sdk.system.get_stats()
|
||||
metadata = await remnawave_sdk.system.get_metadata()
|
||||
|
||||
logger.success(stats)
|
||||
return {
|
||||
"version": metadata.version,
|
||||
"cpu_cores": result.cpu.physical_cores,
|
||||
"cpu_threads": result.cpu.cores,
|
||||
"ram_used": i18n_format_bytes_to_unit(result.memory.active),
|
||||
"ram_total": i18n_format_bytes_to_unit(result.memory.total),
|
||||
"cpu_cores": stats.cpu.cores,
|
||||
"ram_used": i18n_format_bytes_to_unit(stats.memory.used),
|
||||
"ram_total": i18n_format_bytes_to_unit(stats.memory.total),
|
||||
"ram_used_percent": percent(
|
||||
part=result.memory.active or 0,
|
||||
whole=result.memory.total,
|
||||
part=stats.memory.used or 0,
|
||||
whole=stats.memory.total,
|
||||
),
|
||||
"uptime": i18n_format_seconds(result.uptime),
|
||||
"uptime": i18n_format_seconds(stats.uptime),
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@ async def nodes_getter(
|
||||
xray_uptime=i18n_format_seconds(node.xray_uptime),
|
||||
users_online=node.users_online,
|
||||
traffic_used=i18n_format_bytes_to_unit(node.traffic_used_bytes),
|
||||
traffic_limit=i18n_format_bytes_to_unit(node.traffic_limit_bytes, round_up=True),
|
||||
traffic_limit=i18n_format_bytes_to_unit(
|
||||
node.traffic_limit_bytes or None, round_up=True
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -43,14 +43,12 @@ async def payments_webhook(
|
||||
|
||||
payment_id, payment_status = await gateway.handle_webhook(request)
|
||||
await handle_payment_transaction_task.kiq(payment_id, payment_status) # type: ignore[call-overload]
|
||||
return Response(status_code=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error processing webhook for '{gateway_type}': {e}")
|
||||
error_event = ErrorEvent(**config.build.data, exception=e)
|
||||
await event_publisher.publish(error_event)
|
||||
|
||||
finally:
|
||||
if gateway is not None:
|
||||
return await gateway.build_webhook_response(request)
|
||||
return Response(status_code=status.HTTP_200_OK)
|
||||
if gateway is not None:
|
||||
return await gateway.build_webhook_response(request)
|
||||
return Response(status_code=status.HTTP_200_OK)
|
||||
|
||||
Reference in New Issue
Block a user