forked from FOSS/AutoShop-Djimbo
Compare commits
19 Commits
main
...
30a98f30c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 30a98f30c4 | |||
| 79bb03e5af | |||
| 47f57ae2ba | |||
| f328fdc705 | |||
| a04734b8a7 | |||
| 5e5bab91f2 | |||
| 0d34ac8f1c | |||
| fb80c9fcd8 | |||
| 6f10f085ee | |||
| 55b31b28ca | |||
| 30686aeabd | |||
| 7190963724 | |||
| 3fef026b6e | |||
| 8181394b40 | |||
| f2f8356d99 | |||
| 6697917b95 | |||
| c40fa63142 | |||
| 573dae70fb | |||
| cb56f6757e |
@@ -1,35 +0,0 @@
|
|||||||
.git
|
|
||||||
.gitignore
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
.pytest_cache/
|
|
||||||
.mypy_cache/
|
|
||||||
.ruff_cache/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
htmlcov/
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
.venv/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env/
|
|
||||||
|
|
||||||
*.db
|
|
||||||
*.db-journal
|
|
||||||
*.db-shm
|
|
||||||
*.db-wal
|
|
||||||
*.sqlite
|
|
||||||
*.sqlite3
|
|
||||||
*.log
|
|
||||||
tgbot/data/*.db*
|
|
||||||
tgbot/data/*.sqlite*
|
|
||||||
tgbot/data/*.log
|
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
BOT_TOKEN=
|
BOT_TOKEN=
|
||||||
BOT_ADMIN_IDS=
|
BOT_ADMIN_IDS=
|
||||||
|
|
||||||
BOT_DATABASE_EXPORT=True
|
BOT_DATABASE_EXPORT=True
|
||||||
BOT_STATUS_NOTIFICATION=True
|
BOT_STATUS_NOTIFICATION=True
|
||||||
|
|
||||||
BOT_TIMEZONE=Europe/Moscow
|
BOT_TIMEZONE=Europe/Moscow
|
||||||
BOT_USER_CACHE_TTL=300
|
BOT_USER_CACHE_TTL=300
|
||||||
BOT_THROTTLE_RATE=0.5
|
BOT_THROTTLE_RATE=0.5
|
||||||
|
|
||||||
PATH_DATABASE=tgbot/data/database.db
|
PATH_DATABASE=tgbot/data/database.db
|
||||||
PATH_LOGS=tgbot/data/logs.log
|
PATH_LOGS=tgbot/data/logs.log
|
||||||
|
|||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
FROM python:3.11-slim
|
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
PIP_NO_CACHE_DIR=1
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN useradd --create-home --shell /usr/sbin/nologin bot
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
COPY --chown=bot:bot . .
|
|
||||||
RUN mkdir -p /app/tgbot/data && chown -R bot:bot /app
|
|
||||||
|
|
||||||
USER bot
|
|
||||||
|
|
||||||
CMD ["sh", "-c", "python migrate.py up && python main.py"]
|
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
# Telegram Autoshop Bot by Djimbo
|
# Telegram Autoshop Bot
|
||||||
|
|
||||||
Telegram bot на `aiogram 3` для магазина с категориями, позициями, товарами, покупками, пополнениями и админкой.
|
Telegram bot на `aiogram 3` для магазина с категориями, позициями, товарами, покупками, пополнениями и админкой.
|
||||||
|
|
||||||
## Быстрый запуск
|
## Запуск через .bat (Windows)
|
||||||
|
|
||||||
|
Запусти `start.bat` двойным кликом. Скрипт автоматически:
|
||||||
|
|
||||||
|
1. Создаст виртуальное окружение `.venv`
|
||||||
|
2. Установит зависимости из `requirements.txt`
|
||||||
|
3. Создаст `.env` из примера (если ещё нет)
|
||||||
|
4. Запустит бота
|
||||||
|
|
||||||
|
## Запуск через командную строку (вручную)
|
||||||
|
|
||||||
1. Установи Python `3.11+`.
|
1. Установи Python `3.11+`.
|
||||||
2. Поставь зависимости:
|
2. Поставь зависимости:
|
||||||
@@ -31,11 +40,3 @@ BOT_ADMIN_IDS=
|
|||||||
```bash
|
```bash
|
||||||
python3 main.py
|
python3 main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Контейнер сам применяет миграции перед запуском.
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
services:
|
|
||||||
bot:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
restart: unless-stopped
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
volumes:
|
|
||||||
- ./tgbot/data:/app/tgbot/data
|
|
||||||
stop_grace_period: 30s
|
|
||||||
@@ -9,7 +9,13 @@ from aiogram.client.default import DefaultBotProperties
|
|||||||
from aiogram.client.session.aiohttp import AiohttpSession
|
from aiogram.client.session.aiohttp import AiohttpSession
|
||||||
from aiogram.enums import ParseMode
|
from aiogram.enums import ParseMode
|
||||||
|
|
||||||
from tgbot.data.config import BOT_DATABASE_EXPORT, BOT_SCHEDULER, BOT_TOKEN, get_admins, validate_bot_config
|
from tgbot.data.config import (
|
||||||
|
BOT_DATABASE_EXPORT,
|
||||||
|
BOT_SCHEDULER,
|
||||||
|
BOT_TOKEN,
|
||||||
|
get_admins,
|
||||||
|
validate_bot_config,
|
||||||
|
)
|
||||||
from tgbot.database.core import close_database
|
from tgbot.database.core import close_database
|
||||||
from tgbot.database.repository import prepare_database
|
from tgbot.database.repository import prepare_database
|
||||||
from tgbot.middlewares import register_all_middlewares
|
from tgbot.middlewares import register_all_middlewares
|
||||||
@@ -141,9 +147,7 @@ async def main():
|
|||||||
bot = Bot(
|
bot = Bot(
|
||||||
token=BOT_TOKEN,
|
token=BOT_TOKEN,
|
||||||
session=session,
|
session=session,
|
||||||
default=DefaultBotProperties(
|
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
||||||
parse_mode=ParseMode.HTML
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
register_all_middlewares(dp)
|
register_all_middlewares(dp)
|
||||||
@@ -160,8 +164,10 @@ async def main():
|
|||||||
|
|
||||||
bot_info = await bot.get_me()
|
bot_info = await bot.get_me()
|
||||||
bot_logger.info("Бот запущен: @%s", bot_info.username)
|
bot_logger.info("Бот запущен: @%s", bot_info.username)
|
||||||
print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Бот запущен - @{bot_info.username} ~~~~~")
|
print(
|
||||||
print(colorama.Fore.LIGHTBLUE_EX + "~~~~~ TG developer - @djimbox ~~~~~")
|
colorama.Fore.LIGHTYELLOW_EX
|
||||||
|
+ f"~~~~~ Бот запущен - @{bot_info.username} ~~~~~"
|
||||||
|
)
|
||||||
print(colorama.Fore.RESET)
|
print(colorama.Fore.RESET)
|
||||||
|
|
||||||
if len(get_admins()) == 0:
|
if len(get_admins()) == 0:
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
powershell -ExecutionPolicy Bypass -File "%~dp0start.ps1"
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
$Host.UI.RawUI.WindowTitle = "AutoShop Bot"
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
Set-Location $ScriptDir
|
||||||
|
|
||||||
|
# Проверка Python
|
||||||
|
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "[ERROR] Python не найден. Установи Python 3.11+ с https://python.org" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Создание .venv если нет
|
||||||
|
if (-not (Test-Path ".venv")) {
|
||||||
|
Write-Host "[INFO] Создание виртуального окружения..."
|
||||||
|
python -m venv .venv
|
||||||
|
}
|
||||||
|
|
||||||
|
# Активация
|
||||||
|
& ".venv\Scripts\Activate.ps1"
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
Write-Host "[INFO] Установка зависимостей..."
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
cls
|
||||||
|
|
||||||
|
# Создание .env если нет
|
||||||
|
if (-not (Test-Path ".env")) {
|
||||||
|
Write-Host "[INFO] Создание .env из примера..."
|
||||||
|
Copy-Item ".env.example" ".env"
|
||||||
|
Write-Host "[!] Заполни BOT_TOKEN и BOT_ADMIN_IDS в файле .env" -ForegroundColor Yellow
|
||||||
|
Write-Host "[!] Открываю .env для редактирования..." -ForegroundColor Yellow
|
||||||
|
notepad .env
|
||||||
|
Read-Host "Нажми Enter после сохранения .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
cls
|
||||||
|
|
||||||
|
# Проверка BOT_TOKEN
|
||||||
|
$envContent = Get-Content ".env" -Raw
|
||||||
|
if ($envContent -notmatch "(?m)^BOT_TOKEN=\S+") {
|
||||||
|
Write-Host "[ERROR] BOT_TOKEN не заполнен в .env" -ForegroundColor Red
|
||||||
|
Write-Host "[!] Открываю .env для редактирования..." -ForegroundColor Yellow
|
||||||
|
notepad .env
|
||||||
|
Read-Host "Нажми Enter после сохранения .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
cls
|
||||||
|
|
||||||
|
# Запуск бота
|
||||||
|
Write-Host "[INFO] Запуск бота..." -ForegroundColor Green
|
||||||
|
python main.py
|
||||||
+1
-20
@@ -168,23 +168,4 @@ def validate_bot_config() -> None:
|
|||||||
def get_text_desc() -> str:
|
def get_text_desc() -> str:
|
||||||
from tgbot.utils.const_functions import ded
|
from tgbot.utils.const_functions import ded
|
||||||
|
|
||||||
return ded(f"""
|
return ded(f"""<b>♻️ Версия бота: <code>{BOT_VERSION}</code>""").strip()
|
||||||
<b>♻️ Версия бота: <code>{BOT_VERSION}</code>
|
|
||||||
👑 Разработчик бота - @djimbox
|
|
||||||
🍩 Донат разработчику: <a href='https://t.me/send?start=IV8bjPKhYkYJ'>Click me</a>
|
|
||||||
🤖 Новости и обновления: <a href='https://t.me/DJIMBO_SHOP'>Click me</a>
|
|
||||||
🔗 Тема с ботом [LOLZ]: <a href='https://lolz.guru/threads/1888814'>Click me</a></b>
|
|
||||||
""").strip()
|
|
||||||
|
|
||||||
|
|
||||||
# Получение варнинг-текста
|
|
||||||
def get_text_warning() -> str:
|
|
||||||
from tgbot.utils.const_functions import ded
|
|
||||||
|
|
||||||
return ded(f"""
|
|
||||||
❗️ Если вы видите данное сообщение, значит <u>Администратор</u> бота не изменил текст установленный
|
|
||||||
по умолчанию. Скорее всего данный бот занимается <u>скамом, обманом</u> или другими схожими действиями.
|
|
||||||
|
|
||||||
❗️ Если вас обманули, не надо писать <b><u>РАЗРАБОТЧИКУ</u></b> бота, он ничем не сможет помочь. Бот является
|
|
||||||
открытым и распространяется публично. Абсолютно любой желающий мог его запустить.
|
|
||||||
""")
|
|
||||||
|
|||||||
@@ -6,7 +6,29 @@ from .db_purchases import PurchaseModel, Purchasesx
|
|||||||
from .db_refill import RefillModel, Refillx
|
from .db_refill import RefillModel, Refillx
|
||||||
from .db_settings import SettingsModel, Settingsx
|
from .db_settings import SettingsModel, Settingsx
|
||||||
from .db_users import UserModel, UsersRepository, Userx
|
from .db_users import UserModel, UsersRepository, Userx
|
||||||
from .entities import Category, Item, Payments, Position, Purchase, Refill, Settings, User
|
|
||||||
|
from .db_referral import (
|
||||||
|
ReferralModel,
|
||||||
|
ReferralTransactionModel,
|
||||||
|
ReferralWithdrawalModel,
|
||||||
|
Referralx,
|
||||||
|
ReferralTransactionx,
|
||||||
|
ReferralWithdrawalx,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .entities import (
|
||||||
|
Category,
|
||||||
|
Item,
|
||||||
|
Payments,
|
||||||
|
Position,
|
||||||
|
Purchase,
|
||||||
|
Refill,
|
||||||
|
Settings,
|
||||||
|
User,
|
||||||
|
Referral,
|
||||||
|
ReferralTransaction,
|
||||||
|
ReferralWithdrawal,
|
||||||
|
)
|
||||||
|
|
||||||
ModelCategory = Category
|
ModelCategory = Category
|
||||||
ModelItem = Item
|
ModelItem = Item
|
||||||
@@ -18,3 +40,7 @@ ModelSettings = Settings
|
|||||||
ModelUser = User
|
ModelUser = User
|
||||||
ModelUsers = User
|
ModelUsers = User
|
||||||
SettingsRepository = Settingsx
|
SettingsRepository = Settingsx
|
||||||
|
|
||||||
|
ModelReferral = Referral
|
||||||
|
ModelReferralTransaction = ReferralTransaction
|
||||||
|
ModelReferralWithdrawal = ReferralWithdrawal
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional
|
|||||||
|
|
||||||
from sqlalchemy import Integer, String, Float
|
from sqlalchemy import Integer, String, Float
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
from tgbot.database.core import Base
|
from tgbot.database.core import Base
|
||||||
from tgbot.database.entities import Payments
|
from tgbot.database.entities import Payments
|
||||||
@@ -22,11 +23,81 @@ class PaymentsModel(Base):
|
|||||||
String(16), nullable=False, default="False"
|
String(16), nullable=False, default="False"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Оплата через Lolzteam
|
||||||
|
lolzteam_token: Mapped[str] = mapped_column(String, nullable=False, default="None")
|
||||||
|
lolzteam_merchant_id: Mapped[Optional[int]] = mapped_column(
|
||||||
|
Integer, nullable=True, default=None
|
||||||
|
)
|
||||||
|
status_lolzteam: Mapped[str] = mapped_column(
|
||||||
|
String(16), nullable=False, default="False"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ModelBase = Payments
|
ModelBase = Payments
|
||||||
BaseModel = Payments
|
BaseModel = Payments
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_payments_schema(conn: AsyncConnection) -> None:
|
||||||
|
columns = (
|
||||||
|
await conn.exec_driver_sql("PRAGMA table_info(storage_payments)")
|
||||||
|
).mappings().all()
|
||||||
|
|
||||||
|
if not columns:
|
||||||
|
return
|
||||||
|
|
||||||
|
merchant_column = next(
|
||||||
|
(column for column in columns if column["name"] == "lolzteam_merchant_id"),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if merchant_column is None or merchant_column["notnull"] == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_payments RENAME TO storage_payments__legacy"
|
||||||
|
)
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE storage_payments (
|
||||||
|
id INTEGER NOT NULL,
|
||||||
|
cryptobot_token VARCHAR NOT NULL,
|
||||||
|
stars_course FLOAT NOT NULL,
|
||||||
|
status_cryptobot VARCHAR(16) NOT NULL,
|
||||||
|
status_stars VARCHAR(16) NOT NULL,
|
||||||
|
lolzteam_token VARCHAR NOT NULL,
|
||||||
|
lolzteam_merchant_id INTEGER,
|
||||||
|
status_lolzteam VARCHAR(16) NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"""
|
||||||
|
INSERT INTO storage_payments (
|
||||||
|
id,
|
||||||
|
cryptobot_token,
|
||||||
|
stars_course,
|
||||||
|
status_cryptobot,
|
||||||
|
status_stars,
|
||||||
|
lolzteam_token,
|
||||||
|
lolzteam_merchant_id,
|
||||||
|
status_lolzteam
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
cryptobot_token,
|
||||||
|
stars_course,
|
||||||
|
status_cryptobot,
|
||||||
|
status_stars,
|
||||||
|
lolzteam_token,
|
||||||
|
lolzteam_merchant_id,
|
||||||
|
status_lolzteam
|
||||||
|
FROM storage_payments__legacy
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
await conn.exec_driver_sql("DROP TABLE storage_payments__legacy")
|
||||||
|
|
||||||
|
|
||||||
class Paymentsx(BaseRepository[PaymentsModel, Payments]):
|
class Paymentsx(BaseRepository[PaymentsModel, Payments]):
|
||||||
# Подключение модели платежных настроек
|
# Подключение модели платежных настроек
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -0,0 +1,728 @@
|
|||||||
|
# - *- coding: utf- 8 - *-
|
||||||
|
from math import isfinite
|
||||||
|
import re
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Float, Integer, String, UniqueConstraint, text
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional, Tuple, Union
|
||||||
|
|
||||||
|
from tgbot.database.core import Base, session_factory
|
||||||
|
from tgbot.database.entities import Referral, ReferralTransaction, ReferralWithdrawal
|
||||||
|
from tgbot.database.repository import BaseRepository
|
||||||
|
from tgbot.utils.const_functions import get_unix
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_referral_withdrawal_recipient(
|
||||||
|
withdrawal_method: str, recipient: str
|
||||||
|
) -> bool:
|
||||||
|
if withdrawal_method == "Cryptobot":
|
||||||
|
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
|
||||||
|
|
||||||
|
if withdrawal_method == "Lolzteam":
|
||||||
|
parsed_url = urlparse(recipient)
|
||||||
|
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
|
||||||
|
"zelenka.guru",
|
||||||
|
"lolz.team",
|
||||||
|
"lolz.live",
|
||||||
|
"lolz.guru",
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_referrals_schema(conn: AsyncConnection) -> None:
|
||||||
|
user_columns_result = await conn.exec_driver_sql("PRAGMA table_info(storage_users)")
|
||||||
|
user_columns = {column[1] for column in user_columns_result.fetchall()}
|
||||||
|
|
||||||
|
# Обновление полей пользователя
|
||||||
|
if user_columns:
|
||||||
|
if "user_referrer_id" not in user_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_users ADD COLUMN user_referrer_id BIGINT"
|
||||||
|
)
|
||||||
|
if "user_referral_balance" not in user_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_users "
|
||||||
|
"ADD COLUMN user_referral_balance FLOAT NOT NULL DEFAULT 0"
|
||||||
|
)
|
||||||
|
if "user_referral_hold" not in user_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_users "
|
||||||
|
"ADD COLUMN user_referral_hold FLOAT NOT NULL DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_storage_users_user_referrer_id "
|
||||||
|
"ON storage_users (user_referrer_id)"
|
||||||
|
)
|
||||||
|
|
||||||
|
settings_columns_result = await conn.exec_driver_sql(
|
||||||
|
"PRAGMA table_info(storage_settings)"
|
||||||
|
)
|
||||||
|
settings_columns = {column[1] for column in settings_columns_result.fetchall()}
|
||||||
|
|
||||||
|
# Обновление полей настроек
|
||||||
|
if settings_columns:
|
||||||
|
if "status_referral" not in settings_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_settings "
|
||||||
|
"ADD COLUMN status_referral VARCHAR(16) NOT NULL DEFAULT 'False'"
|
||||||
|
)
|
||||||
|
if "referral_bonus_rub" not in settings_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_settings "
|
||||||
|
"ADD COLUMN referral_bonus_rub FLOAT NOT NULL DEFAULT 0"
|
||||||
|
)
|
||||||
|
if "referral_refill_percent" not in settings_columns:
|
||||||
|
await conn.exec_driver_sql(
|
||||||
|
"ALTER TABLE storage_settings "
|
||||||
|
"ADD COLUMN referral_refill_percent FLOAT NOT NULL DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferralModel(Base):
|
||||||
|
__tablename__ = "storage_referrals"
|
||||||
|
|
||||||
|
increment: Mapped[int] = mapped_column(
|
||||||
|
Integer, primary_key=True, autoincrement=True
|
||||||
|
)
|
||||||
|
referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||||
|
referral_id: Mapped[int] = mapped_column(
|
||||||
|
BigInteger, nullable=False, unique=True, index=True
|
||||||
|
)
|
||||||
|
referral_unix: Mapped[int] = mapped_column(
|
||||||
|
Integer, nullable=False, default=get_unix
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferralTransactionModel(Base):
|
||||||
|
__tablename__ = "storage_referral_transactions"
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint(
|
||||||
|
"user_id",
|
||||||
|
"transaction_type",
|
||||||
|
"source_id",
|
||||||
|
name="uq_referral_transaction_source",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
increment: Mapped[int] = mapped_column(
|
||||||
|
Integer, primary_key=True, autoincrement=True
|
||||||
|
)
|
||||||
|
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||||
|
related_user_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
||||||
|
|
||||||
|
transaction_type: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||||
|
source_id: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||||
|
amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||||
|
transaction_unix: Mapped[int] = mapped_column(
|
||||||
|
Integer, nullable=False, default=get_unix
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferralWithdrawalModel(Base):
|
||||||
|
__tablename__ = "storage_referral_withdrawals"
|
||||||
|
|
||||||
|
increment: Mapped[int] = mapped_column(
|
||||||
|
Integer, primary_key=True, autoincrement=True
|
||||||
|
)
|
||||||
|
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||||
|
withdrawal_amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||||
|
withdrawal_method: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||||
|
withdrawal_recipient: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
|
withdrawal_status: Mapped[str] = mapped_column(
|
||||||
|
String(32), nullable=False, default="pending"
|
||||||
|
)
|
||||||
|
withdrawal_unix: Mapped[int] = mapped_column(
|
||||||
|
Integer, nullable=False, default=get_unix
|
||||||
|
)
|
||||||
|
|
||||||
|
processed_unix: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||||
|
processed_admin_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Referralx(BaseRepository[ReferralModel, Referral]):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.table_model = ReferralModel
|
||||||
|
self.entity_model = Referral
|
||||||
|
self.storage_name = ReferralModel.__tablename__
|
||||||
|
|
||||||
|
async def add(self, referrer_id: int, referral_id: int) -> Referral:
|
||||||
|
return await self._insert(
|
||||||
|
referrer_id=referrer_id,
|
||||||
|
referral_id=referral_id,
|
||||||
|
referral_unix=get_unix(),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def register_referral(self, referral_id: int, referrer_id: int) -> str:
|
||||||
|
if referral_id == referrer_id:
|
||||||
|
return "SELF_REFERRAL"
|
||||||
|
|
||||||
|
async with session_factory() as session:
|
||||||
|
try:
|
||||||
|
await session.execute(text("BEGIN IMMEDIATE"))
|
||||||
|
|
||||||
|
settings_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT status_referral, referral_bonus_rub "
|
||||||
|
"FROM storage_settings WHERE id = 1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
settings = settings_result.mappings().first()
|
||||||
|
|
||||||
|
if settings is None or settings["status_referral"] != "True":
|
||||||
|
await session.rollback()
|
||||||
|
return "DISABLED"
|
||||||
|
|
||||||
|
referrer_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_referral_balance FROM storage_users "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": referrer_id},
|
||||||
|
)
|
||||||
|
referrer = referrer_result.mappings().first()
|
||||||
|
|
||||||
|
if referrer is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "REFERRER_NOT_FOUND"
|
||||||
|
|
||||||
|
referral_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_balance, user_referrer_id "
|
||||||
|
"FROM storage_users WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": referral_id},
|
||||||
|
)
|
||||||
|
referral = referral_result.mappings().first()
|
||||||
|
|
||||||
|
if referral is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "REFERRAL_NOT_FOUND"
|
||||||
|
|
||||||
|
existing_referral_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT increment FROM storage_referrals "
|
||||||
|
"WHERE referral_id = :user_id LIMIT 1"
|
||||||
|
),
|
||||||
|
{"user_id": referral_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
referral["user_referrer_id"] is not None
|
||||||
|
or existing_referral_result.mappings().first() is not None
|
||||||
|
):
|
||||||
|
await session.rollback()
|
||||||
|
return "ALREADY"
|
||||||
|
|
||||||
|
bonus = round(float(settings["referral_bonus_rub"]), 2)
|
||||||
|
source_id = f"registration:{referral_id}"
|
||||||
|
now_unix = get_unix()
|
||||||
|
|
||||||
|
# Приглашённому — бонус на основной баланс.
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referrals "
|
||||||
|
"(referrer_id, referral_id, referral_unix) "
|
||||||
|
"VALUES (:referrer_id, :referral_id, :referral_unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"referrer_id": referrer_id,
|
||||||
|
"referral_id": referral_id,
|
||||||
|
"referral_unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referrer_id = :referrer_id, "
|
||||||
|
"user_balance = :balance "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"referrer_id": referrer_id,
|
||||||
|
"balance": round(float(referral["user_balance"]) + bonus, 2),
|
||||||
|
"user_id": referral_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Рефереру — бонус на реферальный баланс.
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referral_balance = :balance "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"balance": round(
|
||||||
|
float(referrer["user_referral_balance"]) + bonus, 2
|
||||||
|
),
|
||||||
|
"user_id": referrer_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": referral_id,
|
||||||
|
"related_user_id": referrer_id,
|
||||||
|
"transaction_type": "registration_bonus_main",
|
||||||
|
"source_id": source_id,
|
||||||
|
"amount": bonus,
|
||||||
|
"transaction_unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": referrer_id,
|
||||||
|
"related_user_id": referral_id,
|
||||||
|
"transaction_type": "registration_bonus_referral",
|
||||||
|
"source_id": source_id,
|
||||||
|
"amount": bonus,
|
||||||
|
"transaction_unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
async def transfer_to_main_balance(self, user_id: int, amount: float) -> str:
|
||||||
|
try:
|
||||||
|
transfer_amount = round(float(amount), 2)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return "INVALID_AMOUNT"
|
||||||
|
|
||||||
|
if not isfinite(transfer_amount) or transfer_amount <= 0:
|
||||||
|
return "INVALID_AMOUNT"
|
||||||
|
|
||||||
|
async with session_factory() as session:
|
||||||
|
try:
|
||||||
|
await session.execute(text("BEGIN IMMEDIATE"))
|
||||||
|
|
||||||
|
user_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_balance, user_referral_balance, user_referral_hold "
|
||||||
|
"FROM storage_users WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": user_id},
|
||||||
|
)
|
||||||
|
get_user = user_result.mappings().first()
|
||||||
|
|
||||||
|
if get_user is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "USER_NOT_FOUND"
|
||||||
|
|
||||||
|
available_balance = round(
|
||||||
|
float(get_user["user_referral_balance"])
|
||||||
|
- float(get_user["user_referral_hold"]),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if transfer_amount > available_balance:
|
||||||
|
await session.rollback()
|
||||||
|
return "INSUFFICIENT_FUNDS"
|
||||||
|
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_balance = :user_balance, "
|
||||||
|
"user_referral_balance = :referral_balance "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_balance": round(
|
||||||
|
float(get_user["user_balance"]) + transfer_amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"referral_balance": round(
|
||||||
|
float(get_user["user_referral_balance"]) - transfer_amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"transaction_type": "transfer_to_main",
|
||||||
|
"source_id": f"transfer:{user_id}:{uuid4().hex}",
|
||||||
|
"amount": transfer_amount,
|
||||||
|
"transaction_unix": get_unix(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
|
||||||
|
class ReferralTransactionx(
|
||||||
|
BaseRepository[ReferralTransactionModel, ReferralTransaction]
|
||||||
|
):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.table_model = ReferralTransactionModel
|
||||||
|
self.entity_model = ReferralTransaction
|
||||||
|
self.storage_name = ReferralTransactionModel.__tablename__
|
||||||
|
|
||||||
|
async def get_refill_reward(self, source_id: str) -> Optional[ReferralTransaction]:
|
||||||
|
return await self.get(
|
||||||
|
transaction_type="refill_percent",
|
||||||
|
source_id=source_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def add(
|
||||||
|
self,
|
||||||
|
user_id: int,
|
||||||
|
transaction_type: str,
|
||||||
|
source_id: str,
|
||||||
|
amount: float,
|
||||||
|
related_user_id: Optional[int] = None,
|
||||||
|
) -> ReferralTransaction:
|
||||||
|
return await self._insert(
|
||||||
|
user_id=user_id,
|
||||||
|
related_user_id=related_user_id,
|
||||||
|
transaction_type=transaction_type,
|
||||||
|
source_id=source_id,
|
||||||
|
amount=round(amount, 2),
|
||||||
|
transaction_unix=get_unix(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferralWithdrawalx(BaseRepository[ReferralWithdrawalModel, ReferralWithdrawal]):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.table_model = ReferralWithdrawalModel
|
||||||
|
self.entity_model = ReferralWithdrawal
|
||||||
|
self.storage_name = ReferralWithdrawalModel.__tablename__
|
||||||
|
|
||||||
|
async def create_pending(
|
||||||
|
self,
|
||||||
|
user_id: int,
|
||||||
|
withdrawal_amount: float,
|
||||||
|
withdrawal_method: str,
|
||||||
|
withdrawal_recipient: str,
|
||||||
|
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||||
|
try:
|
||||||
|
amount = round(float(withdrawal_amount), 2)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return "INVALID_AMOUNT", None
|
||||||
|
|
||||||
|
recipient = str(withdrawal_recipient).strip()
|
||||||
|
|
||||||
|
if not isfinite(amount) or amount <= 0:
|
||||||
|
return "INVALID_AMOUNT", None
|
||||||
|
|
||||||
|
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||||
|
return "INVALID_METHOD", None
|
||||||
|
|
||||||
|
if not is_valid_referral_withdrawal_recipient(withdrawal_method, recipient):
|
||||||
|
return "INVALID_RECIPIENT", None
|
||||||
|
|
||||||
|
withdrawal_id: Optional[int] = None
|
||||||
|
|
||||||
|
async with session_factory() as session:
|
||||||
|
try:
|
||||||
|
await session.execute(text("BEGIN IMMEDIATE"))
|
||||||
|
|
||||||
|
user_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_referral_balance, user_referral_hold "
|
||||||
|
"FROM storage_users WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": user_id},
|
||||||
|
)
|
||||||
|
get_user = user_result.mappings().first()
|
||||||
|
|
||||||
|
if get_user is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "USER_NOT_FOUND", None
|
||||||
|
|
||||||
|
available_balance = round(
|
||||||
|
float(get_user["user_referral_balance"])
|
||||||
|
- float(get_user["user_referral_hold"]),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if amount > available_balance:
|
||||||
|
await session.rollback()
|
||||||
|
return "INSUFFICIENT_FUNDS", None
|
||||||
|
|
||||||
|
now_unix = get_unix()
|
||||||
|
withdrawal_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_withdrawals "
|
||||||
|
"(user_id, withdrawal_amount, withdrawal_method, withdrawal_recipient, withdrawal_status, withdrawal_unix) "
|
||||||
|
"VALUES (:user_id, :amount, :method, :recipient, 'pending', :unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": amount,
|
||||||
|
"method": withdrawal_method,
|
||||||
|
"recipient": recipient,
|
||||||
|
"unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
withdrawal_id = int(withdrawal_result.lastrowid)
|
||||||
|
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referral_hold = :hold "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"hold": round(
|
||||||
|
float(get_user["user_referral_hold"]) + amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"transaction_type": "withdrawal_hold",
|
||||||
|
"source_id": f"withdrawal:{withdrawal_id}",
|
||||||
|
"amount": amount,
|
||||||
|
"unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "ok", await self.get(increment=withdrawal_id)
|
||||||
|
|
||||||
|
async def complete(
|
||||||
|
self, withdrawal_id: int, admin_id: int
|
||||||
|
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||||
|
async with session_factory() as session:
|
||||||
|
try:
|
||||||
|
await session.execute(text("BEGIN IMMEDIATE"))
|
||||||
|
|
||||||
|
withdrawal_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_id, withdrawal_amount, withdrawal_status "
|
||||||
|
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
|
||||||
|
),
|
||||||
|
{"withdrawal_id": withdrawal_id},
|
||||||
|
)
|
||||||
|
withdrawal = withdrawal_result.mappings().first()
|
||||||
|
|
||||||
|
if withdrawal is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "NOT_FOUND", None
|
||||||
|
if withdrawal["withdrawal_status"] != "pending":
|
||||||
|
await session.rollback()
|
||||||
|
return "ALREADY_PROCESSED", None
|
||||||
|
|
||||||
|
user_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_referral_balance, user_referral_hold "
|
||||||
|
"FROM storage_users WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": withdrawal["user_id"]},
|
||||||
|
)
|
||||||
|
get_user = user_result.mappings().first()
|
||||||
|
amount = float(withdrawal["withdrawal_amount"])
|
||||||
|
|
||||||
|
if (
|
||||||
|
get_user is None
|
||||||
|
or float(get_user["user_referral_balance"]) < amount
|
||||||
|
or float(get_user["user_referral_hold"]) < amount
|
||||||
|
):
|
||||||
|
await session.rollback()
|
||||||
|
return "BALANCE_ERROR", None
|
||||||
|
|
||||||
|
now_unix = get_unix()
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referral_balance = :balance, user_referral_hold = :hold "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"balance": round(
|
||||||
|
float(get_user["user_referral_balance"]) - amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"hold": round(
|
||||||
|
float(get_user["user_referral_hold"]) - amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"user_id": withdrawal["user_id"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_referral_withdrawals "
|
||||||
|
"SET withdrawal_status = 'completed', processed_unix = :unix, processed_admin_id = :admin_id "
|
||||||
|
"WHERE increment = :withdrawal_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"unix": now_unix,
|
||||||
|
"admin_id": admin_id,
|
||||||
|
"withdrawal_id": withdrawal_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": withdrawal["user_id"],
|
||||||
|
"transaction_type": "withdrawal_completed",
|
||||||
|
"source_id": f"withdrawal:{withdrawal_id}",
|
||||||
|
"amount": amount,
|
||||||
|
"unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "ok", await self.get(increment=withdrawal_id)
|
||||||
|
|
||||||
|
async def reject(
|
||||||
|
self, withdrawal_id: int, admin_id: int
|
||||||
|
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||||
|
async with session_factory() as session:
|
||||||
|
try:
|
||||||
|
await session.execute(text("BEGIN IMMEDIATE"))
|
||||||
|
|
||||||
|
withdrawal_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_id, withdrawal_amount, withdrawal_status "
|
||||||
|
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
|
||||||
|
),
|
||||||
|
{"withdrawal_id": withdrawal_id},
|
||||||
|
)
|
||||||
|
withdrawal = withdrawal_result.mappings().first()
|
||||||
|
|
||||||
|
if withdrawal is None:
|
||||||
|
await session.rollback()
|
||||||
|
return "NOT_FOUND", None
|
||||||
|
if withdrawal["withdrawal_status"] != "pending":
|
||||||
|
await session.rollback()
|
||||||
|
return "ALREADY_PROCESSED", None
|
||||||
|
|
||||||
|
user_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_referral_hold FROM storage_users "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": withdrawal["user_id"]},
|
||||||
|
)
|
||||||
|
get_user = user_result.mappings().first()
|
||||||
|
amount = float(withdrawal["withdrawal_amount"])
|
||||||
|
|
||||||
|
if get_user is None or float(get_user["user_referral_hold"]) < amount:
|
||||||
|
await session.rollback()
|
||||||
|
return "BALANCE_ERROR", None
|
||||||
|
|
||||||
|
now_unix = get_unix()
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referral_hold = :hold WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"hold": round(
|
||||||
|
float(get_user["user_referral_hold"]) - amount,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"user_id": withdrawal["user_id"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_referral_withdrawals "
|
||||||
|
"SET withdrawal_status = 'rejected', processed_unix = :unix, processed_admin_id = :admin_id "
|
||||||
|
"WHERE increment = :withdrawal_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"unix": now_unix,
|
||||||
|
"admin_id": admin_id,
|
||||||
|
"withdrawal_id": withdrawal_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": withdrawal["user_id"],
|
||||||
|
"transaction_type": "withdrawal_rejected",
|
||||||
|
"source_id": f"withdrawal:{withdrawal_id}",
|
||||||
|
"amount": amount,
|
||||||
|
"unix": now_unix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return "ok", await self.get(increment=withdrawal_id)
|
||||||
|
|
||||||
|
async def add(
|
||||||
|
self,
|
||||||
|
user_id: int,
|
||||||
|
withdrawal_amount: float,
|
||||||
|
withdrawal_method: str,
|
||||||
|
withdrawal_recipient: str,
|
||||||
|
) -> ReferralWithdrawal:
|
||||||
|
return await self._insert(
|
||||||
|
user_id=user_id,
|
||||||
|
withdrawal_amount=round(withdrawal_amount, 2),
|
||||||
|
withdrawal_method=withdrawal_method,
|
||||||
|
withdrawal_recipient=withdrawal_recipient,
|
||||||
|
withdrawal_status="pending",
|
||||||
|
withdrawal_unix=get_unix(),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
where: Optional[Union[Dict[str, Any], int]] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> int:
|
||||||
|
if isinstance(where, int):
|
||||||
|
where = {"increment": where}
|
||||||
|
|
||||||
|
return await self._update(where=where, **kwargs)
|
||||||
@@ -90,7 +90,7 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
|||||||
user_result = await session.execute(
|
user_result = await session.execute(
|
||||||
text(
|
text(
|
||||||
"""
|
"""
|
||||||
SELECT user_balance, user_refill
|
SELECT user_balance, user_refill, user_referrer_id
|
||||||
FROM storage_users
|
FROM storage_users
|
||||||
WHERE user_id = :user_id
|
WHERE user_id = :user_id
|
||||||
"""
|
"""
|
||||||
@@ -103,6 +103,14 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
|||||||
await session.rollback()
|
await session.rollback()
|
||||||
return "USER_NOT_FOUND"
|
return "USER_NOT_FOUND"
|
||||||
|
|
||||||
|
settings_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT status_referral, referral_refill_percent "
|
||||||
|
"FROM storage_settings WHERE id = 1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
get_settings = settings_result.mappings().first()
|
||||||
|
|
||||||
new_balance = round(float(get_user["user_balance"]) + float(pay_amount), 2)
|
new_balance = round(float(get_user["user_balance"]) + float(pay_amount), 2)
|
||||||
new_refill = round(float(get_user["user_refill"]) + float(pay_amount), 2)
|
new_refill = round(float(get_user["user_refill"]) + float(pay_amount), 2)
|
||||||
|
|
||||||
@@ -138,6 +146,66 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
|||||||
),
|
),
|
||||||
{"balance": new_balance, "refill": new_refill, "user_id": user_id},
|
{"balance": new_balance, "refill": new_refill, "user_id": user_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
referrer_id = get_user["user_referrer_id"]
|
||||||
|
referral_reward = 0.0
|
||||||
|
|
||||||
|
if (
|
||||||
|
get_settings is not None
|
||||||
|
and get_settings["status_referral"] == "True"
|
||||||
|
and referrer_id is not None
|
||||||
|
and referrer_id != user_id
|
||||||
|
):
|
||||||
|
referral_reward = round(
|
||||||
|
float(pay_amount)
|
||||||
|
* float(get_settings["referral_refill_percent"])
|
||||||
|
/ 100,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if referral_reward > 0:
|
||||||
|
referrer_result = await session.execute(
|
||||||
|
text(
|
||||||
|
"SELECT user_referral_balance FROM storage_users "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{"user_id": referrer_id},
|
||||||
|
)
|
||||||
|
get_referrer = referrer_result.mappings().first()
|
||||||
|
|
||||||
|
if get_referrer is not None:
|
||||||
|
source_id = f"refill:{pay_comment or pay_receipt}"
|
||||||
|
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"UPDATE storage_users "
|
||||||
|
"SET user_referral_balance = :balance "
|
||||||
|
"WHERE user_id = :user_id"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"balance": round(
|
||||||
|
float(get_referrer["user_referral_balance"])
|
||||||
|
+ referral_reward,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"user_id": referrer_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await session.execute(
|
||||||
|
text(
|
||||||
|
"INSERT INTO storage_referral_transactions "
|
||||||
|
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||||
|
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"user_id": referrer_id,
|
||||||
|
"related_user_id": user_id,
|
||||||
|
"transaction_type": "refill_percent",
|
||||||
|
"source_id": source_id,
|
||||||
|
"amount": referral_reward,
|
||||||
|
"transaction_unix": get_unix(),
|
||||||
|
},
|
||||||
|
)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# - *- coding: utf- 8 - *-
|
# - *- coding: utf- 8 - *-
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from sqlalchemy import Integer, String
|
from sqlalchemy import Integer, String, Float
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from tgbot.database.core import Base
|
from tgbot.database.core import Base
|
||||||
@@ -46,6 +46,15 @@ class SettingsModel(Base):
|
|||||||
misc_profit_week: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
misc_profit_week: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||||
misc_profit_month: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
misc_profit_month: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||||
|
|
||||||
|
# Реферальная система
|
||||||
|
referral_refill_percent: Mapped[float] = mapped_column(
|
||||||
|
Float, nullable=False, default=0
|
||||||
|
)
|
||||||
|
status_referral: Mapped[str] = mapped_column(
|
||||||
|
String(16), nullable=False, default="False"
|
||||||
|
)
|
||||||
|
referral_bonus_rub: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||||
|
|
||||||
|
|
||||||
ModelBase = Settings
|
ModelBase = Settings
|
||||||
BaseModel = Settings
|
BaseModel = Settings
|
||||||
|
|||||||
+33
-17
@@ -15,8 +15,12 @@ from tgbot.utils.const_functions import get_unix
|
|||||||
class UserModel(Base):
|
class UserModel(Base):
|
||||||
__tablename__ = "storage_users"
|
__tablename__ = "storage_users"
|
||||||
|
|
||||||
increment: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
increment: Mapped[int] = mapped_column(
|
||||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, unique=True, index=True)
|
Integer, primary_key=True, autoincrement=True
|
||||||
|
)
|
||||||
|
user_id: Mapped[int] = mapped_column(
|
||||||
|
BigInteger, nullable=False, unique=True, index=True
|
||||||
|
)
|
||||||
user_login: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
user_login: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||||
user_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
user_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||||
user_surname: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
user_surname: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||||
@@ -26,6 +30,13 @@ class UserModel(Base):
|
|||||||
user_give: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
user_give: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||||
user_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix)
|
user_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix)
|
||||||
|
|
||||||
|
# Поля для реферальной системы
|
||||||
|
user_referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=True, index=True)
|
||||||
|
user_referral_balance: Mapped[float] = mapped_column(
|
||||||
|
Float, nullable=False, default=0
|
||||||
|
)
|
||||||
|
user_referral_hold: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||||
|
|
||||||
|
|
||||||
ModelBase = User
|
ModelBase = User
|
||||||
BaseModel = User
|
BaseModel = User
|
||||||
@@ -41,31 +52,34 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
|||||||
|
|
||||||
# Добавление или обновление пользователя
|
# Добавление или обновление пользователя
|
||||||
async def add(
|
async def add(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user_login: str,
|
user_login: str,
|
||||||
user_name: str,
|
user_name: str,
|
||||||
user_surname: str = "",
|
user_surname: str = "",
|
||||||
user_fullname: str = "",
|
user_fullname: str = "",
|
||||||
) -> User:
|
) -> User:
|
||||||
return await self.upsert(
|
return await self.upsert(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
user_login=user_login,
|
user_login=user_login,
|
||||||
user_name=user_name,
|
user_name=user_name,
|
||||||
user_surname=user_surname,
|
user_surname=user_surname,
|
||||||
user_fullname=user_fullname or " ".join(filter(None, [user_name, user_surname])),
|
user_fullname=user_fullname
|
||||||
|
or " ".join(filter(None, [user_name, user_surname])),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Выполнение upsert пользователя по телеграм ID
|
# Выполнение upsert пользователя по телеграм ID
|
||||||
async def upsert(
|
async def upsert(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user_login: str,
|
user_login: str,
|
||||||
user_name: str,
|
user_name: str,
|
||||||
user_surname: str = "",
|
user_surname: str = "",
|
||||||
user_fullname: str = "",
|
user_fullname: str = "",
|
||||||
) -> User:
|
) -> User:
|
||||||
user_fullname = user_fullname or " ".join(filter(None, [user_name, user_surname]))
|
user_fullname = user_fullname or " ".join(
|
||||||
|
filter(None, [user_name, user_surname])
|
||||||
|
)
|
||||||
user_table = UserModel.__table__
|
user_table = UserModel.__table__
|
||||||
statement = insert(UserModel).values(
|
statement = insert(UserModel).values(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@@ -105,7 +119,9 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
# Обновление пользователя по ID или фильтру
|
# Обновление пользователя по ID или фильтру
|
||||||
async def update(self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs) -> int:
|
async def update(
|
||||||
|
self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs
|
||||||
|
) -> int:
|
||||||
if isinstance(where, int):
|
if isinstance(where, int):
|
||||||
where = {"user_id": where}
|
where = {"user_id": where}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# - *- coding: utf- 8 - *-
|
# - *- coding: utf- 8 - *-
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -40,6 +41,12 @@ class Payments:
|
|||||||
status_cryptobot: str
|
status_cryptobot: str
|
||||||
status_stars: str
|
status_stars: str
|
||||||
|
|
||||||
|
# Оплата через Lolzteam
|
||||||
|
lolzteam_token: str
|
||||||
|
lolzteam_merchant_id: Optional[int]
|
||||||
|
|
||||||
|
status_lolzteam: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Purchase:
|
class Purchase:
|
||||||
@@ -90,6 +97,11 @@ class Settings:
|
|||||||
misc_profit_week: int
|
misc_profit_week: int
|
||||||
misc_profit_month: int
|
misc_profit_month: int
|
||||||
|
|
||||||
|
# Реферальная система
|
||||||
|
referral_refill_percent: float
|
||||||
|
status_referral: str
|
||||||
|
referral_bonus_rub: float
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class User:
|
class User:
|
||||||
@@ -103,3 +115,40 @@ class User:
|
|||||||
user_refill: float
|
user_refill: float
|
||||||
user_give: float
|
user_give: float
|
||||||
user_unix: int
|
user_unix: int
|
||||||
|
|
||||||
|
# Поля для реферальной системы
|
||||||
|
user_referrer_id: Optional[int]
|
||||||
|
user_referral_balance: float
|
||||||
|
user_referral_hold: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Referral:
|
||||||
|
increment: int
|
||||||
|
referrer_id: int
|
||||||
|
referral_id: int
|
||||||
|
referral_unix: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReferralTransaction:
|
||||||
|
increment: int
|
||||||
|
user_id: int
|
||||||
|
related_user_id: Optional[int]
|
||||||
|
transaction_type: str
|
||||||
|
source_id: str
|
||||||
|
amount: float
|
||||||
|
transaction_unix: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReferralWithdrawal:
|
||||||
|
increment: int
|
||||||
|
user_id: int
|
||||||
|
withdrawal_amount: float
|
||||||
|
withdrawal_method: str
|
||||||
|
withdrawal_recipient: str
|
||||||
|
withdrawal_status: str
|
||||||
|
withdrawal_unix: int
|
||||||
|
processed_unix: Optional[int]
|
||||||
|
processed_admin_id: Optional[int]
|
||||||
|
|||||||
@@ -160,8 +160,15 @@ async def prepare_database() -> None:
|
|||||||
import tgbot.database.db_refill # noqa: F401
|
import tgbot.database.db_refill # noqa: F401
|
||||||
import tgbot.database.db_settings # noqa: F401
|
import tgbot.database.db_settings # noqa: F401
|
||||||
import tgbot.database.db_users # noqa: F401
|
import tgbot.database.db_users # noqa: F401
|
||||||
|
import tgbot.database.db_referral # noqa: F401
|
||||||
|
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
|
from tgbot.database.db_payments import ensure_payments_schema
|
||||||
|
from tgbot.database.db_referral import ensure_referrals_schema
|
||||||
|
|
||||||
|
await ensure_referrals_schema(conn)
|
||||||
|
await ensure_payments_schema(conn)
|
||||||
|
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
|
||||||
from tgbot.database.db_payments import Paymentsx
|
from tgbot.database.db_payments import Paymentsx
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ def close_finl() -> InlineKeyboardMarkup:
|
|||||||
|
|
||||||
|
|
||||||
# Рассылка
|
# Рассылка
|
||||||
|
def referral_withdrawal_actions_finl(withdrawal_id: int) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
keyboard.row(
|
||||||
|
ikb("✅ Выполнено", data=f"referral_withdrawal_complete:{withdrawal_id}"),
|
||||||
|
ikb("❌ Отклонить", data=f"referral_withdrawal_reject:{withdrawal_id}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
def mail_confirm_finl() -> InlineKeyboardMarkup:
|
def mail_confirm_finl() -> InlineKeyboardMarkup:
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
@@ -98,6 +109,43 @@ async def payment_cryptobot_finl() -> InlineKeyboardMarkup:
|
|||||||
return keyboard.as_markup()
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
# Управление - Lolzteam
|
||||||
|
async def payment_lolzteam_finl() -> InlineKeyboardMarkup:
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
get_payments = await Paymentsx().get()
|
||||||
|
|
||||||
|
if get_payments.lolzteam_token == "None":
|
||||||
|
assets_symbol = "➖"
|
||||||
|
else:
|
||||||
|
assets_symbol = "➕"
|
||||||
|
|
||||||
|
if get_payments.status_lolzteam == "True":
|
||||||
|
status_kb = ikb(
|
||||||
|
f"{assets_symbol} | Статус: Включено ✅",
|
||||||
|
data="payment_lolzteam_status:False",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
status_kb = ikb(
|
||||||
|
f"{assets_symbol} | Статус: Выключено ❌",
|
||||||
|
data="payment_lolzteam_status:True",
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard.row(
|
||||||
|
ikb("Информация ♻️", data="payment_lolzteam_check"),
|
||||||
|
).row(
|
||||||
|
ikb("Баланс 💰", data="payment_lolzteam_balance"),
|
||||||
|
).row(
|
||||||
|
ikb("Изменить 🖍", data="payment_lolzteam_edit"),
|
||||||
|
).row(
|
||||||
|
ikb("", data="..."),
|
||||||
|
).row(
|
||||||
|
status_kb,
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
# Управление - Звёзды
|
# Управление - Звёзды
|
||||||
async def payment_stars_finl() -> InlineKeyboardMarkup:
|
async def payment_stars_finl() -> InlineKeyboardMarkup:
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
@@ -170,6 +218,17 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
|||||||
else:
|
else:
|
||||||
method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip")
|
method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip")
|
||||||
|
|
||||||
|
# Бонус за приглашение
|
||||||
|
referral_bonus_kb = ikb(
|
||||||
|
f"{get_settings.referral_bonus_rub}₽", data="settings_edit_referral_bonus"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Доход с пополнений
|
||||||
|
deposit_percent_kb = ikb(
|
||||||
|
f"{get_settings.referral_refill_percent}%",
|
||||||
|
data="settings_edit_referral_percent",
|
||||||
|
)
|
||||||
|
|
||||||
keyboard.row(
|
keyboard.row(
|
||||||
ikb("❔ FAQ", data="..."),
|
ikb("❔ FAQ", data="..."),
|
||||||
faq_kb,
|
faq_kb,
|
||||||
@@ -185,6 +244,12 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
|||||||
).row(
|
).row(
|
||||||
ikb("🎁 Метод добавления", data="..."),
|
ikb("🎁 Метод добавления", data="..."),
|
||||||
method_prod_kb,
|
method_prod_kb,
|
||||||
|
).row(
|
||||||
|
ikb("🤝 Бонус за приглашение", data="..."),
|
||||||
|
referral_bonus_kb,
|
||||||
|
).row(
|
||||||
|
ikb("📈 Доход с пополнений", data="..."),
|
||||||
|
deposit_percent_kb,
|
||||||
)
|
)
|
||||||
|
|
||||||
return keyboard.as_markup()
|
return keyboard.as_markup()
|
||||||
@@ -199,6 +264,7 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
|||||||
status_work_kb = ikb("Включены ✅", data="settings_status_work:False")
|
status_work_kb = ikb("Включены ✅", data="settings_status_work:False")
|
||||||
status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False")
|
status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False")
|
||||||
status_refill_kb = ikb("Включены ✅", data="settings_status_refill:False")
|
status_refill_kb = ikb("Включены ✅", data="settings_status_refill:False")
|
||||||
|
status_referral_kb = ikb("Включена ✅", data="settings_status_referral:False")
|
||||||
notification_buy_kb = ikb("Включены 🔔", data="settings_notification_buy:False")
|
notification_buy_kb = ikb("Включены 🔔", data="settings_notification_buy:False")
|
||||||
notification_refill_kb = ikb(
|
notification_refill_kb = ikb(
|
||||||
"Включены 🔔", data="settings_notification_refill:False"
|
"Включены 🔔", data="settings_notification_refill:False"
|
||||||
@@ -210,6 +276,8 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
|||||||
status_work_kb = ikb("Выключены ❌", data="settings_status_work:True")
|
status_work_kb = ikb("Выключены ❌", data="settings_status_work:True")
|
||||||
if get_settings.status_refill == "False":
|
if get_settings.status_refill == "False":
|
||||||
status_refill_kb = ikb("Выключены ❌", data="settings_status_refill:True")
|
status_refill_kb = ikb("Выключены ❌", data="settings_status_refill:True")
|
||||||
|
if get_settings.status_referral == "False":
|
||||||
|
status_referral_kb = ikb("Выключена ❌", data="settings_status_referral:True")
|
||||||
if get_settings.notification_buy == "False":
|
if get_settings.notification_buy == "False":
|
||||||
notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True")
|
notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True")
|
||||||
if get_settings.notification_refill == "False":
|
if get_settings.notification_refill == "False":
|
||||||
@@ -226,6 +294,9 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
|||||||
).row(
|
).row(
|
||||||
ikb("🎁 Покупки", data="..."),
|
ikb("🎁 Покупки", data="..."),
|
||||||
status_buy_kb,
|
status_buy_kb,
|
||||||
|
).row(
|
||||||
|
ikb("🤝 Реферальная система", data="..."),
|
||||||
|
status_referral_kb,
|
||||||
).row(
|
).row(
|
||||||
ikb("📢 Увед. о покупках", data="..."),
|
ikb("📢 Увед. о покупках", data="..."),
|
||||||
notification_buy_kb,
|
notification_buy_kb,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ async def category_edit_open_finl(
|
|||||||
).row(
|
).row(
|
||||||
ikb(
|
ikb(
|
||||||
"▪️ Скопировать ссылку",
|
"▪️ Скопировать ссылку",
|
||||||
copy=f"t.me/{get_bot.username}?start=c_{category_id}",
|
copy=f"telegram.me/{get_bot.username}?start=c_{category_id}",
|
||||||
),
|
),
|
||||||
ikb("▪️ Удалить", data=f"category_edit_delete:{category_id}:{remover}"),
|
ikb("▪️ Удалить", data=f"category_edit_delete:{category_id}:{remover}"),
|
||||||
).row(
|
).row(
|
||||||
@@ -85,7 +85,7 @@ async def position_edit_open_finl(
|
|||||||
).row(
|
).row(
|
||||||
ikb(
|
ikb(
|
||||||
"▪️ Скопировать ссылку",
|
"▪️ Скопировать ссылку",
|
||||||
copy=f"t.me/{get_bot.username}?start=p_{position_id}",
|
copy=f"telegram.me/{get_bot.username}?start=p_{position_id}",
|
||||||
),
|
),
|
||||||
ikb("▪️ Удалить Позицию", data=f"position_edit_delete:{position_id}:{remover}"),
|
ikb("▪️ Удалить Позицию", data=f"position_edit_delete:{position_id}:{remover}"),
|
||||||
).row(
|
).row(
|
||||||
|
|||||||
@@ -27,7 +27,42 @@ def user_support_finl(support_login: str) -> InlineKeyboardMarkup:
|
|||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
keyboard.row(
|
keyboard.row(
|
||||||
ikb("💌 Написать в поддержку", url=f"https://t.me/{support_login}"),
|
ikb("💌 Написать в поддержку", url=f"https://telegram.me/{support_login}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def referral_menu_finl(referral_link: str) -> InlineKeyboardMarkup:
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
keyboard.row(
|
||||||
|
ikb("💸 Перевести на основной баланс", data="referral_transfer"),
|
||||||
|
).row(
|
||||||
|
ikb("📥 Вывести средства", data="referral_withdrawal"),
|
||||||
|
)
|
||||||
|
keyboard.row(ikb("▪️ Скопировать ссылку", copy=referral_link))
|
||||||
|
|
||||||
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def referral_transfer_method_finl() -> InlineKeyboardMarkup:
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
keyboard.row(ikb("🔙 Вернуться", data="referral_menu"))
|
||||||
|
|
||||||
|
return keyboard.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def referral_withdrawal_method_finl() -> InlineKeyboardMarkup:
|
||||||
|
keyboard = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
keyboard.row(
|
||||||
|
ikb("🔷 CryptoBot", data="referral_withdrawal_method:Cryptobot"),
|
||||||
|
).row(
|
||||||
|
ikb("🟢 Lolzteam", data="referral_withdrawal_method:Lolzteam"),
|
||||||
|
).row(
|
||||||
|
ikb("🔙 Вернуться", data="referral_menu"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return keyboard.as_markup()
|
return keyboard.as_markup()
|
||||||
@@ -43,6 +78,8 @@ async def refill_method_finl() -> InlineKeyboardMarkup:
|
|||||||
|
|
||||||
if get_payments.status_cryptobot == "True":
|
if get_payments.status_cryptobot == "True":
|
||||||
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
||||||
|
if get_payments.status_lolzteam == "True":
|
||||||
|
keyboard.row(ikb("🟢 Lolzteam", data="user_refill_method:Lolzteam"))
|
||||||
if get_payments.status_stars == "True":
|
if get_payments.status_stars == "True":
|
||||||
keyboard.row(ikb("⭐️ Звёзды", data="user_refill_method:Stars"))
|
keyboard.row(ikb("⭐️ Звёзды", data="user_refill_method:Stars"))
|
||||||
|
|
||||||
@@ -74,6 +111,8 @@ async def refill_method_buy_finl() -> InlineKeyboardMarkup:
|
|||||||
|
|
||||||
if get_payments.status_cryptobot == "True":
|
if get_payments.status_cryptobot == "True":
|
||||||
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
||||||
|
if get_payments.status_lolzteam == "True":
|
||||||
|
keyboard.row(ikb("🟢 Lolzteam", data="user_refill_method:Lolzteam"))
|
||||||
if get_payments.status_stars == "True":
|
if get_payments.status_stars == "True":
|
||||||
keyboard.row(ikb("⭐️ Звёзды", data="user_refill_method:Stars"))
|
keyboard.row(ikb("⭐️ Звёзды", data="user_refill_method:Stars"))
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
|
|||||||
rkb("🎁 Купить"),
|
rkb("🎁 Купить"),
|
||||||
rkb("👤 Профиль"),
|
rkb("👤 Профиль"),
|
||||||
rkb("🧮 Наличие товаров"),
|
rkb("🧮 Наличие товаров"),
|
||||||
|
).row(
|
||||||
|
rkb("🤝 Реферальная система"),
|
||||||
).row(
|
).row(
|
||||||
rkb("☎️ Поддержка"),
|
rkb("☎️ Поддержка"),
|
||||||
rkb("❔ FAQ"),
|
rkb("❔ FAQ"),
|
||||||
@@ -38,6 +40,7 @@ def payments_frep() -> ReplyKeyboardMarkup:
|
|||||||
|
|
||||||
keyboard.row(
|
keyboard.row(
|
||||||
rkb("🔷 CryptoBot"),
|
rkb("🔷 CryptoBot"),
|
||||||
|
rkb("🟢 Lolzteam"),
|
||||||
rkb("⭐️ Звёзды"),
|
rkb("⭐️ Звёзды"),
|
||||||
).row(
|
).row(
|
||||||
rkb("🔙 Главное меню"),
|
rkb("🔙 Главное меню"),
|
||||||
|
|||||||
@@ -3,16 +3,37 @@ from aiogram import BaseMiddleware
|
|||||||
from cachetools import TTLCache
|
from cachetools import TTLCache
|
||||||
|
|
||||||
from tgbot.data.config import BOT_USER_CACHE_TTL
|
from tgbot.data.config import BOT_USER_CACHE_TTL
|
||||||
from tgbot.database import Userx
|
from tgbot.database import Referralx, Settingsx, Userx
|
||||||
from tgbot.utils.const_functions import clear_html
|
from tgbot.utils.const_functions import clear_html
|
||||||
|
from tgbot.utils.misc.bot_logging import bot_logger
|
||||||
|
|
||||||
|
|
||||||
class ExistsUserMiddleware(BaseMiddleware):
|
class ExistsUserMiddleware(BaseMiddleware):
|
||||||
# Создание кеша пользователей
|
# Создание кеша пользователей
|
||||||
def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None:
|
def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None:
|
||||||
self.users = Userx()
|
self.users = Userx()
|
||||||
|
self.referrals = Referralx()
|
||||||
self.cache = TTLCache(maxsize=10_000, ttl=cache_ttl)
|
self.cache = TTLCache(maxsize=10_000, ttl=cache_ttl)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_referrer_id(event) -> int | None:
|
||||||
|
event_text = getattr(event, "text", None)
|
||||||
|
|
||||||
|
if not isinstance(event_text, str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
start_data = event_text.split(maxsplit=1)
|
||||||
|
|
||||||
|
if len(start_data) != 2 or start_data[0] != "/start":
|
||||||
|
return None
|
||||||
|
|
||||||
|
referral_data = start_data[1]
|
||||||
|
|
||||||
|
if not referral_data.startswith("r_") or not referral_data[2:].isdigit():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return int(referral_data[2:])
|
||||||
|
|
||||||
# Добавление или обновление пользователя перед handler-ом
|
# Добавление или обновление пользователя перед handler-ом
|
||||||
async def __call__(self, handler, event, data):
|
async def __call__(self, handler, event, data):
|
||||||
this_user = data.get("event_from_user")
|
this_user = data.get("event_from_user")
|
||||||
@@ -32,6 +53,12 @@ class ExistsUserMiddleware(BaseMiddleware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cached_user = self.cache.get(user_id)
|
cached_user = self.cache.get(user_id)
|
||||||
|
existing_user = None
|
||||||
|
|
||||||
|
if cached_user is None:
|
||||||
|
existing_user = await self.users.get(user_id=user_id)
|
||||||
|
|
||||||
|
is_new_user = cached_user is None and existing_user is None
|
||||||
|
|
||||||
if cached_user is None or cached_user["data"] != user_data:
|
if cached_user is None or cached_user["data"] != user_data:
|
||||||
user = await self.users.upsert(
|
user = await self.users.upsert(
|
||||||
@@ -41,10 +68,48 @@ class ExistsUserMiddleware(BaseMiddleware):
|
|||||||
user_surname=user_data[2],
|
user_surname=user_data[2],
|
||||||
user_fullname=user_data[3],
|
user_fullname=user_data[3],
|
||||||
)
|
)
|
||||||
self.cache[user_id] = {"data": user_data, "user": user}
|
|
||||||
else:
|
else:
|
||||||
user = cached_user["user"]
|
user = cached_user["user"]
|
||||||
|
|
||||||
|
referrer_id = self._get_referrer_id(event)
|
||||||
|
|
||||||
|
if is_new_user and referrer_id is not None:
|
||||||
|
registration_status = await self.referrals.register_referral(
|
||||||
|
referral_id=user_id,
|
||||||
|
referrer_id=referrer_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if registration_status == "ok":
|
||||||
|
user = await self.users.get_required(user_id=user_id)
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
bot = data.get("bot") or getattr(event, "bot", None)
|
||||||
|
|
||||||
|
if bot is not None:
|
||||||
|
notifications = (
|
||||||
|
(
|
||||||
|
referrer_id,
|
||||||
|
"<b>🤝 Новый реферал</b>\n\n"
|
||||||
|
f"▪️ Пользователь: <a href='tg://user?id={user_id}'>{user.user_name}</a>\n"
|
||||||
|
f"▪️ Начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
"<b>🤝 Реферальный бонус</b>\n\n"
|
||||||
|
f"▪️ Вам начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for recipient_id, notification_text in notifications:
|
||||||
|
try:
|
||||||
|
await bot.send_message(recipient_id, notification_text)
|
||||||
|
except Exception:
|
||||||
|
bot_logger.warning(
|
||||||
|
"Не удалось отправить уведомление о реферальной регистрации",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cache[user_id] = {"data": user_data, "user": user}
|
||||||
|
|
||||||
data["User"] = user
|
data["User"] = user
|
||||||
|
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|||||||
+46
-12
@@ -2,8 +2,19 @@
|
|||||||
from aiogram import Dispatcher
|
from aiogram import Dispatcher
|
||||||
|
|
||||||
from tgbot.routers import main_errors, main_start, main_missed
|
from tgbot.routers import main_errors, main_start, main_missed
|
||||||
from tgbot.routers.admin import admin_menu, admin_functions, admin_payments, admin_products, admin_settings
|
from tgbot.routers.admin import (
|
||||||
from tgbot.routers.user import user_menu, user_transactions, user_products
|
admin_menu,
|
||||||
|
admin_functions,
|
||||||
|
admin_payments,
|
||||||
|
admin_products,
|
||||||
|
admin_settings,
|
||||||
|
)
|
||||||
|
from tgbot.routers.user import (
|
||||||
|
user_menu,
|
||||||
|
user_referral,
|
||||||
|
user_transactions,
|
||||||
|
user_products,
|
||||||
|
)
|
||||||
from tgbot.utils.misc.bot_filters import IsAdmin, IsPrivate
|
from tgbot.utils.misc.bot_filters import IsAdmin, IsPrivate
|
||||||
|
|
||||||
|
|
||||||
@@ -23,17 +34,39 @@ def register_all_routers(dp: Dispatcher):
|
|||||||
user_products.router.callback_query.filter(IsPrivate())
|
user_products.router.callback_query.filter(IsPrivate())
|
||||||
user_transactions.router.message.filter(IsPrivate())
|
user_transactions.router.message.filter(IsPrivate())
|
||||||
user_transactions.router.callback_query.filter(IsPrivate())
|
user_transactions.router.callback_query.filter(IsPrivate())
|
||||||
|
user_referral.router.message.filter(IsPrivate())
|
||||||
|
user_referral.router.callback_query.filter(IsPrivate())
|
||||||
|
|
||||||
admin_menu.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
admin_menu.router.message.filter(
|
||||||
admin_menu.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
IsPrivate(), IsAdmin()
|
||||||
admin_functions.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
) # Работа message роутера только для админов
|
||||||
admin_functions.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
admin_menu.router.callback_query.filter(
|
||||||
admin_payments.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
IsPrivate(), IsAdmin()
|
||||||
admin_payments.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
) # Работа callback роутера только для админов
|
||||||
admin_settings.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
admin_functions.router.message.filter(
|
||||||
admin_settings.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
IsPrivate(), IsAdmin()
|
||||||
admin_products.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
) # Работа message роутера только для админов
|
||||||
admin_products.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
admin_functions.router.callback_query.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа callback роутера только для админов
|
||||||
|
admin_payments.router.message.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа message роутера только для админов
|
||||||
|
admin_payments.router.callback_query.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа callback роутера только для админов
|
||||||
|
admin_settings.router.message.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа message роутера только для админов
|
||||||
|
admin_settings.router.callback_query.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа callback роутера только для админов
|
||||||
|
admin_products.router.message.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа message роутера только для админов
|
||||||
|
admin_products.router.callback_query.filter(
|
||||||
|
IsPrivate(), IsAdmin()
|
||||||
|
) # Работа callback роутера только для админов
|
||||||
|
|
||||||
# Подключение обязательных роутеров
|
# Подключение обязательных роутеров
|
||||||
dp.include_router(main_errors.router) # Роутер ошибки
|
dp.include_router(main_errors.router) # Роутер ошибки
|
||||||
@@ -44,6 +77,7 @@ def register_all_routers(dp: Dispatcher):
|
|||||||
dp.include_router(admin_menu.router) # Админ роутер
|
dp.include_router(admin_menu.router) # Админ роутер
|
||||||
dp.include_router(user_products.router) # Юзер роутер
|
dp.include_router(user_products.router) # Юзер роутер
|
||||||
dp.include_router(user_transactions.router) # Юзер роутер
|
dp.include_router(user_transactions.router) # Юзер роутер
|
||||||
|
dp.include_router(user_referral.router) # Юзер роутер
|
||||||
dp.include_router(admin_functions.router) # Админ роутер
|
dp.include_router(admin_functions.router) # Админ роутер
|
||||||
dp.include_router(admin_payments.router) # Админ роутер
|
dp.include_router(admin_payments.router) # Админ роутер
|
||||||
dp.include_router(admin_settings.router) # Админ роутер
|
dp.include_router(admin_settings.router) # Админ роутер
|
||||||
|
|||||||
@@ -5,18 +5,137 @@ from aiogram import Router, Bot, F
|
|||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
from aiogram.types import CallbackQuery, Message
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
from tgbot.database import Purchasesx, Refillx, Userx
|
from tgbot.database import Purchasesx, Refillx, ReferralWithdrawalx, Userx
|
||||||
from tgbot.keyboards.inline_admin import profile_edit_return_finl, mail_confirm_finl
|
from tgbot.keyboards.inline_admin import profile_edit_return_finl, mail_confirm_finl
|
||||||
from tgbot.services.api_hosting_text import HostingAPI
|
from tgbot.services.api_hosting_text import HostingAPI
|
||||||
from tgbot.utils.const_functions import is_number, to_number, del_message, ded, clear_html, convert_date
|
from tgbot.utils.const_functions import (
|
||||||
|
is_number,
|
||||||
|
to_number,
|
||||||
|
del_message,
|
||||||
|
ded,
|
||||||
|
clear_html,
|
||||||
|
convert_date,
|
||||||
|
)
|
||||||
from tgbot.utils.misc.bot_logging import bot_logger
|
from tgbot.utils.misc.bot_logging import bot_logger
|
||||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||||
from tgbot.utils.misc_functions import functions_mail_make
|
from tgbot.utils.misc_functions import functions_mail_make
|
||||||
from tgbot.utils.text_functions import open_profile_admin, refill_open_admin, purchase_open_admin
|
from tgbot.utils.text_functions import (
|
||||||
|
open_profile_admin,
|
||||||
|
refill_open_admin,
|
||||||
|
purchase_open_admin,
|
||||||
|
)
|
||||||
|
|
||||||
router = Router(name=__name__)
|
router = Router(name=__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def withdrawal_method_title(withdrawal_method: str) -> str:
|
||||||
|
if withdrawal_method == "Cryptobot":
|
||||||
|
return "CryptoBot"
|
||||||
|
|
||||||
|
return withdrawal_method
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("referral_withdrawal_complete:"))
|
||||||
|
async def referral_withdrawal_complete(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
withdrawal_id_raw = call.data.split(":", 1)[1]
|
||||||
|
if not withdrawal_id_raw.isdigit():
|
||||||
|
return await call.answer("❌ Заявка не найдена", True)
|
||||||
|
|
||||||
|
status, withdrawal = await ReferralWithdrawalx().complete(
|
||||||
|
withdrawal_id=int(withdrawal_id_raw),
|
||||||
|
admin_id=call.from_user.id,
|
||||||
|
)
|
||||||
|
if status == "ALREADY_PROCESSED":
|
||||||
|
return await call.answer("❌ Заявка уже обработана", True)
|
||||||
|
if status != "ok" or withdrawal is None:
|
||||||
|
return await call.answer("❌ Не удалось обработать заявку", True)
|
||||||
|
|
||||||
|
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||||
|
get_user = await Userx().get_required(user_id=withdrawal.user_id)
|
||||||
|
await call.message.edit_text(
|
||||||
|
ded(f"""
|
||||||
|
<b>✅ Заявка на вывод выполнена</b>
|
||||||
|
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||||
|
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
▪️ Способ: <code>{method_title}</code>
|
||||||
|
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||||
|
"""),
|
||||||
|
reply_markup=None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await bot.send_message(
|
||||||
|
withdrawal.user_id,
|
||||||
|
ded(f"""
|
||||||
|
<b>✅ Заявка на вывод выполнена</b>
|
||||||
|
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
▪️ Способ: <code>{method_title}</code>
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
bot_logger.warning(
|
||||||
|
"Не удалось отправить пользователю уведомление о выполненном выводе",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await call.answer("✅ Заявка выполнена", True)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("referral_withdrawal_reject:"))
|
||||||
|
async def referral_withdrawal_reject(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
withdrawal_id_raw = call.data.split(":", 1)[1]
|
||||||
|
if not withdrawal_id_raw.isdigit():
|
||||||
|
return await call.answer("❌ Заявка не найдена", True)
|
||||||
|
|
||||||
|
status, withdrawal = await ReferralWithdrawalx().reject(
|
||||||
|
withdrawal_id=int(withdrawal_id_raw),
|
||||||
|
admin_id=call.from_user.id,
|
||||||
|
)
|
||||||
|
if status == "ALREADY_PROCESSED":
|
||||||
|
return await call.answer("❌ Заявка уже обработана", True)
|
||||||
|
if status != "ok" or withdrawal is None:
|
||||||
|
return await call.answer("❌ Не удалось обработать заявку", True)
|
||||||
|
|
||||||
|
get_user = await Userx().get_required(user_id=withdrawal.user_id)
|
||||||
|
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||||
|
await call.message.edit_text(
|
||||||
|
ded(f"""
|
||||||
|
<b>❌ Заявка на вывод отклонена</b>
|
||||||
|
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||||
|
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
▪️ Способ: <code>{method_title}</code>
|
||||||
|
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||||
|
"""),
|
||||||
|
reply_markup=None,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await bot.send_message(
|
||||||
|
withdrawal.user_id,
|
||||||
|
ded(f"""
|
||||||
|
<b>❌ Заявка на вывод отклонена</b>
|
||||||
|
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
bot_logger.warning(
|
||||||
|
"Не удалось отправить пользователю уведомление об отклонённом выводе",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
await call.answer("❌ Заявка отклонена", True)
|
||||||
|
|
||||||
|
|
||||||
# Поиск чеков и профилей
|
# Поиск чеков и профилей
|
||||||
@router.message(F.text == "🔍 Поиск")
|
@router.message(F.text == "🔍 Поиск")
|
||||||
async def functions_find(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_find(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
@@ -55,17 +174,23 @@ async def functions_mail_get(message: Message, bot: Bot, state: FSM, arSession:
|
|||||||
|
|
||||||
|
|
||||||
# Подтверждение отправки рассылки
|
# Подтверждение отправки рассылки
|
||||||
@router.callback_query(F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm"))
|
@router.callback_query(
|
||||||
async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm")
|
||||||
|
)
|
||||||
|
async def functions_mail_confirm(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
get_status = call.data.split(":")[1]
|
get_status = call.data.split(":")[1]
|
||||||
|
|
||||||
send_message = (await state.get_data())['here_mail_message']
|
send_message = (await state.get_data())["here_mail_message"]
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
if get_status == "Yes":
|
if get_status == "Yes":
|
||||||
get_users = await Userx().get_all()
|
get_users = await Userx().get_all()
|
||||||
|
|
||||||
await call.message.edit_text(f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>")
|
await call.message.edit_text(
|
||||||
|
f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>"
|
||||||
|
)
|
||||||
|
|
||||||
await asyncio.create_task(functions_mail_make(bot, send_message, call))
|
await asyncio.create_task(functions_mail_make(bot, send_message, call))
|
||||||
else:
|
else:
|
||||||
@@ -76,7 +201,7 @@ async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSe
|
|||||||
##################################### ПОИСК ####################################
|
##################################### ПОИСК ####################################
|
||||||
# Принятие айди/логина пользователя или чека для поиска
|
# Принятие айди/логина пользователя или чека для поиска
|
||||||
@router.message(F.text, StateFilter("here_find"))
|
@router.message(F.text, StateFilter("here_find"))
|
||||||
@router.message(F.text.lower().startswith(('.find', 'find')))
|
@router.message(F.text.lower().startswith((".find", "find")))
|
||||||
async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
find_data = message.text.lower()
|
find_data = message.text.lower()
|
||||||
|
|
||||||
@@ -116,14 +241,18 @@ async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession:
|
|||||||
return await refill_open_admin(bot, message.from_user.id, get_refill)
|
return await refill_open_admin(bot, message.from_user.id, get_refill)
|
||||||
|
|
||||||
if get_purchase is not None:
|
if get_purchase is not None:
|
||||||
return await purchase_open_admin(bot, arSession, message.from_user.id, get_purchase)
|
return await purchase_open_admin(
|
||||||
|
bot, arSession, message.from_user.id, get_purchase
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
############################## УПРАВЛЕНИЕ ПРОФИЛЕМ #############################
|
############################## УПРАВЛЕНИЕ ПРОФИЛЕМ #############################
|
||||||
# Обновление профиля пользователя
|
# Обновление профиля пользователя
|
||||||
@router.callback_query(F.data.startswith("admin_user_refresh:"))
|
@router.callback_query(F.data.startswith("admin_user_refresh:"))
|
||||||
async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_refresh(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
user_id = int(call.data.split(":")[1])
|
user_id = int(call.data.split(":")[1])
|
||||||
|
|
||||||
get_user = await Userx().get_required(user_id=user_id)
|
get_user = await Userx().get_required(user_id=user_id)
|
||||||
@@ -136,7 +265,9 @@ async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSe
|
|||||||
|
|
||||||
# Покупки пользователя
|
# Покупки пользователя
|
||||||
@router.callback_query(F.data.startswith("admin_user_purchases:"))
|
@router.callback_query(F.data.startswith("admin_user_purchases:"))
|
||||||
async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_purchases(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
user_id = int(call.data.split(":")[1])
|
user_id = int(call.data.split(":")[1])
|
||||||
|
|
||||||
get_user = await Userx().get_required(user_id=user_id)
|
get_user = await Userx().get_required(user_id=user_id)
|
||||||
@@ -174,7 +305,9 @@ async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, ar
|
|||||||
|
|
||||||
# Выдача баланса пользователю
|
# Выдача баланса пользователю
|
||||||
@router.callback_query(F.data.startswith("admin_user_balance_add:"))
|
@router.callback_query(F.data.startswith("admin_user_balance_add:"))
|
||||||
async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_balance_add(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
user_id = int(call.data.split(":")[1])
|
user_id = int(call.data.split(":")[1])
|
||||||
|
|
||||||
await state.update_data(here_user=user_id)
|
await state.update_data(here_user=user_id)
|
||||||
@@ -188,8 +321,10 @@ async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM,
|
|||||||
|
|
||||||
# Принятие суммы для выдачи баланса пользователю
|
# Принятие суммы для выдачи баланса пользователю
|
||||||
@router.message(F.text, StateFilter("here_user_add"))
|
@router.message(F.text, StateFilter("here_user_add"))
|
||||||
async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_balance_add_get(
|
||||||
user_id = (await state.get_data())['here_user']
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
user_id = (await state.get_data())["here_user"]
|
||||||
|
|
||||||
if not is_number(message.text):
|
if not is_number(message.text):
|
||||||
return await message.answer(
|
return await message.answer(
|
||||||
@@ -222,7 +357,11 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
|
|||||||
f"<b>💰 Вам было выдано <code>{message.text}₽</code></b>",
|
f"<b>💰 Вам было выдано <code>{message.text}₽</code></b>",
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
bot_logger.debug("Не удалось уведомить пользователя %s о выдаче баланса", user_id, exc_info=True)
|
bot_logger.debug(
|
||||||
|
"Не удалось уведомить пользователя %s о выдаче баланса",
|
||||||
|
user_id,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
await message.answer(
|
await message.answer(
|
||||||
f"👤 Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
|
f"👤 Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
|
||||||
@@ -235,7 +374,9 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
|
|||||||
|
|
||||||
# Изменение баланса пользователю
|
# Изменение баланса пользователю
|
||||||
@router.callback_query(F.data.startswith("admin_user_balance_set:"))
|
@router.callback_query(F.data.startswith("admin_user_balance_set:"))
|
||||||
async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_balance_set(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
user_id = int(call.data.split(":")[1])
|
user_id = int(call.data.split(":")[1])
|
||||||
|
|
||||||
await state.update_data(here_user=user_id)
|
await state.update_data(here_user=user_id)
|
||||||
@@ -249,8 +390,10 @@ async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM,
|
|||||||
|
|
||||||
# Принятие суммы для изменения баланса пользователя
|
# Принятие суммы для изменения баланса пользователя
|
||||||
@router.message(F.text, StateFilter("here_user_set"))
|
@router.message(F.text, StateFilter("here_user_set"))
|
||||||
async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_balance_set_get(
|
||||||
user_id = (await state.get_data())['here_user']
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
user_id = (await state.get_data())["here_user"]
|
||||||
|
|
||||||
if not is_number(message.text):
|
if not is_number(message.text):
|
||||||
return await message.answer(
|
return await message.answer(
|
||||||
@@ -294,7 +437,9 @@ async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM,
|
|||||||
|
|
||||||
# Отправка сообщения пользователю
|
# Отправка сообщения пользователю
|
||||||
@router.callback_query(F.data.startswith("admin_user_message:"))
|
@router.callback_query(F.data.startswith("admin_user_message:"))
|
||||||
async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_user_message(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
user_id = int(call.data.split(":")[1])
|
user_id = int(call.data.split(":")[1])
|
||||||
|
|
||||||
await state.update_data(here_user_id=user_id)
|
await state.update_data(here_user_id=user_id)
|
||||||
@@ -309,17 +454,24 @@ async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM,
|
|||||||
|
|
||||||
# Принятие сообщения для отправки пользователю
|
# Принятие сообщения для отправки пользователю
|
||||||
@router.message(F.text, StateFilter("here_user_message"))
|
@router.message(F.text, StateFilter("here_user_message"))
|
||||||
async def functions_user_user_message_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def functions_user_user_message_get(
|
||||||
user_id = (await state.get_data())['here_user_id']
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
user_id = (await state.get_data())["here_user_id"]
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
get_message = "<b>💌 Сообщение от администратора:</b>\n" + f"<code>{clear_html(message.text)}</code>"
|
get_message = (
|
||||||
|
"<b>💌 Сообщение от администратора:</b>\n"
|
||||||
|
+ f"<code>{clear_html(message.text)}</code>"
|
||||||
|
)
|
||||||
get_user = await Userx().get_required(user_id=user_id)
|
get_user = await Userx().get_required(user_id=user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.send_message(user_id, get_message)
|
await bot.send_message(user_id, get_message)
|
||||||
except Exception:
|
except Exception:
|
||||||
bot_logger.debug("Не удалось отправить сообщение пользователю %s", user_id, exc_info=True)
|
bot_logger.debug(
|
||||||
|
"Не удалось отправить сообщение пользователю %s", user_id, exc_info=True
|
||||||
|
)
|
||||||
await message.reply("<b>❌ Не удалось отправить сообщение</b>")
|
await message.reply("<b>❌ Не удалось отправить сообщение</b>")
|
||||||
else:
|
else:
|
||||||
await message.reply("<b>✅ Сообщение было успешно доставлено</b>")
|
await message.reply("<b>✅ Сообщение было успешно доставлено</b>")
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ from tgbot.database import Paymentsx
|
|||||||
from tgbot.keyboards.inline_admin import (
|
from tgbot.keyboards.inline_admin import (
|
||||||
close_finl,
|
close_finl,
|
||||||
payment_cryptobot_finl,
|
payment_cryptobot_finl,
|
||||||
|
payment_lolzteam_finl,
|
||||||
payment_stars_finl,
|
payment_stars_finl,
|
||||||
)
|
)
|
||||||
from tgbot.services.api_cryptobot import CryptobotAPI
|
from tgbot.services.api_cryptobot import CryptobotAPI
|
||||||
|
from tgbot.services.api_lolzteam import LolzteamAPI
|
||||||
from tgbot.services.api_stars import StarsAPI
|
from tgbot.services.api_stars import StarsAPI
|
||||||
from tgbot.utils.const_functions import ded, is_number, to_number
|
from tgbot.utils.const_functions import ded, is_number, to_number
|
||||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||||
@@ -30,6 +32,19 @@ async def payments_cryptobot_open(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Управление - Lolzteam
|
||||||
|
@router.message(F.text == "🟢 Lolzteam")
|
||||||
|
async def payments_cryptobot_open(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"<b>🟢 Управление - Lolzteam</b>",
|
||||||
|
reply_markup=await payment_lolzteam_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Управление - Звёзды
|
# Управление - Звёзды
|
||||||
@router.message(F.text == "⭐️ Звёзды")
|
@router.message(F.text == "⭐️ Звёзды")
|
||||||
async def payments_stars_open(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def payments_stars_open(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
@@ -161,6 +176,162 @@ async def payments_cryptobot_get(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
################################### LOLZTEAM ##################################
|
||||||
|
# Баланс - Lolzteam
|
||||||
|
@router.callback_query(F.data == "payment_lolzteam_balance")
|
||||||
|
async def payments_lolzteam_balance(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
response = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=call,
|
||||||
|
skipping_error=True,
|
||||||
|
)
|
||||||
|
).balance()
|
||||||
|
|
||||||
|
await call.message.answer(
|
||||||
|
response,
|
||||||
|
reply_markup=close_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Информация - Lolzteam
|
||||||
|
@router.callback_query(F.data == "payment_lolzteam_check")
|
||||||
|
async def payment_lolzteam_check(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
status, response = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=call,
|
||||||
|
skipping_error=True,
|
||||||
|
)
|
||||||
|
).check()
|
||||||
|
|
||||||
|
await call.message.answer(
|
||||||
|
response,
|
||||||
|
reply_markup=close_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Изменение - Lolzteam
|
||||||
|
@router.callback_query(F.data == "payment_lolzteam_edit")
|
||||||
|
async def payment_lolzteam_edit(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
await state.set_state("here_lolzteam_token")
|
||||||
|
await call.message.edit_text(
|
||||||
|
ded(f"""
|
||||||
|
<b>🟢 Изменение Lolzteam - <a href='https://lolz.live/account/api'>API</a></b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Создайте Приложение в "Lolzteam", сгенерируйте токен (basic, invoice, market) и отправьте
|
||||||
|
"""),
|
||||||
|
disable_web_page_preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Выключатель - Lolzteam
|
||||||
|
@router.callback_query(F.data.startswith("payment_lolzteam_status:"))
|
||||||
|
async def payment_lolzteam_status(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_status = call.data.split(":")[1]
|
||||||
|
get_payments = await Paymentsx().get()
|
||||||
|
|
||||||
|
if get_status == "True" and get_payments.lolzteam_token == "None":
|
||||||
|
return await call.answer(
|
||||||
|
"❌ Токен данной платежной системы не был добавлен", True
|
||||||
|
)
|
||||||
|
|
||||||
|
await Paymentsx().update(status_lolzteam=get_status)
|
||||||
|
|
||||||
|
await call.message.edit_text(
|
||||||
|
"<b>🟢 Управление - Lolzteam</b>",
|
||||||
|
reply_markup=await payment_lolzteam_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
############################## ПРИНЯТИЕ LOLZTEAM ##############################
|
||||||
|
# Принятие токена Lolzteam
|
||||||
|
@router.message(StateFilter("here_lolzteam_token"))
|
||||||
|
async def payments_lolzteam_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
|
get_token = message.text
|
||||||
|
|
||||||
|
cache_message = await message.answer(
|
||||||
|
"<b>🟢 Проверка введённых Lolzteam данных... 🔄</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
status, response = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=message,
|
||||||
|
skipping_error=True,
|
||||||
|
token=get_token,
|
||||||
|
merchant_id="None",
|
||||||
|
)
|
||||||
|
).check()
|
||||||
|
|
||||||
|
if status:
|
||||||
|
await Paymentsx().update(lolzteam_token=get_token)
|
||||||
|
|
||||||
|
await state.set_state("here_lolzteam_merchant_id")
|
||||||
|
await cache_message.edit_text(
|
||||||
|
ded(f"""
|
||||||
|
<b>🟢 Изменение Lolzteam - <a href='https://lzt.market/merchants'>Мерчанты</a></b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Создайте мерчант в "Lolzteam Market" и отправьте его ID
|
||||||
|
"""),
|
||||||
|
disable_web_page_preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
await cache_message.edit_text("<b>🟢 Не удалось изменить Lolzteam ❌</b>")
|
||||||
|
|
||||||
|
|
||||||
|
# Принятие айди мерчанта Lolzteam
|
||||||
|
@router.message(StateFilter("here_lolzteam_merchant_id"))
|
||||||
|
async def payments_lolzteam_merchant_id_get(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_payments = await Paymentsx().get()
|
||||||
|
get_token = get_payments.lolzteam_token
|
||||||
|
|
||||||
|
get_merchant_id = message.text
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cache_message = await message.answer(
|
||||||
|
"<b>🟢 Проверка введённых Lolzteam данных... 🔄</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
status, response = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=message,
|
||||||
|
skipping_error=True,
|
||||||
|
token=get_token,
|
||||||
|
merchant_id=get_merchant_id,
|
||||||
|
)
|
||||||
|
).check()
|
||||||
|
|
||||||
|
if status:
|
||||||
|
await Paymentsx().update(lolzteam_merchant_id=int(get_merchant_id))
|
||||||
|
await cache_message.edit_text("<b>🟢 Lolzteam был успешно изменён ✅</b>")
|
||||||
|
else:
|
||||||
|
await cache_message.edit_text("<b>🟢 Не удалось изменить Lolzteam ❌</b>")
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"<b>🟢 Управление - Lolzteam</b>",
|
||||||
|
reply_markup=await payment_lolzteam_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
#################################### ЗВЁЗДЫ ####################################
|
#################################### ЗВЁЗДЫ ####################################
|
||||||
# Баланс - Звёзды
|
# Баланс - Звёзды
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# - *- coding: utf- 8 - *-
|
# - *- coding: utf- 8 - *-
|
||||||
|
from math import isfinite
|
||||||
|
|
||||||
from aiogram import Router, Bot, F
|
from aiogram import Router, Bot, F
|
||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
from aiogram.types import CallbackQuery, Message
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
from tgbot.database import Settingsx, Userx
|
from tgbot.database import Settingsx, Userx
|
||||||
from tgbot.keyboards.inline_admin import settings_status_finl, settings_finl
|
from tgbot.keyboards.inline_admin import settings_status_finl, settings_finl
|
||||||
from tgbot.utils.const_functions import ded
|
from tgbot.utils.const_functions import ded, is_number, to_number
|
||||||
from tgbot.utils.misc.bot_logging import bot_logger
|
from tgbot.utils.misc.bot_logging import bot_logger
|
||||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||||
from tgbot.utils.misc_functions import send_admins, insert_tags
|
from tgbot.utils.misc_functions import send_admins, insert_tags
|
||||||
@@ -116,6 +118,31 @@ async def settings_status_refill(
|
|||||||
await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
|
await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
|
||||||
|
|
||||||
|
|
||||||
|
# Включение/выключение реферальной системы
|
||||||
|
@router.callback_query(F.data.startswith("settings_status_referral:"))
|
||||||
|
async def settings_status_referral(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_status = call.data.split(":")[1]
|
||||||
|
|
||||||
|
get_user = await Userx().get_required(user_id=call.from_user.id)
|
||||||
|
await Settingsx().update(status_referral=get_status)
|
||||||
|
|
||||||
|
if get_status == "True":
|
||||||
|
send_text = "🟢 Включил реферальную систему в боте"
|
||||||
|
else:
|
||||||
|
send_text = "🔴 Выключил реферальную систему в боте"
|
||||||
|
|
||||||
|
await send_admins(
|
||||||
|
bot,
|
||||||
|
f"👤 Администратор <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
|
||||||
|
f"{send_text}",
|
||||||
|
not_me=get_user.user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
|
||||||
|
|
||||||
|
|
||||||
# Включение/выключение уведомлений о покупках
|
# Включение/выключение уведомлений о покупках
|
||||||
@router.callback_query(F.data.startswith("settings_notification_buy:"))
|
@router.callback_query(F.data.startswith("settings_notification_buy:"))
|
||||||
async def settings_notification_buy(
|
async def settings_notification_buy(
|
||||||
@@ -267,6 +294,72 @@ async def settings_faq_get(message: Message, bot: Bot, state: FSM, arSession: AR
|
|||||||
|
|
||||||
|
|
||||||
# Принятие поддержки
|
# Принятие поддержки
|
||||||
|
@router.callback_query(F.data == "settings_edit_referral_bonus")
|
||||||
|
async def settings_edit_referral_bonus(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
await state.set_state("here_settings_referral_bonus")
|
||||||
|
await call.message.edit_text(
|
||||||
|
"<b>🤝 Введите фиксированный бонус за приглашение в рублях</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings_edit_referral_percent")
|
||||||
|
async def settings_edit_referral_percent(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
await state.set_state("here_settings_referral_percent")
|
||||||
|
await call.message.edit_text(
|
||||||
|
"<b>📈 Введите процент дохода с пополнений от 0 до 100</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text, StateFilter("here_settings_referral_bonus"))
|
||||||
|
async def settings_referral_bonus_get(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
if not is_number(message.text):
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Введите неотрицательное число в рублях</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
referral_bonus = float(to_number(message.text))
|
||||||
|
|
||||||
|
if not isfinite(referral_bonus) or referral_bonus < 0:
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Введите неотрицательное число в рублях</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
await Settingsx().update(referral_bonus_rub=round(referral_bonus, 2))
|
||||||
|
await state.clear()
|
||||||
|
await message.answer(
|
||||||
|
"<b>🖍 Изменение данных бота</b>",
|
||||||
|
reply_markup=await settings_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text, StateFilter("here_settings_referral_percent"))
|
||||||
|
async def settings_referral_percent_get(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
if not is_number(message.text):
|
||||||
|
return await message.answer("<b>❌ Введите число от 0 до 100</b>")
|
||||||
|
|
||||||
|
referral_percent = float(to_number(message.text))
|
||||||
|
|
||||||
|
if not isfinite(referral_percent) or not 0 <= referral_percent <= 100:
|
||||||
|
return await message.answer("<b>❌ Введите число от 0 до 100</b>")
|
||||||
|
|
||||||
|
await Settingsx().update(referral_refill_percent=round(referral_percent, 2))
|
||||||
|
await state.clear()
|
||||||
|
await message.answer(
|
||||||
|
"<b>🖍 Изменение данных бота</b>",
|
||||||
|
reply_markup=await settings_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text, StateFilter("here_settings_support"))
|
@router.message(F.text, StateFilter("here_settings_support"))
|
||||||
async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
get_support = message.text
|
get_support = message.text
|
||||||
|
|||||||
@@ -124,7 +124,9 @@ async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
|||||||
async def main_start_deeplink(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
async def main_start_deeplink(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||||
deepling_args = message.text[7:]
|
deepling_args = message.text[7:]
|
||||||
|
|
||||||
if deepling_args.startswith("p_"):
|
if deepling_args.startswith("r_"):
|
||||||
|
await main_start(message, bot, state, arSession)
|
||||||
|
elif deepling_args.startswith("p_"):
|
||||||
position_id_raw = deepling_args[2:]
|
position_id_raw = deepling_args[2:]
|
||||||
|
|
||||||
if not position_id_raw.isdigit():
|
if not position_id_raw.isdigit():
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# - *- coding: utf- 8 - *-
|
||||||
|
from math import isfinite
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import re
|
||||||
|
|
||||||
|
from aiogram import Bot, F, Router
|
||||||
|
from aiogram.filters import StateFilter
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
|
from tgbot.database import ReferralWithdrawalx, Referralx, Settingsx, Userx
|
||||||
|
from tgbot.keyboards.inline_admin import referral_withdrawal_actions_finl
|
||||||
|
from tgbot.keyboards.inline_user import (
|
||||||
|
referral_menu_finl,
|
||||||
|
referral_transfer_method_finl,
|
||||||
|
referral_withdrawal_method_finl,
|
||||||
|
)
|
||||||
|
from tgbot.utils.const_functions import clear_html, ded, is_number, to_number
|
||||||
|
from tgbot.utils.misc.bot_models import ARS, FSM
|
||||||
|
from tgbot.utils.misc_functions import send_admins
|
||||||
|
|
||||||
|
router = Router(name=__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_withdrawal_recipient(withdrawal_method: str, recipient: str) -> bool:
|
||||||
|
if withdrawal_method == "Cryptobot":
|
||||||
|
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
|
||||||
|
|
||||||
|
if withdrawal_method == "Lolzteam":
|
||||||
|
parsed_url = urlparse(recipient)
|
||||||
|
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
|
||||||
|
"zelenka.guru",
|
||||||
|
"lolz.team",
|
||||||
|
"lolz.live",
|
||||||
|
"lolz.guru",
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def withdrawal_recipient_hint(withdrawal_method: str) -> str:
|
||||||
|
if withdrawal_method == "Cryptobot":
|
||||||
|
return (
|
||||||
|
"<b>📥 Введите @username получателя в CryptoBot</b>\n"
|
||||||
|
"➖➖➖➖➖➖➖➖➖➖\n"
|
||||||
|
"<i>Пример: @username</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
"<b>📥 Введите ссылку на профиль получателя Lolzteam</b>\n"
|
||||||
|
"➖➖➖➖➖➖➖➖➖➖\n"
|
||||||
|
"<i>Подойдут ссылки с zelenka.guru, lolz.team, lolz.live или lolz.guru</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def withdrawal_method_title(withdrawal_method: str) -> str:
|
||||||
|
if withdrawal_method == "Cryptobot":
|
||||||
|
return "CryptoBot"
|
||||||
|
|
||||||
|
return withdrawal_method
|
||||||
|
|
||||||
|
|
||||||
|
async def get_referral_menu_data(user_id: int, bot: Bot):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
return None
|
||||||
|
|
||||||
|
get_user = await Userx().get_required(user_id=user_id)
|
||||||
|
get_referrals = await Referralx().gets(referrer_id=user_id)
|
||||||
|
|
||||||
|
bot_username = get_settings.misc_bot
|
||||||
|
|
||||||
|
if bot_username == "None":
|
||||||
|
bot_username = (await bot.get_me()).username or ""
|
||||||
|
|
||||||
|
referral_link = f"https://t.me/{bot_username}?start=r_{user_id}"
|
||||||
|
available_balance = round(
|
||||||
|
get_user.user_referral_balance - get_user.user_referral_hold,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
referral_text = ded(f"""
|
||||||
|
<b>🤝 Реферальная система</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Приглашено пользователей: <code>{len(get_referrals)}</code>
|
||||||
|
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
|
||||||
|
▪️ Доступно: <code>{available_balance}₽</code>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Бонус за приглашение: <code>{get_settings.referral_bonus_rub}₽</code>
|
||||||
|
▪️ Доход с пополнений: <code>{get_settings.referral_refill_percent}%</code>
|
||||||
|
""")
|
||||||
|
|
||||||
|
return referral_text, referral_menu_finl(referral_link)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text == "🤝 Реферальная система")
|
||||||
|
async def show_referral_menu(
|
||||||
|
message: Message,
|
||||||
|
bot: Bot,
|
||||||
|
state: FSM,
|
||||||
|
arSession: ARS,
|
||||||
|
user_id: int | None = None,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
user_id = user_id or message.from_user.id
|
||||||
|
referral_menu_data = await get_referral_menu_data(user_id, bot)
|
||||||
|
if referral_menu_data is None:
|
||||||
|
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||||
|
|
||||||
|
referral_text, referral_keyboard = referral_menu_data
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
referral_text,
|
||||||
|
reply_markup=referral_keyboard,
|
||||||
|
disable_web_page_preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "referral_menu")
|
||||||
|
async def user_referral_menu_return(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
referral_menu_data = await get_referral_menu_data(call.from_user.id, bot)
|
||||||
|
if referral_menu_data is None:
|
||||||
|
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||||
|
|
||||||
|
referral_text, referral_keyboard = referral_menu_data
|
||||||
|
await call.message.edit_text(
|
||||||
|
referral_text,
|
||||||
|
reply_markup=referral_keyboard,
|
||||||
|
disable_web_page_preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "referral_transfer")
|
||||||
|
async def user_referral_transfer_start(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
await state.set_state("here_referral_transfer_amount")
|
||||||
|
await call.message.edit_text(
|
||||||
|
"<b>💸 Введите сумму для перевода на основной баланс</b>",
|
||||||
|
reply_markup=referral_transfer_method_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "referral_withdrawal")
|
||||||
|
async def user_referral_withdrawal_start(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
await call.message.edit_text(
|
||||||
|
"<b>📥 Выберите способ вывода средств</b>",
|
||||||
|
reply_markup=referral_withdrawal_method_finl(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("referral_withdrawal_method:"))
|
||||||
|
async def user_referral_withdrawal_method(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
withdrawal_method = call.data.split(":", 1)[1]
|
||||||
|
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||||
|
return await call.answer("❌ Способ вывода не найден", True)
|
||||||
|
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
await state.update_data(here_referral_withdrawal_method=withdrawal_method)
|
||||||
|
await state.set_state("here_referral_withdrawal_amount")
|
||||||
|
await call.message.edit_text("<b>📥 Введите сумму для вывода</b>")
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text, StateFilter("here_referral_withdrawal_amount"))
|
||||||
|
async def user_referral_withdrawal_amount(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||||
|
|
||||||
|
if not is_number(message.text):
|
||||||
|
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||||
|
|
||||||
|
withdrawal_amount = float(to_number(message.text))
|
||||||
|
if not isfinite(withdrawal_amount) or withdrawal_amount <= 0:
|
||||||
|
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||||
|
|
||||||
|
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||||
|
available_balance = round(
|
||||||
|
get_user.user_referral_balance - get_user.user_referral_hold, 2
|
||||||
|
)
|
||||||
|
if withdrawal_amount > available_balance:
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
withdrawal_data = await state.get_data()
|
||||||
|
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
|
||||||
|
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
|
||||||
|
|
||||||
|
await state.update_data(here_referral_withdrawal_amount=withdrawal_amount)
|
||||||
|
await state.set_state("here_referral_withdrawal_recipient")
|
||||||
|
await message.answer(withdrawal_recipient_hint(withdrawal_method))
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text, StateFilter("here_referral_withdrawal_recipient"))
|
||||||
|
async def user_referral_withdrawal_recipient(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||||
|
|
||||||
|
withdrawal_data = await state.get_data()
|
||||||
|
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
|
||||||
|
withdrawal_amount = withdrawal_data.get("here_referral_withdrawal_amount")
|
||||||
|
|
||||||
|
if withdrawal_method is None or withdrawal_amount is None:
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
|
||||||
|
|
||||||
|
withdrawal_recipient = clear_html(message.text).strip()
|
||||||
|
if not withdrawal_recipient:
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Введите реквизиты</b>\n"
|
||||||
|
f"{withdrawal_recipient_hint(withdrawal_method)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_valid_withdrawal_recipient(withdrawal_method, withdrawal_recipient):
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Некорректные реквизиты</b>\n"
|
||||||
|
f"{withdrawal_recipient_hint(withdrawal_method)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
withdrawal_status, withdrawal = await ReferralWithdrawalx().create_pending(
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
withdrawal_amount=withdrawal_amount,
|
||||||
|
withdrawal_method=withdrawal_method,
|
||||||
|
withdrawal_recipient=withdrawal_recipient,
|
||||||
|
)
|
||||||
|
|
||||||
|
if withdrawal_status == "INSUFFICIENT_FUNDS":
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if withdrawal_status in ("INVALID_AMOUNT", "INVALID_RECIPIENT", "INVALID_METHOD"):
|
||||||
|
return await message.answer("<b>❌ Некорректные данные для вывода</b>")
|
||||||
|
|
||||||
|
if withdrawal_status == "USER_NOT_FOUND":
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if withdrawal_status != "ok" or withdrawal is None:
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Не удалось создать заявку. Попробуйте позже</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||||
|
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||||
|
await message.answer(
|
||||||
|
ded(f"""
|
||||||
|
<b>📥 Заявка на вывод создана</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
▪️ Способ: <code>{method_title}</code>
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
await send_admins(
|
||||||
|
bot,
|
||||||
|
ded(f"""
|
||||||
|
<b>📥 Новая заявка на вывод</b>
|
||||||
|
|
||||||
|
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||||
|
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||||
|
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||||
|
▪️ Способ: <code>{method_title}</code>
|
||||||
|
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||||
|
"""),
|
||||||
|
keyboard=referral_withdrawal_actions_finl(withdrawal.increment),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text, StateFilter("here_referral_transfer_amount"))
|
||||||
|
async def user_referral_transfer_amount(
|
||||||
|
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
get_settings = await Settingsx().get()
|
||||||
|
|
||||||
|
if get_settings.status_referral != "True":
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||||
|
|
||||||
|
if not is_number(message.text):
|
||||||
|
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||||
|
|
||||||
|
transfer_amount = float(to_number(message.text))
|
||||||
|
|
||||||
|
if not isfinite(transfer_amount) or transfer_amount <= 0:
|
||||||
|
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||||
|
|
||||||
|
transfer_status = await Referralx().transfer_to_main_balance(
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
amount=transfer_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
if transfer_status == "INSUFFICIENT_FUNDS":
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||||
|
)
|
||||||
|
if transfer_status == "INVALID_AMOUNT":
|
||||||
|
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||||
|
if transfer_status == "USER_NOT_FOUND":
|
||||||
|
await state.clear()
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
|
||||||
|
)
|
||||||
|
if transfer_status != "ok":
|
||||||
|
return await message.answer(
|
||||||
|
"<b>❌ Не удалось выполнить перевод. Попробуйте позже</b>"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
ded(f"""
|
||||||
|
<b>💸 Средства переведены на основной баланс</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Переведено: <code>{round(transfer_amount, 2)}₽</code>
|
||||||
|
▪️ Основной баланс: <code>{get_user.user_balance}₽</code>
|
||||||
|
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
|
||||||
|
"""),
|
||||||
|
)
|
||||||
@@ -5,9 +5,10 @@ from aiogram import Router, Bot, F
|
|||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
from aiogram.types import CallbackQuery, Message, PreCheckoutQuery
|
from aiogram.types import CallbackQuery, Message, PreCheckoutQuery
|
||||||
|
|
||||||
from tgbot.database import Paymentsx, Refillx, Userx, Settingsx
|
from tgbot.database import Paymentsx, ReferralTransactionx, Refillx, Userx, Settingsx
|
||||||
from tgbot.keyboards.inline_user import refill_bill_finl, refill_method_finl
|
from tgbot.keyboards.inline_user import refill_bill_finl, refill_method_finl
|
||||||
from tgbot.services.api_cryptobot import CryptobotAPI
|
from tgbot.services.api_cryptobot import CryptobotAPI
|
||||||
|
from tgbot.services.api_lolzteam import LolzteamAPI
|
||||||
from tgbot.services.api_stars import StarsAPI
|
from tgbot.services.api_stars import StarsAPI
|
||||||
from tgbot.utils.const_functions import is_number, to_number, gen_id, ded
|
from tgbot.utils.const_functions import is_number, to_number, gen_id, ded
|
||||||
from tgbot.utils.misc.bot_logging import bot_logger
|
from tgbot.utils.misc.bot_logging import bot_logger
|
||||||
@@ -15,6 +16,7 @@ from tgbot.utils.misc.bot_models import FSM, ARS
|
|||||||
from tgbot.utils.misc_functions import send_admins
|
from tgbot.utils.misc_functions import send_admins
|
||||||
|
|
||||||
min_refill_rub = 5 # Минимальная сумма пополнения в рублях
|
min_refill_rub = 5 # Минимальная сумма пополнения в рублях
|
||||||
|
max_refill_rub = 150000 # Максимальная сумма пополнения в рублях
|
||||||
|
|
||||||
router = Router(name=__name__)
|
router = Router(name=__name__)
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ async def refill_method_list(call: CallbackQuery, bot: Bot, state: FSM, arSessio
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
get_payments.status_cryptobot == "False"
|
get_payments.status_cryptobot == "False"
|
||||||
|
and get_payments.status_lolzteam == "False"
|
||||||
and get_payments.status_stars == "False"
|
and get_payments.status_stars == "False"
|
||||||
):
|
):
|
||||||
return await call.answer("❗️ Пополнения временно недоступны", True)
|
return await call.answer("❗️ Пополнения временно недоступны", True)
|
||||||
@@ -49,6 +52,10 @@ async def refill_method_select(
|
|||||||
return await call.answer(
|
return await call.answer(
|
||||||
"❌ Пополнение данным способом временно недоступно", True
|
"❌ Пополнение данным способом временно недоступно", True
|
||||||
)
|
)
|
||||||
|
elif refill_method == "Lolzteam" and get_payments.status_lolzteam == "False":
|
||||||
|
return await call.answer(
|
||||||
|
"❌ Пополнение данным способом временно недоступно", True
|
||||||
|
)
|
||||||
elif refill_method == "Stars" and get_payments.status_stars == "False":
|
elif refill_method == "Stars" and get_payments.status_stars == "False":
|
||||||
return await call.answer(
|
return await call.answer(
|
||||||
"❌ Пополнение данным способом временно недоступно", True
|
"❌ Пополнение данным способом временно недоступно", True
|
||||||
@@ -77,7 +84,7 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
|
|||||||
return await message.answer(
|
return await message.answer(
|
||||||
ded(f"""
|
ded(f"""
|
||||||
<b>❌ Неверная сумма пополнения</b>
|
<b>❌ Неверная сумма пополнения</b>
|
||||||
❗️ Cумма не должна быть меньше <code>{min_refill_rub}₽</code> и больше <code>150 000₽</code>
|
❗️ Cумма не должна быть меньше <code>{min_refill_rub}₽</code> и больше <code>{max_refill_rub}₽</code>
|
||||||
💰 Введите сумму для пополнения средств
|
💰 Введите сумму для пополнения средств
|
||||||
"""),
|
"""),
|
||||||
)
|
)
|
||||||
@@ -98,6 +105,15 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
|
|||||||
)
|
)
|
||||||
).bill(refill_amount)
|
).bill(refill_amount)
|
||||||
|
|
||||||
|
elif refill_method == "Lolzteam":
|
||||||
|
bill_message, bill_link, bill_receipt = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=cache_message,
|
||||||
|
)
|
||||||
|
).bill(refill_amount)
|
||||||
|
|
||||||
elif refill_method == "Stars":
|
elif refill_method == "Stars":
|
||||||
bill_message, bill_link, bill_receipt = await (
|
bill_message, bill_link, bill_receipt = await (
|
||||||
await StarsAPI.connect(
|
await StarsAPI.connect(
|
||||||
@@ -106,6 +122,7 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
|
|||||||
update=cache_message,
|
update=cache_message,
|
||||||
)
|
)
|
||||||
).bill(refill_amount)
|
).bill(refill_amount)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return await cache_message.edit_text(
|
return await cache_message.edit_text(
|
||||||
f"<b>❌ Данный способ пополнения не найден. Попробуйте позже: {refill_method}</b>"
|
f"<b>❌ Данный способ пополнения не найден. Попробуйте позже: {refill_method}</b>"
|
||||||
@@ -180,6 +197,59 @@ async def refill_check_cryptobot(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Проверка оплаты - Lolzteam
|
||||||
|
@router.callback_query(F.data.startswith("Pay:Lolzteam"))
|
||||||
|
async def refill_check_lolzteam(
|
||||||
|
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||||
|
):
|
||||||
|
pay_method = call.data.split(":")[1]
|
||||||
|
pay_comment = call.data.split(":")[2]
|
||||||
|
|
||||||
|
pay_status, pay_amount = await (
|
||||||
|
await LolzteamAPI.connect(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=call,
|
||||||
|
)
|
||||||
|
).bill_check(pay_comment)
|
||||||
|
|
||||||
|
if pay_status == 0:
|
||||||
|
refill_status = await refill_success(
|
||||||
|
bot=bot,
|
||||||
|
call=call,
|
||||||
|
pay_method=pay_method,
|
||||||
|
pay_amount=pay_amount,
|
||||||
|
pay_comment=pay_comment,
|
||||||
|
)
|
||||||
|
|
||||||
|
if refill_status == "ALREADY":
|
||||||
|
await call.answer("❗ Ваше пополнение уже зачислено.", True, cache_time=60)
|
||||||
|
await delete_refill_message(
|
||||||
|
bot, call.message.chat.id, call.message.message_id
|
||||||
|
)
|
||||||
|
elif refill_status == "USER_NOT_FOUND":
|
||||||
|
await call.answer(
|
||||||
|
"❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30
|
||||||
|
)
|
||||||
|
elif pay_status == 1:
|
||||||
|
await call.answer(
|
||||||
|
"❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30
|
||||||
|
)
|
||||||
|
elif pay_status == 2:
|
||||||
|
await call.answer(
|
||||||
|
"❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5
|
||||||
|
)
|
||||||
|
elif pay_status == 3:
|
||||||
|
await call.answer("❗️ Вы не успели оплатить счёт", True, cache_time=5)
|
||||||
|
await call.message.edit_reply_markup()
|
||||||
|
else:
|
||||||
|
await call.answer(
|
||||||
|
f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.",
|
||||||
|
True,
|
||||||
|
cache_time=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Проверка оплаты - Звёзды
|
# Проверка оплаты - Звёзды
|
||||||
@router.callback_query(F.data.startswith("Pay:Stars"))
|
@router.callback_query(F.data.startswith("Pay:Stars"))
|
||||||
async def refill_check_stars(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
async def refill_check_stars(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||||
@@ -357,6 +427,8 @@ async def save_refill_success(
|
|||||||
) -> Tuple[str, int]:
|
) -> Tuple[str, int]:
|
||||||
if pay_method == "Cryptobot":
|
if pay_method == "Cryptobot":
|
||||||
text_method = "CryptoBot"
|
text_method = "CryptoBot"
|
||||||
|
elif pay_method == "Lolzteam":
|
||||||
|
text_method = "Lolzteam"
|
||||||
elif pay_method == "Stars":
|
elif pay_method == "Stars":
|
||||||
text_method = "Telegram Stars"
|
text_method = "Telegram Stars"
|
||||||
else:
|
else:
|
||||||
@@ -388,4 +460,26 @@ async def save_refill_success(
|
|||||||
"""),
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
referral_reward = await ReferralTransactionx().get_refill_reward(
|
||||||
|
f"refill:{pay_comment or pay_receipt}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if referral_reward is not None:
|
||||||
|
try:
|
||||||
|
await bot.send_message(
|
||||||
|
referral_reward.user_id,
|
||||||
|
ded(f"""
|
||||||
|
<b>🤝 Реферальное начисление</b>
|
||||||
|
|
||||||
|
▪️ Ваш реферал: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>
|
||||||
|
▪️ Пополнение: <code>{pay_amount}₽</code>
|
||||||
|
▪️ Начислено: <code>{referral_reward.amount}₽</code>
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
bot_logger.warning(
|
||||||
|
"Не удалось отправить уведомление о реферальном начислении",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
return response_success, pay_receipt
|
return response_success, pay_receipt
|
||||||
|
|||||||
@@ -13,20 +13,31 @@ from tgbot.utils.misc.bot_models import ARS
|
|||||||
from tgbot.utils.misc_functions import send_admins
|
from tgbot.utils.misc_functions import send_admins
|
||||||
|
|
||||||
# Список поддерживаемых валют
|
# Список поддерживаемых валют
|
||||||
ALLOW_CURRENCIES = ['BTC', 'ETH', 'LTC', 'USDT', 'USDC', 'TRX', 'TON', 'BNB', 'SOL', 'DOGE']
|
ALLOW_CURRENCIES = [
|
||||||
|
"BTC",
|
||||||
|
"ETH",
|
||||||
|
"LTC",
|
||||||
|
"USDT",
|
||||||
|
"USDC",
|
||||||
|
"TRX",
|
||||||
|
"TON",
|
||||||
|
"BNB",
|
||||||
|
"SOL",
|
||||||
|
"DOGE",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# АПИ для работы с CryptoBot
|
# АПИ для работы с CryptoBot
|
||||||
class CryptobotAPI:
|
class CryptobotAPI:
|
||||||
# Настройка клиента CryptoBot
|
# Настройка клиента CryptoBot
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
arSession: ARS,
|
arSession: ARS,
|
||||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||||
token: str = "None",
|
token: str = "None",
|
||||||
adding: bool = False,
|
adding: bool = False,
|
||||||
skipping_error: bool = False,
|
skipping_error: bool = False,
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.arSession = arSession
|
self.arSession = arSession
|
||||||
@@ -38,12 +49,12 @@ class CryptobotAPI:
|
|||||||
# Инициализация данных
|
# Инициализация данных
|
||||||
@classmethod
|
@classmethod
|
||||||
async def connect(
|
async def connect(
|
||||||
cls,
|
cls,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
arSession: ARS,
|
arSession: ARS,
|
||||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||||
token: Optional[str] = None,
|
token: Optional[str] = None,
|
||||||
skipping_error: bool = False,
|
skipping_error: bool = False,
|
||||||
) -> "CryptobotAPI":
|
) -> "CryptobotAPI":
|
||||||
adding = token is not None
|
adding = token is not None
|
||||||
|
|
||||||
@@ -75,21 +86,23 @@ class CryptobotAPI:
|
|||||||
await send_admins(
|
await send_admins(
|
||||||
self.bot,
|
self.bot,
|
||||||
f"<b>🔷 CryptoBot недоступен. Как можно быстрее его замените</b>\n"
|
f"<b>🔷 CryptoBot недоступен. Как можно быстрее его замените</b>\n"
|
||||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
f"❗️ Ошибка: <code>{error_code}</code>",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Проверка кассы/кошелька
|
# Проверка кассы/кошелька
|
||||||
async def check(self) -> Tuple[bool, str]:
|
async def check(self) -> Tuple[bool, str]:
|
||||||
status, response = await self._request("getMe")
|
status, response = await self._request("getMe")
|
||||||
|
|
||||||
if status and response['ok']:
|
if status and response["ok"]:
|
||||||
return True, ded(f"""
|
return True, ded(
|
||||||
|
f"""
|
||||||
<b>🔷 CryptoBot кошелёк полностью функционирует ✅</b>
|
<b>🔷 CryptoBot кошелёк полностью функционирует ✅</b>
|
||||||
➖➖➖➖➖➖➖➖➖➖
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
▪️ Токен: <code>{self.token}</code>
|
▪️ Токен: <code>{self.token}</code>
|
||||||
▪️ Айди: <code>{response['result']['app_id']}</code>
|
▪️ Айди: <code>{response['result']['app_id']}</code>
|
||||||
▪️ Имя: <code>{response['result']['name']}</code>
|
▪️ Имя: <code>{response['result']['name']}</code>
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
return False, "<b>🔷 Не удалось проверить CryptoBot кошелёк ❌</b>"
|
return False, "<b>🔷 Не удалось проверить CryptoBot кошелёк ❌</b>"
|
||||||
|
|
||||||
@@ -97,17 +110,17 @@ class CryptobotAPI:
|
|||||||
async def balance(self) -> str:
|
async def balance(self) -> str:
|
||||||
status, response = await self._request("getBalance")
|
status, response = await self._request("getBalance")
|
||||||
|
|
||||||
if status and response['ok']:
|
if status and response["ok"]:
|
||||||
save_currencies = []
|
save_currencies = []
|
||||||
|
|
||||||
response_balances = sorted(
|
response_balances = sorted(
|
||||||
response['result'],
|
response["result"],
|
||||||
reverse=True,
|
reverse=True,
|
||||||
key=lambda balance: to_number(balance['available']),
|
key=lambda balance: to_number(balance["available"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
for currency in response_balances:
|
for currency in response_balances:
|
||||||
if currency['currency_code'] in ALLOW_CURRENCIES:
|
if currency["currency_code"] in ALLOW_CURRENCIES:
|
||||||
save_currencies.append(
|
save_currencies.append(
|
||||||
f"▪️ {currency['currency_code']}: <code>{currency['available']}</code>"
|
f"▪️ {currency['currency_code']}: <code>{currency['available']}</code>"
|
||||||
)
|
)
|
||||||
@@ -123,24 +136,26 @@ class CryptobotAPI:
|
|||||||
return "<b>🔷 Не удалось получить баланс CryptoBot кошелька ❌</b>"
|
return "<b>🔷 Не удалось получить баланс CryptoBot кошелька ❌</b>"
|
||||||
|
|
||||||
# Создание счета на оплату
|
# Создание счета на оплату
|
||||||
async def bill(self, pay_amount: Union[float, int]) -> Tuple[Union[str, bool], str, str]:
|
async def bill(
|
||||||
|
self, pay_amount: Union[float, int]
|
||||||
|
) -> Tuple[Union[str, bool], str, str]:
|
||||||
assets_currencies = ",".join(ALLOW_CURRENCIES)
|
assets_currencies = ",".join(ALLOW_CURRENCIES)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'currency_type': 'fiat',
|
"currency_type": "fiat",
|
||||||
'fiat': 'RUB',
|
"fiat": "RUB",
|
||||||
'amount': str(pay_amount),
|
"amount": str(pay_amount),
|
||||||
'expires_in': 10800,
|
"expires_in": 10800,
|
||||||
'accepted_assets': assets_currencies
|
"accepted_assets": assets_currencies,
|
||||||
}
|
}
|
||||||
|
|
||||||
status, response = await self._request("createInvoice", payload)
|
status, response = await self._request("createInvoice", payload)
|
||||||
|
|
||||||
if status and response['ok']:
|
if status and response["ok"]:
|
||||||
bill_message = ded(f"""
|
bill_message = ded(f"""
|
||||||
<b>💰 Пополнение баланса</b>
|
<b>💰 Пополнение баланса</b>
|
||||||
➖➖➖➖➖➖➖➖➖➖
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||||
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
||||||
▪️ У вас имеется 3 часа на оплату счета
|
▪️ У вас имеется 3 часа на оплату счета
|
||||||
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
||||||
@@ -148,46 +163,52 @@ class CryptobotAPI:
|
|||||||
❗️ После оплаты, нажмите на <code>Проверить оплату</code>
|
❗️ После оплаты, нажмите на <code>Проверить оплату</code>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
return bill_message, response['result']['mini_app_invoice_url'], response['result']['invoice_id']
|
return (
|
||||||
|
bill_message,
|
||||||
|
response["result"]["mini_app_invoice_url"],
|
||||||
|
response["result"]["invoice_id"],
|
||||||
|
)
|
||||||
|
|
||||||
return False, "", ""
|
return False, "", ""
|
||||||
|
|
||||||
# Проверка счета на оплату
|
# Проверка счета на оплату
|
||||||
async def bill_check(self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1) -> Tuple[int, float]:
|
async def bill_check(
|
||||||
|
self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1
|
||||||
|
) -> Tuple[int, float]:
|
||||||
payload = {
|
payload = {
|
||||||
'invoice_ids': f'{bill_receipt}',
|
"invoice_ids": f"{bill_receipt}",
|
||||||
'fiat': 'RUB',
|
"fiat": "RUB",
|
||||||
}
|
}
|
||||||
|
|
||||||
status, response = await self._request("getInvoices", payload)
|
status, response = await self._request("getInvoices", payload)
|
||||||
|
|
||||||
pay_status, pay_amount = 1, 0
|
pay_status, pay_amount = 1, 0
|
||||||
|
|
||||||
if status and response['ok']:
|
if status and response["ok"]:
|
||||||
get_invoice = response['result']['items'][0]
|
get_invoice = response["result"]["items"][0]
|
||||||
|
|
||||||
if get_invoice['status'] == "active":
|
if get_invoice["status"] == "active":
|
||||||
pay_status = 2
|
pay_status = 2
|
||||||
elif get_invoice['status'] == "expired":
|
elif get_invoice["status"] == "expired":
|
||||||
pay_status = 3
|
pay_status = 3
|
||||||
else:
|
else:
|
||||||
pay_status = 0
|
pay_status = 0
|
||||||
pay_amount = to_number(get_invoice['amount'])
|
pay_amount = to_number(get_invoice["amount"])
|
||||||
|
|
||||||
return pay_status, pay_amount
|
return pay_status, pay_amount
|
||||||
|
|
||||||
# Генерация запроса
|
# Генерация запроса
|
||||||
async def _request(
|
async def _request(
|
||||||
self,
|
self,
|
||||||
method: str,
|
method: str,
|
||||||
data: Optional[Dict[str, Any]] = None,
|
data: Optional[Dict[str, Any]] = None,
|
||||||
) -> Tuple[bool, Any]:
|
) -> Tuple[bool, Any]:
|
||||||
session = await self.arSession.get_session()
|
session = await self.arSession.get_session()
|
||||||
|
|
||||||
base_url = 'https://pay.crypt.bot/api/'
|
base_url = "https://pay.crypt.bot/api/"
|
||||||
headers = {
|
headers = {
|
||||||
'Crypto-Pay-API-Token': self.token,
|
"Crypto-Pay-API-Token": self.token,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
}
|
}
|
||||||
|
|
||||||
url = base_url + method
|
url = base_url + method
|
||||||
@@ -204,7 +225,9 @@ class CryptobotAPI:
|
|||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return True, response_data
|
return True, response_data
|
||||||
else:
|
else:
|
||||||
await self.error_notification(f"{response.status} - {str(response_data)}")
|
await self.error_notification(
|
||||||
|
f"{response.status} - {str(response_data)}"
|
||||||
|
)
|
||||||
|
|
||||||
return False, response_data
|
return False, response_data
|
||||||
except ClientConnectorCertificateError:
|
except ClientConnectorCertificateError:
|
||||||
|
|||||||
@@ -0,0 +1,303 @@
|
|||||||
|
# - *- coding: utf- 8 - *-
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict, Optional, Tuple, Union
|
||||||
|
|
||||||
|
from aiogram import Bot
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
from aiohttp import ClientConnectorCertificateError
|
||||||
|
|
||||||
|
from tgbot.database import Paymentsx
|
||||||
|
from tgbot.utils.const_functions import ded, to_number, get_unix, gen_id
|
||||||
|
from tgbot.utils.misc.bot_logging import bot_logger
|
||||||
|
from tgbot.utils.misc.bot_models import ARS
|
||||||
|
from tgbot.utils.misc_functions import send_admins
|
||||||
|
|
||||||
|
# Список поддерживаемых валют
|
||||||
|
ALLOW_CURRENCIES = [
|
||||||
|
"rub",
|
||||||
|
"uah",
|
||||||
|
"kzt",
|
||||||
|
"byn",
|
||||||
|
"usd",
|
||||||
|
"eur",
|
||||||
|
"gbp",
|
||||||
|
"cny",
|
||||||
|
"try",
|
||||||
|
"jpy",
|
||||||
|
"brl",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# АПИ для работы с Lolzteam
|
||||||
|
class LolzteamAPI:
|
||||||
|
# Настройка клиента Lolzteam
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: Bot,
|
||||||
|
arSession: ARS,
|
||||||
|
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||||
|
token: str = "None",
|
||||||
|
merchant_id: int = "None",
|
||||||
|
adding: bool = False,
|
||||||
|
skipping_error: bool = False,
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
self.arSession = arSession
|
||||||
|
self.update = update
|
||||||
|
self.token = token
|
||||||
|
self.merchant_id = merchant_id
|
||||||
|
self.adding = adding
|
||||||
|
self.skipping_error = skipping_error
|
||||||
|
|
||||||
|
# Инициализация данных
|
||||||
|
@classmethod
|
||||||
|
async def connect(
|
||||||
|
cls,
|
||||||
|
bot: Bot,
|
||||||
|
arSession: ARS,
|
||||||
|
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||||
|
token: Optional[str] = None,
|
||||||
|
merchant_id: Optional[int] = None,
|
||||||
|
skipping_error: bool = False,
|
||||||
|
) -> "LolzteamAPI":
|
||||||
|
adding = token is not None
|
||||||
|
|
||||||
|
if token is None or merchant_id is None:
|
||||||
|
payments = await Paymentsx().get()
|
||||||
|
adding = False
|
||||||
|
|
||||||
|
if token is None:
|
||||||
|
token = payments.lolzteam_token
|
||||||
|
|
||||||
|
if merchant_id is None:
|
||||||
|
merchant_id = payments.lolzteam_merchant_id
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
bot=bot,
|
||||||
|
arSession=arSession,
|
||||||
|
update=update,
|
||||||
|
token=token,
|
||||||
|
merchant_id=merchant_id,
|
||||||
|
adding=adding,
|
||||||
|
skipping_error=skipping_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Уведомления о неработоспособности кассы/кошелька
|
||||||
|
async def error_notification(self, error_code: str = "Unknown"):
|
||||||
|
bot_logger.warning("Lolzteam недоступен: %s", error_code)
|
||||||
|
|
||||||
|
if not self.skipping_error:
|
||||||
|
if self.adding and self.update is not None:
|
||||||
|
await self.update.edit_text(
|
||||||
|
f"<b>🟢 Не удалось добавить Lolzteam кассу ❌</b>\n"
|
||||||
|
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await send_admins(
|
||||||
|
self.bot,
|
||||||
|
f"<b>🟢 Lolzteam недоступен. Как можно быстрее его замените</b>\n"
|
||||||
|
f"❗️ Ошибка: <code>{error_code}</code>",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Проверка кассы/кошелька
|
||||||
|
async def check(self) -> Tuple[bool, str]:
|
||||||
|
status, response = await self._request("me", method="GET")
|
||||||
|
|
||||||
|
if status and "errors" not in response and self.merchant_id == "None":
|
||||||
|
return True, ded(
|
||||||
|
f"""
|
||||||
|
<b>🟢 Lolzteam функционирует ✅</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Токен: <code>{self.token}</code>
|
||||||
|
▪️ Айди: <code>{response['user']['user_id']}</code>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
elif status and "errors" not in response:
|
||||||
|
# ! Проверка этим способом не работает для мерчантов, у которых нет отдельного
|
||||||
|
# ! баланса на маркете, и поэтому используется способ ниже
|
||||||
|
# balances = response["user"]["balances"]
|
||||||
|
|
||||||
|
# for balance in balances:
|
||||||
|
# if int(self.merchant_id) == balance["merchant_id"]:
|
||||||
|
# merchant_valid = True
|
||||||
|
|
||||||
|
# ! Создание оплаты и проверка возвращаемого статуса (если статус положительный - то и мерчант валидный)
|
||||||
|
bill_message, bill_link, bill_receipt = await self.bill(
|
||||||
|
1, is_test=True
|
||||||
|
) # Тестовый инвойс
|
||||||
|
|
||||||
|
if bill_message:
|
||||||
|
return True, ded(
|
||||||
|
f"""
|
||||||
|
<b>🟢 Lolzteam полностью функционирует ✅</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Токен: <code>{self.token}</code>
|
||||||
|
▪️ Айди: <code>{response['user']['user_id']}</code>
|
||||||
|
▪️ Айди Мерчанта: <code>{self.merchant_id}</code>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return False, "<b>🟢 Не удалось проверить Lolzteam ❌</b>"
|
||||||
|
|
||||||
|
# Получение баланса
|
||||||
|
async def balance(self) -> str:
|
||||||
|
status, response = await self._request("me", method="GET")
|
||||||
|
|
||||||
|
if status and "errors" not in response:
|
||||||
|
merchants = []
|
||||||
|
|
||||||
|
response_balances = sorted(
|
||||||
|
response["user"]["balances"],
|
||||||
|
reverse=True,
|
||||||
|
key=lambda balance: to_number(balance["balance"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
for balance in response_balances:
|
||||||
|
if balance["merchant_id"] == self.merchant_id:
|
||||||
|
title = balance["title"]
|
||||||
|
custom_title = balance["custom_title"]
|
||||||
|
|
||||||
|
merchants.append(
|
||||||
|
f"▪️ {title if not custom_title else custom_title}: <code>{balance['balance']} ₽</code>"
|
||||||
|
)
|
||||||
|
|
||||||
|
merchants = "\n".join(merchants)
|
||||||
|
|
||||||
|
return ded(f"""
|
||||||
|
<b>🟢 Баланс Lolzteam составляет</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
{merchants}
|
||||||
|
""")
|
||||||
|
|
||||||
|
return "<b>🟢 Не удалось получить баланс Lolzteam ❌</b>"
|
||||||
|
|
||||||
|
# Создание счета на оплату
|
||||||
|
async def bill(
|
||||||
|
self,
|
||||||
|
pay_amount: Union[float, int],
|
||||||
|
is_test: bool = False,
|
||||||
|
) -> Tuple[Union[str, bool], str, str]:
|
||||||
|
|
||||||
|
pro_payment_id = get_unix()
|
||||||
|
eu_payment_id = gen_id(12)
|
||||||
|
|
||||||
|
payment_id = str(pro_payment_id) + str(eu_payment_id)
|
||||||
|
|
||||||
|
bot_data = await self.bot.get_me()
|
||||||
|
bot_username = bot_data.username
|
||||||
|
bot_url = f"https://telegram.me/{bot_username}"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"currency": "rub",
|
||||||
|
"amount": pay_amount,
|
||||||
|
"payment_id": payment_id,
|
||||||
|
"comment": f"Пополнение #{payment_id}",
|
||||||
|
"url_success": bot_url,
|
||||||
|
"merchant_id": self.merchant_id,
|
||||||
|
"lifetime": 10800,
|
||||||
|
"is_test": is_test, # Тестовый режим для отладки
|
||||||
|
}
|
||||||
|
|
||||||
|
status, response = await self._request("invoice", payload)
|
||||||
|
|
||||||
|
if status and "errors" not in response:
|
||||||
|
bill_message = ded(f"""
|
||||||
|
<b>💰 Пополнение баланса</b>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||||
|
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
||||||
|
▪️ У вас имеется 3 часа на оплату счета
|
||||||
|
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
||||||
|
➖➖➖➖➖➖➖➖➖➖
|
||||||
|
❗️ После оплаты, нажмите на <code>Проверить оплату</code>
|
||||||
|
""")
|
||||||
|
|
||||||
|
return (
|
||||||
|
bill_message,
|
||||||
|
response["invoice"]["url"],
|
||||||
|
response["invoice"]["invoice_id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return False, "", ""
|
||||||
|
|
||||||
|
# Проверка счета на оплату
|
||||||
|
async def bill_check(
|
||||||
|
self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1
|
||||||
|
) -> Tuple[int, float]:
|
||||||
|
payload = {"invoice_id": int(bill_receipt)}
|
||||||
|
|
||||||
|
status, response = await self._request("invoice", payload, method="GET")
|
||||||
|
|
||||||
|
pay_status, pay_amount = 1, 0
|
||||||
|
|
||||||
|
if status and "errors" not in response:
|
||||||
|
get_invoice = response["invoice"]
|
||||||
|
status = get_invoice["status"]
|
||||||
|
|
||||||
|
time = response["system_info"]["time"]
|
||||||
|
expires_at = get_invoice["expires_at"]
|
||||||
|
|
||||||
|
# not_paid + time < expires_at
|
||||||
|
if (status == "not_paid") and (time < expires_at):
|
||||||
|
pay_status = 2
|
||||||
|
|
||||||
|
# not_paid + time > expires_at
|
||||||
|
elif (status == "expired") and (time > expires_at):
|
||||||
|
pay_status = 3
|
||||||
|
|
||||||
|
# paid
|
||||||
|
else:
|
||||||
|
pay_status = 0
|
||||||
|
pay_amount = to_number(get_invoice["amount"])
|
||||||
|
|
||||||
|
return pay_status, pay_amount
|
||||||
|
|
||||||
|
# Генерация запроса
|
||||||
|
async def _request(
|
||||||
|
self,
|
||||||
|
path: str,
|
||||||
|
data: Optional[Dict[str, Any]] = None,
|
||||||
|
method: str = "POST",
|
||||||
|
) -> Tuple[bool, Any]:
|
||||||
|
session = await self.arSession.get_session()
|
||||||
|
method = method.upper()
|
||||||
|
|
||||||
|
base_url = "https://prod-api.lzt.market/"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.token}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = base_url + path
|
||||||
|
|
||||||
|
try:
|
||||||
|
if method == "POST":
|
||||||
|
response = await session.post(url=url, headers=headers, json=data)
|
||||||
|
|
||||||
|
elif method == "GET":
|
||||||
|
response = await session.get(url=url, headers=headers, params=data)
|
||||||
|
|
||||||
|
response_data = json.loads((await response.read()).decode())
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
return True, response_data
|
||||||
|
else:
|
||||||
|
await self.error_notification(
|
||||||
|
f"{response.status} - {str(response_data)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return False, response_data
|
||||||
|
|
||||||
|
except ClientConnectorCertificateError:
|
||||||
|
bot_logger.warning("Ошибка SSL при запросе Lolzteam", exc_info=True)
|
||||||
|
await self.error_notification("CERTIFICATE_VERIFY_FAILED")
|
||||||
|
|
||||||
|
return False, "CERTIFICATE_VERIFY_FAILED"
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
bot_logger.warning("Ошибка запроса Lolzteam", exc_info=True)
|
||||||
|
await self.error_notification(str(ex))
|
||||||
|
|
||||||
|
return False, str(ex)
|
||||||
@@ -48,7 +48,7 @@ def ikb(
|
|||||||
if copy is not None:
|
if copy is not None:
|
||||||
return InlineKeyboardButton(text=text, copy_text=CopyTextButton(text=copy))
|
return InlineKeyboardButton(text=text, copy_text=CopyTextButton(text=copy))
|
||||||
if login is not None:
|
if login is not None:
|
||||||
return InlineKeyboardButton(text=text, url=f"https://t.me/{login}")
|
return InlineKeyboardButton(text=text, url=f"https://telegram.me/{login}")
|
||||||
|
|
||||||
raise ValueError("Не указано действие для inline-кнопки")
|
raise ValueError("Не указано действие для inline-кнопки")
|
||||||
|
|
||||||
|
|||||||
@@ -350,8 +350,9 @@ async def get_statistics() -> str:
|
|||||||
users_money_have,
|
users_money_have,
|
||||||
users_money_give,
|
users_money_give,
|
||||||
) = (0, 0, 0, 0, 0, 0)
|
) = (0, 0, 0, 0, 0, 0)
|
||||||
refill_cryptobot_count, refill_cryptobot_amount = 0, 0
|
|
||||||
refill_stars_count, refill_stars_amount = 0, 0
|
refill_cryptobot_count, refill_lolzteam_count, refill_cryptobot_amount = 0, 0, 0
|
||||||
|
refill_stars_count, refill_lolzteam_amount, refill_stars_amount = 0, 0, 0
|
||||||
|
|
||||||
get_categories = await Categoryx().get_all()
|
get_categories = await Categoryx().get_all()
|
||||||
get_positions = await Positionx().get_all()
|
get_positions = await Positionx().get_all()
|
||||||
@@ -384,6 +385,11 @@ async def get_statistics() -> str:
|
|||||||
if refill.refill_method == "Cryptobot":
|
if refill.refill_method == "Cryptobot":
|
||||||
refill_cryptobot_count += 1
|
refill_cryptobot_count += 1
|
||||||
refill_cryptobot_amount += refill.refill_amount
|
refill_cryptobot_amount += refill.refill_amount
|
||||||
|
|
||||||
|
elif refill.refill_method == "Lolzteam":
|
||||||
|
refill_lolzteam_count += 1
|
||||||
|
refill_lolzteam_amount += refill.refill_amount
|
||||||
|
|
||||||
elif refill.refill_method == "Stars":
|
elif refill.refill_method == "Stars":
|
||||||
refill_stars_count += 1
|
refill_stars_count += 1
|
||||||
refill_stars_amount += refill.refill_amount
|
refill_stars_amount += refill.refill_amount
|
||||||
@@ -483,6 +489,7 @@ async def get_statistics() -> str:
|
|||||||
┃
|
┃
|
||||||
┣‒ Платежные системы (всего)
|
┣‒ Платежные системы (всего)
|
||||||
┣ CryptoBot: <code>{refill_cryptobot_count}шт</code> - <code>{round(refill_cryptobot_amount, 2)}₽</code>
|
┣ CryptoBot: <code>{refill_cryptobot_count}шт</code> - <code>{round(refill_cryptobot_amount, 2)}₽</code>
|
||||||
|
┣ Lolzteam: <code>{refill_lolzteam_count}шт</code> - <code>{round(refill_lolzteam_amount, 2)}₽</code>
|
||||||
┣ TG Stars: <code>{refill_stars_count}шт</code> - <code>{round(refill_stars_amount, 2)}₽</code>
|
┣ TG Stars: <code>{refill_stars_count}шт</code> - <code>{round(refill_stars_amount, 2)}₽</code>
|
||||||
┃
|
┃
|
||||||
┣‒ Остальные
|
┣‒ Остальные
|
||||||
|
|||||||
Reference in New Issue
Block a user