Private
Public Access
forked from FOSS/AutoShop-Djimbo-Simple
feat(referral): add referral system with withdrawals
This commit is contained in:
@@ -6,7 +6,29 @@ from .db_purchases import PurchaseModel, Purchasesx
|
||||
from .db_refill import RefillModel, Refillx
|
||||
from .db_settings import SettingsModel, Settingsx
|
||||
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
|
||||
ModelItem = Item
|
||||
@@ -18,3 +40,7 @@ ModelSettings = Settings
|
||||
ModelUser = User
|
||||
ModelUsers = User
|
||||
SettingsRepository = Settingsx
|
||||
|
||||
ModelReferral = Referral
|
||||
ModelReferralTransaction = ReferralTransaction
|
||||
ModelReferralWithdrawal = ReferralWithdrawal
|
||||
|
||||
@@ -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(
|
||||
text(
|
||||
"""
|
||||
SELECT user_balance, user_refill
|
||||
SELECT user_balance, user_refill, user_referrer_id
|
||||
FROM storage_users
|
||||
WHERE user_id = :user_id
|
||||
"""
|
||||
@@ -103,6 +103,14 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
||||
await session.rollback()
|
||||
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_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},
|
||||
)
|
||||
|
||||
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()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
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 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_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
|
||||
BaseModel = Settings
|
||||
|
||||
+33
-17
@@ -15,8 +15,12 @@ from tgbot.utils.const_functions import get_unix
|
||||
class UserModel(Base):
|
||||
__tablename__ = "storage_users"
|
||||
|
||||
increment: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, unique=True, index=True)
|
||||
increment: Mapped[int] = mapped_column(
|
||||
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_name: 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_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
|
||||
BaseModel = User
|
||||
@@ -41,31 +52,34 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
||||
|
||||
# Добавление или обновление пользователя
|
||||
async def add(
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
) -> User:
|
||||
return await self.upsert(
|
||||
user_id=user_id,
|
||||
user_login=user_login,
|
||||
user_name=user_name,
|
||||
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
|
||||
async def upsert(
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
) -> 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__
|
||||
statement = insert(UserModel).values(
|
||||
user_id=user_id,
|
||||
@@ -105,7 +119,9 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
||||
return user
|
||||
|
||||
# Обновление пользователя по 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):
|
||||
where = {"user_id": where}
|
||||
|
||||
|
||||
@@ -97,6 +97,11 @@ class Settings:
|
||||
misc_profit_week: int
|
||||
misc_profit_month: int
|
||||
|
||||
# Реферальная система
|
||||
referral_refill_percent: float
|
||||
status_referral: str
|
||||
referral_bonus_rub: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
@@ -110,3 +115,40 @@ class User:
|
||||
user_refill: float
|
||||
user_give: float
|
||||
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,10 +160,13 @@ async def prepare_database() -> None:
|
||||
import tgbot.database.db_refill # noqa: F401
|
||||
import tgbot.database.db_settings # noqa: F401
|
||||
import tgbot.database.db_users # noqa: F401
|
||||
import tgbot.database.db_referral # noqa: F401
|
||||
|
||||
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)
|
||||
|
||||
@@ -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:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
@@ -207,6 +218,17 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
||||
else:
|
||||
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(
|
||||
ikb("❔ FAQ", data="..."),
|
||||
faq_kb,
|
||||
@@ -222,6 +244,12 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
||||
).row(
|
||||
ikb("🎁 Метод добавления", data="..."),
|
||||
method_prod_kb,
|
||||
).row(
|
||||
ikb("🤝 Бонус за приглашение", data="..."),
|
||||
referral_bonus_kb,
|
||||
).row(
|
||||
ikb("📈 Доход с пополнений", data="..."),
|
||||
deposit_percent_kb,
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -236,6 +264,7 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
||||
status_work_kb = ikb("Включены ✅", data="settings_status_work:False")
|
||||
status_buy_kb = ikb("Включены ✅", data="settings_status_buy: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_refill_kb = ikb(
|
||||
"Включены 🔔", data="settings_notification_refill:False"
|
||||
@@ -247,6 +276,8 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
||||
status_work_kb = ikb("Выключены ❌", data="settings_status_work:True")
|
||||
if get_settings.status_refill == "False":
|
||||
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":
|
||||
notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True")
|
||||
if get_settings.notification_refill == "False":
|
||||
@@ -263,6 +294,9 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
||||
).row(
|
||||
ikb("🎁 Покупки", data="..."),
|
||||
status_buy_kb,
|
||||
).row(
|
||||
ikb("🤝 Реферальная система", data="..."),
|
||||
status_referral_kb,
|
||||
).row(
|
||||
ikb("📢 Увед. о покупках", data="..."),
|
||||
notification_buy_kb,
|
||||
|
||||
@@ -33,6 +33,41 @@ def user_support_finl(support_login: str) -> InlineKeyboardMarkup:
|
||||
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()
|
||||
|
||||
|
||||
################################################################################
|
||||
################################### ПЛАТЕЖИ ####################################
|
||||
# Выбор способа пополнения
|
||||
|
||||
@@ -14,6 +14,8 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
|
||||
rkb("🎁 Купить"),
|
||||
rkb("👤 Профиль"),
|
||||
rkb("🧮 Наличие товаров"),
|
||||
).row(
|
||||
rkb("🤝 Реферальная система"),
|
||||
).row(
|
||||
rkb("☎️ Поддержка"),
|
||||
rkb("❔ FAQ"),
|
||||
|
||||
@@ -3,16 +3,37 @@ from aiogram import BaseMiddleware
|
||||
from cachetools import TTLCache
|
||||
|
||||
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.misc.bot_logging import bot_logger
|
||||
|
||||
|
||||
class ExistsUserMiddleware(BaseMiddleware):
|
||||
# Создание кеша пользователей
|
||||
def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None:
|
||||
self.users = Userx()
|
||||
self.referrals = Referralx()
|
||||
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-ом
|
||||
async def __call__(self, handler, event, data):
|
||||
this_user = data.get("event_from_user")
|
||||
@@ -32,6 +53,12 @@ class ExistsUserMiddleware(BaseMiddleware):
|
||||
)
|
||||
|
||||
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:
|
||||
user = await self.users.upsert(
|
||||
@@ -41,10 +68,48 @@ class ExistsUserMiddleware(BaseMiddleware):
|
||||
user_surname=user_data[2],
|
||||
user_fullname=user_data[3],
|
||||
)
|
||||
self.cache[user_id] = {"data": user_data, "user": user}
|
||||
else:
|
||||
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
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
+46
-12
@@ -2,8 +2,19 @@
|
||||
from aiogram import Dispatcher
|
||||
|
||||
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.user import user_menu, user_transactions, user_products
|
||||
from tgbot.routers.admin import (
|
||||
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
|
||||
|
||||
|
||||
@@ -23,17 +34,39 @@ def register_all_routers(dp: Dispatcher):
|
||||
user_products.router.callback_query.filter(IsPrivate())
|
||||
user_transactions.router.message.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.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_functions.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
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 роутера только для админов
|
||||
admin_menu.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_menu.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
admin_functions.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
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) # Роутер ошибки
|
||||
@@ -44,6 +77,7 @@ def register_all_routers(dp: Dispatcher):
|
||||
dp.include_router(admin_menu.router) # Админ роутер
|
||||
dp.include_router(user_products.router) # Юзер роутер
|
||||
dp.include_router(user_transactions.router) # Юзер роутер
|
||||
dp.include_router(user_referral.router) # Юзер роутер
|
||||
dp.include_router(admin_functions.router) # Админ роутер
|
||||
dp.include_router(admin_payments.router) # Админ роутер
|
||||
dp.include_router(admin_settings.router) # Админ роутер
|
||||
|
||||
@@ -5,18 +5,137 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
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.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_models import FSM, ARS
|
||||
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__)
|
||||
|
||||
|
||||
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 == "🔍 Поиск")
|
||||
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"))
|
||||
async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.callback_query(
|
||||
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]
|
||||
|
||||
send_message = (await state.get_data())['here_mail_message']
|
||||
send_message = (await state.get_data())["here_mail_message"]
|
||||
await state.clear()
|
||||
|
||||
if get_status == "Yes":
|
||||
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))
|
||||
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.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):
|
||||
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)
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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"))
|
||||
async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user']
|
||||
async def functions_user_balance_add_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user"]
|
||||
|
||||
if not is_number(message.text):
|
||||
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>",
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.debug("Не удалось уведомить пользователя %s о выдаче баланса", user_id, exc_info=True)
|
||||
bot_logger.debug(
|
||||
"Не удалось уведомить пользователя %s о выдаче баланса",
|
||||
user_id,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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"))
|
||||
async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user']
|
||||
async def functions_user_balance_set_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user"]
|
||||
|
||||
if not is_number(message.text):
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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"))
|
||||
async def functions_user_user_message_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user_id']
|
||||
async def functions_user_user_message_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user_id"]
|
||||
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)
|
||||
|
||||
try:
|
||||
await bot.send_message(user_id, get_message)
|
||||
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>")
|
||||
else:
|
||||
await message.reply("<b>✅ Сообщение было успешно доставлено</b>")
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from math import isfinite
|
||||
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from tgbot.database import Settingsx, Userx
|
||||
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_models import FSM, ARS
|
||||
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())
|
||||
|
||||
|
||||
# Включение/выключение реферальной системы
|
||||
@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:"))
|
||||
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"))
|
||||
async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
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):
|
||||
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:]
|
||||
|
||||
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,7 +5,7 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
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.services.api_cryptobot import CryptobotAPI
|
||||
from tgbot.services.api_lolzteam import LolzteamAPI
|
||||
@@ -459,4 +459,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
|
||||
|
||||
Reference in New Issue
Block a user