From fdb19720c4f707f4f0d9ea2eb7ce964ad43a18f7 Mon Sep 17 00:00:00 2001 From: Ilay Date: Sun, 21 Dec 2025 16:04:48 +0500 Subject: [PATCH 1/4] fix: UI and signature validation for Cryptomus/Heleket - removed webhook copy button for Cryptomus and Heleket - fixed signature validation for Cryptomus and Heleket --- src/__version__.py | 2 +- src/bot/routers/dashboard/remnashop/gateways/dialog.py | 1 + src/bot/routers/dashboard/remnashop/gateways/getters.py | 1 + src/infrastructure/database/models/dto/payment_gateway.py | 7 +++++++ src/infrastructure/payment_gateways/cryptomus.py | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/__version__.py b/src/__version__.py index 63af887..364e7ba 100644 --- a/src/__version__.py +++ b/src/__version__.py @@ -1 +1 @@ -__version__ = "0.6.3" +__version__ = "0.6.4" diff --git a/src/bot/routers/dashboard/remnashop/gateways/dialog.py b/src/bot/routers/dashboard/remnashop/gateways/dialog.py index 07ded41..6b6c4be 100644 --- a/src/bot/routers/dashboard/remnashop/gateways/dialog.py +++ b/src/bot/routers/dashboard/remnashop/gateways/dialog.py @@ -108,6 +108,7 @@ text=I18nFormat("btn-gateways-webhook-copy"), copy_text=Format("{webhook}"), ), + when=F["requires_webhook"], ), Row( SwitchTo( diff --git a/src/bot/routers/dashboard/remnashop/gateways/getters.py b/src/bot/routers/dashboard/remnashop/gateways/getters.py index f66c6e8..d192172 100644 --- a/src/bot/routers/dashboard/remnashop/gateways/getters.py +++ b/src/bot/routers/dashboard/remnashop/gateways/getters.py @@ -55,6 +55,7 @@ async def gateway_getter( "is_active": gateway.is_active, "settings": gateway.settings.get_settings_as_list_data, "webhook": config.get_webhook(gateway.type), + "requires_webhook": gateway.requires_webhook, } diff --git a/src/infrastructure/database/models/dto/payment_gateway.py b/src/infrastructure/database/models/dto/payment_gateway.py index 33d8d92..ee155fa 100644 --- a/src/infrastructure/database/models/dto/payment_gateway.py +++ b/src/infrastructure/database/models/dto/payment_gateway.py @@ -23,6 +23,13 @@ class PaymentGatewayDto(TrackableDto): is_active: bool settings: Optional["AnyGatewaySettingsDto"] = None + @property + def requires_webhook(self) -> bool: + return self.type not in { + PaymentGatewayType.CRYPTOMUS, + PaymentGatewayType.HELEKET, + } + class GatewaySettingsDto(TrackableDto): model_config = ConfigDict(validate_assignment=True) diff --git a/src/infrastructure/payment_gateways/cryptomus.py b/src/infrastructure/payment_gateways/cryptomus.py index 4c76e93..8644bc9 100644 --- a/src/infrastructure/payment_gateways/cryptomus.py +++ b/src/infrastructure/payment_gateways/cryptomus.py @@ -138,7 +138,7 @@ def _verify_webhook(self, request: Request, data: dict) -> bool: if not sign: raise ValueError("Missing signature") - json_data = json.dumps(data) + json_data = json.dumps(data, separators=(",", ":")) hash_value = self._generate_signature(json_data) if not compare_digest(hash_value, sign): From dcc25fef7be4ae9e39c474c2dea3a8a7ad328244 Mon Sep 17 00:00:00 2001 From: Ilay Date: Sun, 21 Dec 2025 17:26:53 +0500 Subject: [PATCH 2/4] chore: bump remnapy to 2.3.3 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9f0773b..620ba41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "pydantic-settings~=2.11.0", "redis~=7.0.0", #"remnapy @ git+https://github.com/snoups/remnapy.git@development", - "remnapy>=2.3.2", + "remnapy>=2.3.3", "taskiq[orjson]~=0.12.1", "taskiq-redis~=1.2.0", "uvicorn>=0.38.0", diff --git a/uv.lock b/uv.lock index 723959e..481799f 100644 --- a/uv.lock +++ b/uv.lock @@ -883,7 +883,7 @@ wheels = [ [[package]] name = "remnapy" -version = "2.3.2" +version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -893,9 +893,9 @@ dependencies = [ { name = "pydantic-core" }, { name = "rapid-api-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/05/c6aa9dae9dee923543b8c61231684a1f203248a11af656ab5b7e184516e8/remnapy-2.3.2.tar.gz", hash = "sha256:c8594d04319dab6c233b3adf241531e4a98c3c9670692bac1e3687e858b0ea7d", size = 124378, upload-time = "2025-12-19T12:40:12.817Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/02/6a85175242e312de064d9ac3255dff46f94c2ba35afe90fdf1b6bd8bfa3f/remnapy-2.3.3.tar.gz", hash = "sha256:e0ce86e32476170ca47933f1c2c7c8b55eef35d80d153774f4e24addf84854af", size = 124384, upload-time = "2025-12-21T12:24:32.197Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/b4/27f09be6d255586062c631fa7e0974955b8520ebe3a7a19d41f566341598/remnapy-2.3.2-py3-none-any.whl", hash = "sha256:ace0a7fce44c9eccca26b1e3e9fe8a5b67d8590e07e4a03cd4bf72416c910432", size = 78561, upload-time = "2025-12-19T12:40:11.466Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/22e9ff49d4fa71275939a6cf65822e96e80c44d1327df7473025e4cfda31/remnapy-2.3.3-py3-none-any.whl", hash = "sha256:a03c44e87cb3330a3dd3123b0a6a85e7f32ceaf9917bd5966f9fa82a228b2ea9", size = 78565, upload-time = "2025-12-21T12:24:30.767Z" }, ] [[package]] @@ -950,7 +950,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = "~=2.11.0" }, { name = "qrcode", extras = ["pil"], specifier = ">=8.2" }, { name = "redis", specifier = "~=7.0.0" }, - { name = "remnapy", specifier = ">=2.3.2" }, + { name = "remnapy", specifier = ">=2.3.3" }, { name = "sqlalchemy", extras = ["mypy"], specifier = ">=2.0.0" }, { name = "taskiq", extras = ["orjson"], specifier = "~=0.12.1" }, { name = "taskiq-redis", specifier = "~=1.2.0" }, From 30e158973cc928f5ee484e9894cb5b029dc2ad67 Mon Sep 17 00:00:00 2001 From: Ilay Date: Mon, 22 Dec 2025 09:03:07 +0500 Subject: [PATCH 3/4] fix: device deletion callback, branch variable, notifications - fixed long callback_data for device deletion (use shortened HWID) - fixed variable used to pass branch name during build - improved error handling and logging when sending notifications --- .github/workflows/prod-docker-release.yml | 2 +- .../routers/dashboard/users/user/dialog.py | 1 + .../routers/dashboard/users/user/getters.py | 3 +++ .../routers/dashboard/users/user/handlers.py | 15 +++++++++--- src/bot/routers/menu/dialog.py | 2 +- src/bot/routers/menu/getters.py | 3 +++ src/bot/routers/menu/handlers.py | 22 ++++++++++++++---- .../payment_gateways/cryptomus.py | 2 +- src/services/notification.py | 23 +++++++++---------- 9 files changed, 51 insertions(+), 22 deletions(-) diff --git a/.github/workflows/prod-docker-release.yml b/.github/workflows/prod-docker-release.yml index a948e6d..099b9e6 100644 --- a/.github/workflows/prod-docker-release.yml +++ b/.github/workflows/prod-docker-release.yml @@ -55,7 +55,7 @@ jobs: ghcr.io/snoups/remnashop:${{ github.ref_name }} build-args: | BUILD_TIME=${{ steps.vars.outputs.build_time }} - BUILD_BRANCH=${{ github.ref_name }} + BUILD_BRANCH=main BUILD_COMMIT=${{ steps.vars.outputs.short_sha }} BUILD_TAG=${{ github.ref_name }} diff --git a/src/bot/routers/dashboard/users/user/dialog.py b/src/bot/routers/dashboard/users/user/dialog.py index 434a1fb..08bb3d0 100644 --- a/src/bot/routers/dashboard/users/user/dialog.py +++ b/src/bot/routers/dashboard/users/user/dialog.py @@ -425,6 +425,7 @@ ), id="devices_list", item_id_getter=lambda item: item["hwid"], + item_id_getter=lambda item: item["short_hwid"], items="devices", ), Row( diff --git a/src/bot/routers/dashboard/users/user/getters.py b/src/bot/routers/dashboard/users/user/getters.py index a1fb230..d60c3b6 100644 --- a/src/bot/routers/dashboard/users/user/getters.py +++ b/src/bot/routers/dashboard/users/user/getters.py @@ -189,6 +189,7 @@ async def devices_getter( formatted_devices = [ { + "short_hwid": device.hwid[:32], "hwid": device.hwid, "platform": device.platform, "device_model": device.device_model, @@ -197,6 +198,8 @@ async def devices_getter( for device in devices ] + dialog_manager.dialog_data["hwid_map"] = formatted_devices + return { "current_count": len(devices), "max_count": i18n_format_device_limit(subscription.device_limit), diff --git a/src/bot/routers/dashboard/users/user/handlers.py b/src/bot/routers/dashboard/users/user/handlers.py index 3b8017f..460486a 100644 --- a/src/bot/routers/dashboard/users/user/handlers.py +++ b/src/bot/routers/dashboard/users/user/handlers.py @@ -219,7 +219,16 @@ async def on_device_delete( remnawave_service: FromDishka[RemnawaveService], ) -> None: await sub_manager.load_data() - selected_device = sub_manager.item_id + selected_short_hwid = sub_manager.item_id + hwid_map = sub_manager.dialog_data.get("hwid_map") + + if not hwid_map: + raise ValueError(f"Selected '{selected_short_hwid}' HWID, but 'hwid_map' is missing") + + full_hwid = next((d["hwid"] for d in hwid_map if d["short_hwid"] == selected_short_hwid), None) + + if not full_hwid: + raise ValueError(f"Full HWID not found for '{selected_short_hwid}'") user: UserDto = sub_manager.middleware_data[USER_KEY] target_telegram_id = sub_manager.dialog_data["target_telegram_id"] @@ -228,8 +237,8 @@ async def on_device_delete( if not target_user: raise ValueError(f"User '{target_telegram_id}' not found") - devices = await remnawave_service.delete_device(user=target_user, hwid=selected_device) - logger.info(f"{log(user)} Deleted device '{selected_device}' for user '{target_telegram_id}'") + devices = await remnawave_service.delete_device(user=target_user, hwid=full_hwid) + logger.info(f"{log(user)} Deleted device '{full_hwid}' for user '{target_telegram_id}'") if devices: return diff --git a/src/bot/routers/menu/dialog.py b/src/bot/routers/menu/dialog.py index 8c3788d..685b4b6 100644 --- a/src/bot/routers/menu/dialog.py +++ b/src/bot/routers/menu/dialog.py @@ -124,7 +124,7 @@ ), ), id="devices_list", - item_id_getter=lambda item: item["hwid"], + item_id_getter=lambda item: item["short_hwid"], items="devices", ), Row( diff --git a/src/bot/routers/menu/getters.py b/src/bot/routers/menu/getters.py index 7ab28ec..9ac6f5c 100644 --- a/src/bot/routers/menu/getters.py +++ b/src/bot/routers/menu/getters.py @@ -102,6 +102,7 @@ async def devices_getter( formatted_devices = [ { + "short_hwid": device.hwid[:32], "hwid": device.hwid, "platform": device.platform, "device_model": device.device_model, @@ -110,6 +111,8 @@ async def devices_getter( for device in devices ] + dialog_manager.dialog_data["hwid_map"] = formatted_devices + return { "current_count": len(devices), "max_count": i18n_format_device_limit(user.current_subscription.device_limit), diff --git a/src/bot/routers/menu/handlers.py b/src/bot/routers/menu/handlers.py index b45b838..4a5bad5 100644 --- a/src/bot/routers/menu/handlers.py +++ b/src/bot/routers/menu/handlers.py @@ -98,12 +98,26 @@ async def on_device_delete( remnawave_service: FromDishka[RemnawaveService], ) -> None: await sub_manager.load_data() - selected_device = sub_manager.item_id + selected_short_hwid = sub_manager.item_id user: UserDto = sub_manager.middleware_data[USER_KEY] + hwid_map = sub_manager.dialog_data.get("hwid_map") - if user.current_subscription and user.current_subscription.device_limit: - await remnawave_service.delete_device(user=user, hwid=selected_device) - logger.info(f"{log(user)} Deleted self device '{selected_device}'") + if not hwid_map: + raise ValueError(f"Selected '{selected_short_hwid}' HWID, but 'hwid_map' is missing") + + full_hwid = next((d["hwid"] for d in hwid_map if d["short_hwid"] == selected_short_hwid), None) + + if not full_hwid: + raise ValueError(f"Full HWID not found for '{selected_short_hwid}'") + + if not (user.current_subscription and user.current_subscription.device_limit): + raise ValueError("User has no active subscription or device limit unlimited") + + devices = await remnawave_service.delete_device(user=user, hwid=full_hwid) + logger.info(f"{log(user)} Deleted device '{full_hwid}'") + + if devices: + return await sub_manager.switch_to(state=MainMenu.MAIN) diff --git a/src/infrastructure/payment_gateways/cryptomus.py b/src/infrastructure/payment_gateways/cryptomus.py index 8644bc9..3b7bdbf 100644 --- a/src/infrastructure/payment_gateways/cryptomus.py +++ b/src/infrastructure/payment_gateways/cryptomus.py @@ -17,10 +17,10 @@ from src.core.enums import Currency, TransactionStatus from src.infrastructure.database.models.dto import ( CryptomusGatewaySettingsDto, + HeleketGatewaySettingsDto, PaymentGatewayDto, PaymentResult, ) -from src.infrastructure.database.models.dto.payment_gateway import HeleketGatewaySettingsDto from .base import BasePaymentGateway diff --git a/src/services/notification.py b/src/services/notification.py index cc6f23e..876fe79 100644 --- a/src/services/notification.py +++ b/src/services/notification.py @@ -3,6 +3,7 @@ from typing import Any, Optional, Union, cast from aiogram import Bot +from aiogram.exceptions import TelegramForbiddenError from aiogram.types import ( BufferedInputFile, InlineKeyboardButton, @@ -151,15 +152,14 @@ async def error_notify( # async def _send_message(self, user: BaseUserDto, payload: MessagePayload) -> Optional[Message]: + reply_markup = self._prepare_reply_markup( + payload.reply_markup, + payload.add_close_button, + payload.auto_delete_after, + user.language, + user.telegram_id, + ) try: - reply_markup = self._prepare_reply_markup( - payload.reply_markup, - payload.add_close_button, - payload.auto_delete_after, - user.language, - user.telegram_id, - ) - if (payload.media or payload.media_id) and payload.media_type: sent_message = await self._send_media_message(user, payload, reply_markup) else: @@ -181,11 +181,10 @@ async def _send_message(self, user: BaseUserDto, payload: MessagePayload) -> Opt return sent_message - except Exception as exception: - logger.error( + except TelegramForbiddenError as exception: + logger.exception( f"Failed to send notification '{payload.i18n_key}' " - f"to '{user.telegram_id}': {exception}", - exc_info=True, + f"to '{user.telegram_id}': {exception}" ) return None From c064604779dc5d4f73baf912f4960a0e66834d15 Mon Sep 17 00:00:00 2001 From: Ilay Date: Mon, 22 Dec 2025 09:10:35 +0500 Subject: [PATCH 4/4] chore: update README --- README.md | 2 +- README.ru_RU.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ed7934..4f75bcb 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ ``` > [!WARNING] -> **The latest version of the bot is compatible only with RemnaWave panel version 2.3.\*** +> **The latest version of the bot is compatible only with RemnaWave panel version 2.3–2.4.x** > Before installation, make sure your panel matches this version. diff --git a/README.ru_RU.md b/README.ru_RU.md index c409d92..26f7ed7 100644 --- a/README.ru_RU.md +++ b/README.ru_RU.md @@ -184,7 +184,7 @@ ``` > [!WARNING] -> **Последняя версия бота совместима только с панелью RemnaWave версии 2.3.\*** +> **Последняя версия бота совместима только с панелью RemnaWave версии 2.3–2.4.x** > Перед установкой убедитесь, что ваша панель соответствует этой версии.