Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

> Referral system configurator.

> Reward customization: money, extra days, or automatically generated promocodes.
> Reward customization: points or extra days.

> Two-level referral support.

Expand Down Expand Up @@ -163,15 +163,29 @@
- **🧭 Migration**
> Seamless migration from other bots.

- **🪄 MiniApp Support (maposia)**
- **🪄 MiniApp Subscription Page Support**


# ⚙️ Installation and configuration

Install Docker if not installed yet.
```
sudo curl -fsSL https://get.docker.com | sh
```
## Requirements
- Hardware:
- OS: Recommended Ubuntu or Debian
- RAM: Minimum 2 GB, recommended 4 GB
- CPU: Minimum 2 cores, recommended 4 cores
- Storage: 20 GB, minimum and recommended

- Software:
- [Docker](https://docs.docker.com/get-started/get-docker/)

Install Docker using official script
```
sudo curl -fsSL https://get.docker.com | sh
```

> [!WARNING]
> **The latest version of the bot is compatible only with RemnaWave panel version 2.3.\***
> Before installation, make sure your panel matches this version.


## Step 1 – Download required files
Expand Down
26 changes: 20 additions & 6 deletions README.ru_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

> Конфигуратор реферальной системы.

> Настройка наград: деньги, дни или автоматически создаваемые промокоды.
> Настройка наград: баллы или дни.

> Поддержка двухуровневых рефералов.

Expand Down Expand Up @@ -163,15 +163,29 @@
- **🧭 Миграция**
> Простая миграция с других ботов.

- **🪄 Поддержка MiniApp (maposia)**
- **🪄 Поддержка страницы подписки MiniApp**


# ⚙️ Установка и настройка

Установите Docker, если он еще не установлен:
```
sudo curl -fsSL https://get.docker.com | sh
```
## Требования
- Аппаратные:
- ОС: рекомендуется Ubuntu или Debian
- ОЗУ: минимум 2 ГБ, рекомендуется 4 ГБ
- ЦПУ: минимум 2 ядра, рекомендуется 4 ядра
- Хранилище: минимум 20 ГБ

- Программные:
- [Docker](https://docs.docker.com/get-started/get-docker/)

Установить Docker с помощью официального скрипта
```
sudo curl -fsSL https://get.docker.com | sh
```

> [!WARNING]
> **Последняя версия бота совместима только с панелью RemnaWave версии 2.3.\***
> Перед установкой убедитесь, что ваша панель соответствует этой версии.


## Шаг 1 – Скачивание необходимых файлов
Expand Down
17 changes: 11 additions & 6 deletions assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ The banner system supports **localized versions**. A banner corresponding to the

### How it works:

When loading a banner, the system performs the following search steps:
1. **User's locale:** The system first attempts to find a banner in the folder corresponding to the current user's locale (e.g., `en`). Available locales are defined by the `APP_LOCALES` environment variable.
2. **Fallback:** If a banner is not found in the user's locale (or the locale folder itself is missing), the system automatically searches for a banner in the **default locale**, specified by the `APP_DEFAULT_LOCALE` environment variable.
3. **Placeholder banner:** If a banner is not found in either the user's locale or the default locale, a placeholder banner named `default.jpg` will be used. This file must be located directly in the root `banners` directory.
2. **Default (inside user’s locale):** If the specific banner is not found, the system checks for `default.{format}` inside the same locale folder.
3. **Fallback (default locale):** If neither the banner nor `default.{format}` exists in the user’s locale (or if the locale folder itself is missing), the system searches for the banner in the default locale specified
by the `APP_DEFAULT_LOCALE` environment variable.
4. **Placeholder banner:** If a banner is not found in either the user's locale or the default locale, a placeholder banner named `default.jpg` will be used. This file must be located directly in the root `banners` directory.

This ensures that even if a specific banner or locale is not found, some banner will always be displayed, preventing empty or missing images.

Expand All @@ -37,17 +40,19 @@ Banner filenames must correspond to the following predefined names, specified in
* **`DEFAULT`**: The default banner, used when a specific banner is not found.
* **`MENU`**: The main menu banner.
* **`DASHBOARD`**: The dashboard banner.
* **`SUBSCRIPTION`**: The subscription banner.
* **`REFERRAL`**: The referral banner.

## Example file structure

```
banners/
├── en/
│ ├── MENU.jpg
│ └── DASHBOARD.jpg
│ ├── menu.jpg
│ └── dashboard.jpg
├── ru/
│ ├── MENU.gif
│ └── DASHBOARD.gif
│ ├── menu.gif
│ └── dashboard.gif
└── default.jpg
```

Expand Down
Empty file removed assets/banners/en/.gitkeep
Empty file.
Empty file removed assets/translations/en/buttons.ftl
Empty file.
Empty file.
Empty file.
Empty file removed assets/translations/en/utils.ftl
Empty file.
7 changes: 4 additions & 3 deletions assets/translations/ru/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ msg-user-sync-version = { $version ->
}

msg-user-sync-subscription =
• <b>ID</b>: <code>{ $id }</code>
• Статус: { $status ->
[ACTIVE] Активна
[DISABLED] Отключена
Expand Down Expand Up @@ -664,7 +665,7 @@ msg-remnawave-inbounds =


# RemnaShop
msg-remnashop-main = <b>🛍 RemnaShop</b>
msg-remnashop-main = <b>🛍 RemnaShop v{ $version }</b>
msg-admins-main = <b>👮‍♂️ Администраторы</b>


Expand Down Expand Up @@ -885,12 +886,12 @@ msg-plan-squads =
<b>🔗 Сквады</b>

{ $internal_squads ->
[0] { empty }
[0] { space }
*[HAS] <b>⏺️ Внутренние:</b> { $internal_squads }
}

{ $external_squad ->
[0] { empty }
[0] { space }
*[HAS] <b>⏹️ Внешний:</b> { $external_squad }
}

Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ dependencies = [
"msgspec~=0.19.0",
"pydantic-settings~=2.11.0",
"redis~=6.4.0",
"remnawave>=2.2.6",
#"remnawave @ file:///opt/python-sdk/remnawave",
#"remnawave @ git+https://github.com/remnawave/python-sdk.git@development",
"remnawave>=2.3.2",
#"remnapy>=2.3.1",
"taskiq~=0.11.19",
"taskiq-redis~=1.1.2",
"uvicorn>=0.38.0",
Expand Down Expand Up @@ -84,7 +83,7 @@ extra_checks = true
explicit_package_bases = true

[[tool.mypy.overrides]]
module = ["fluentogram.*", "remnawave.*", "qrcode.*"]
module = ["fluentogram.*", "remnawave.*", "qrcode.*", "remnapy.*"]
follow_untyped_imports = true

[[tool.mypy.overrides]]
Expand Down
2 changes: 1 addition & 1 deletion src/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.5.5"
__version__ = "0.5.6"
3 changes: 2 additions & 1 deletion src/bot/routers/dashboard/remnashop/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from src.bot.widgets import Banner, I18nFormat, IgnoreUpdate
from src.core.enums import BannerName

from .getters import admins_getter
from .getters import admins_getter, remnashop_getter
from .handlers import on_logs_request, on_user_role_remove, on_user_select

remnashop = Window(
Expand Down Expand Up @@ -85,6 +85,7 @@
),
IgnoreUpdate(),
state=DashboardRemnashop.MAIN,
getter=remnashop_getter,
)

admins = Window(
Expand Down
11 changes: 11 additions & 0 deletions src/bot/routers/dashboard/remnashop/getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject

from src.__version__ import __version__
from src.core.config import AppConfig
from src.core.enums import UserRole
from src.infrastructure.database.models.dto import UserDto
from src.services.user import UserService


async def remnashop_getter(
dialog_manager: DialogManager,
config: AppConfig,
**kwargs: Any,
) -> dict[str, Any]:
return {
"version": __version__,
}


@inject
async def admins_getter(
dialog_manager: DialogManager,
Expand Down
6 changes: 2 additions & 4 deletions src/bot/routers/dashboard/remnashop/plans/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from remnawave.enums.users import TrafficLimitStrategy

from src.bot.keyboards import main_menu_button
from src.bot.routers.extra.test import show_dev_popup
from src.bot.states import DashboardRemnashop, RemnashopPlans
from src.bot.widgets import Banner, I18nFormat, IgnoreUpdate
from src.core.enums import BannerName, Currency, PlanAvailability, PlanType
Expand Down Expand Up @@ -523,11 +522,10 @@
),
),
Row(
Button(
SwitchTo(
text=I18nFormat("btn-plan-external-squads"),
id="external",
# state=RemnashopPlans.EXTERNAL_SQUADS,
on_click=show_dev_popup,
state=RemnashopPlans.EXTERNAL_SQUADS,
),
),
Row(
Expand Down
12 changes: 6 additions & 6 deletions src/bot/routers/dashboard/remnashop/plans/getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,16 @@ async def squads_getter(
internal_dict.get(squad, str(squad)) for squad in plan.internal_squads
)

# external_response = await remnawave.external_squads.get_external_squads()
# if not isinstance(external_response, GetExternalSquadsResponseDto):
# raise ValueError("Wrong response from Remnawave external squads")
external_response = await remnawave.external_squads.get_external_squads()
if not isinstance(external_response, GetExternalSquadsResponseDto):
raise ValueError("Wrong response from Remnawave external squads")

# external_dict = {s.uuid: s.name for s in external_response.external_squads}
# external_squad_name = external_dict.get(plan.external_squad) if plan.external_squad else False
external_dict = {s.uuid: s.name for s in external_response.external_squads}
external_squad_name = external_dict.get(plan.external_squad) if plan.external_squad else False

return {
"internal_squads": internal_squads_names or False,
"external_squad": False, # external_squad_name,
"external_squad": external_squad_name or False,
}


Expand Down
16 changes: 4 additions & 12 deletions src/bot/routers/dashboard/remnashop/plans/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,25 +597,17 @@ async def on_allowed_user_input(
if not plan:
raise ValueError("PlanDto not found in dialog data")

allowed_user = await user_service.get(telegram_id=int(message.text))
allowed_user_id = int(message.text)

if not allowed_user:
logger.warning(f"{log(user)} No user found with Telegram ID '{message.text}'")
await notification_service.notify_user(
user=user,
payload=MessagePayload(i18n_key="ntf-plan-no-user-found"),
)
return # NOTE: Allow adding non-existent users to the list?

if allowed_user.telegram_id in plan.allowed_user_ids:
logger.warning(f"{log(user)} User '{allowed_user.telegram_id}' is already allowed for plan")
if allowed_user_id in plan.allowed_user_ids:
logger.warning(f"{log(user)} User '{allowed_user_id}' is already allowed for plan")
await notification_service.notify_user(
user=user,
payload=MessagePayload(i18n_key="ntf-plan-user-already-allowed"),
)
return

plan.allowed_user_ids.append(allowed_user.telegram_id)
plan.allowed_user_ids.append(allowed_user_id)
adapter.save(plan)


Expand Down
11 changes: 6 additions & 5 deletions src/bot/routers/dashboard/remnawave/getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async def system_getter(
raise ValueError("Wrong response from Remnawave")

return {
"version": "", # TODO: Добавить версию панели
"cpu_cores": response.cpu.physical_cores,
"cpu_threads": response.cpu.cores,
"ram_used": i18n_format_bytes_to_unit(response.memory.active),
Expand All @@ -59,10 +60,10 @@ async def users_getter(

return {
"users_total": str(response.users.total_users),
"users_active": str(response.users.status_counts.active),
"users_disabled": str(response.users.status_counts.disabled),
"users_limited": str(response.users.status_counts.limited),
"users_expired": str(response.users.status_counts.expired),
"users_active": str(response.users.status_counts.get("ACTIVE")),
"users_disabled": str(response.users.status_counts.get("DISABLED")),
"users_limited": str(response.users.status_counts.get("LIMITED")),
"users_expired": str(response.users.status_counts.get("EXPIRED")),
"online_last_day": str(response.online_stats.last_day),
"online_last_week": str(response.online_stats.last_week),
"online_never": str(response.online_stats.never_online),
Expand Down Expand Up @@ -127,7 +128,7 @@ async def nodes_getter(
if not isinstance(response, GetAllNodesResponseDto):
raise ValueError("Wrong response from Remnawave")

for node in response.root:
for node in response:
kwargs_for_i18n = {
"xray_uptime": i18n_format_seconds(node.xray_uptime),
"traffic_used": i18n_format_bytes_to_unit(node.traffic_used_bytes),
Expand Down
5 changes: 2 additions & 3 deletions src/bot/routers/dashboard/users/user/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,10 @@
),
),
Row(
Button(
SwitchTo(
text=I18nFormat("btn-user-subscription-external-squads"),
id="external",
# state=DashboardUser.EXTERNAL_SQUADS,
on_click=show_dev_popup,
state=DashboardUser.EXTERNAL_SQUADS,
),
),
Row(
Expand Down
Loading