Compare commits

...

23 Commits

Author SHA1 Message Date
root 30a98f30c4 feat: add Lolzteam payment method and max refill limit 2026-07-18 13:33:09 +05:00
root 79bb03e5af merge upstream 2026-07-18 06:30:37 +00:00
root 215366051c chore: remove alembic database migration system 2026-07-18 11:30:08 +05:00
root 47f57ae2ba revert f328fdc705
revert chore: remove unused Alembic and supervisor config
2026-07-18 06:27:44 +00:00
root f328fdc705 chore: remove unused Alembic and supervisor config 2026-07-18 11:16:19 +05:00
root a04734b8a7 feat(referral): add referral system with withdrawals 2026-07-18 10:58:14 +05:00
root 5e5bab91f2 fix(api): handle None merchant_id in lolzteam initialization 2026-07-16 13:39:37 +05:00
root 0d34ac8f1c refactor(api_lolzteam): simplify merchant_id initialization logic 2026-07-16 13:29:33 +05:00
root fb80c9fcd8 chore(api): remove debug print statement 2026-07-16 13:07:35 +05:00
root 6f10f085ee docs: remove extra blank lines in README 2026-07-16 08:49:27 +05:00
root 55b31b28ca refactor(api): remove unused merchant_valid variable 2026-07-16 08:47:53 +05:00
root 30686aeabd refactor(api_lolzteam): improve merchant validation and request handling 2026-07-16 08:44:59 +05:00
root 7190963724 refactor: replace shell scripts with powershell startup 2026-07-16 07:00:36 +05:00
root 3fef026b6e style: format imports and line wrapping 2026-07-16 06:57:18 +05:00
root 8181394b40 fix(lolzteam): add type conversion and validation checks 2026-07-16 05:28:27 +05:00
root f2f8356d99 style(admin): format error message on single line 2026-07-16 04:57:03 +05:00
root 6697917b95 docs: add startup scripts and instructions for Windows and Linux 2026-07-16 04:50:29 +05:00
root c40fa63142 chore: remove Docker configuration 2026-07-16 04:45:20 +05:00
root 573dae70fb feat: add Lolzteam payment integration 2026-07-16 04:40:02 +05:00
root cb56f6757e Update README.md 2026-07-15 12:03:31 +00:00
root b7cd21bfc3 refactor: remove Yoomoney payment integration 2026-07-15 16:55:53 +05:00
root 572dc8d1b4 refactor(hosting): remove alternative text hosting providers 2026-07-15 16:07:15 +05:00
root ec5ae395f3 refactor: remove Alembic migrations and Discord integration 2026-07-15 15:55:25 +05:00
53 changed files with 3284 additions and 2721 deletions
-35
View File
@@ -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
+3 -1
View File
@@ -1,10 +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
YOOMONEY_CLIENT_ID=
-19
View File
@@ -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"]
+12 -31
View File
@@ -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. Поставь зависимости:
@@ -22,40 +31,12 @@ cp .env.example .env
```env ```env
BOT_TOKEN= BOT_TOKEN=
BOT_ADMIN_IDS= BOT_ADMIN_IDS=
YOOMONEY_CLIENT_ID=
``` ```
Конфигурация читается только из `.env` или переменных окружения. Конфигурация читается только из `.env` или переменных окружения.
5. Примени миграции: 5. Запусти бота:
```bash
python3 migrate.py up
```
6. Запусти бота:
```bash ```bash
python3 main.py python3 main.py
``` ```
## Миграции
Основные команды:
```bash
python3 migrate.py status
python3 migrate.py up
python3 migrate.py down
python3 migrate.py auto "change description"
```
Схема БД теперь живет в `migrations/`, модели — в `tgbot/database/db_*.py`.
## Docker
```bash
docker compose up -d --build
```
Контейнер сам применяет миграции перед запуском.
-42
View File
@@ -1,42 +0,0 @@
[alembic]
script_location = migrations
prepend_sys_path = .
path_separator = os
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
timezone = Europe/Moscow
sqlalchemy.url = sqlite+aiosqlite:///tgbot/data/database.db
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
-13
View File
@@ -1,13 +0,0 @@
# supervisorctl
[program:dj_shop]
directory=/root/autoshopDjimbo/
command=python3.11 -u main.py
environment=PYTHONUNBUFFERED="1"
autostart=True
autorestart=True
stderr_logfile=/root/autoshopDjimbo/tgbot/data/sv_log_err.log
stderr_logfile_maxbytes=50MB
stdout_logfile=/root/autoshopDjimbo/tgbot/data/sv_log_out.log
stdout_logfile_maxbytes=50MB
-11
View File
@@ -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
+12 -6
View File
@@ -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:
-188
View File
@@ -1,188 +0,0 @@
# - *- coding: utf- 8 - *-
import argparse
from typing import Optional
from alembic import command
from alembic.config import Config
HELP_TEXT = """
Миграции базы данных
Безопасное правило:
python migrate.py только показывает эту справку
действия с БД выполняются только при явной команде
Основные команды:
python migrate.py help показать эту справку
python migrate.py up применить все миграции до head
python migrate.py down откатить последнюю миграцию
python migrate.py new "add users" создать пустую миграцию
python migrate.py auto "add users" создать миграцию по SQLAlchemy-моделям
python migrate.py status показать текущую версию и последние версии
Короткие алиасы:
up -> upgrade
down -> downgrade
new -> revision
auto -> revision --autogenerate
autogen -> auto
cur -> current
hist -> history
st -> status
""".strip()
# Сбор CLI-парсера миграций
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Удобный CLI для Alembic-миграций",
formatter_class=argparse.RawTextHelpFormatter,
)
subparsers = parser.add_subparsers(dest="command")
help_parser = subparsers.add_parser("help", aliases=["h"], help="Показать понятную справку")
help_parser.set_defaults(action=show_help)
upgrade_parser = subparsers.add_parser("upgrade", aliases=["up"], help="Применить миграции")
upgrade_parser.add_argument("revision", nargs="?", default="head", help="Версия миграции, по умолчанию head")
upgrade_parser.set_defaults(action=run_upgrade)
downgrade_parser = subparsers.add_parser("downgrade", aliases=["down"], help="Откатить миграции")
downgrade_parser.add_argument("revision", nargs="?", default="-1", help="Версия отката, по умолчанию -1")
downgrade_parser.set_defaults(action=run_downgrade)
revision_parser = subparsers.add_parser("revision", aliases=["new"], help="Создать пустую миграцию")
revision_parser.add_argument("message", nargs="?", help="Название миграции")
revision_parser.add_argument("-m", "--message-option", dest="message_option", help="Название миграции")
revision_parser.add_argument("--autogenerate", "-a", action="store_true", help="Собрать изменения из моделей")
revision_parser.set_defaults(action=run_revision)
auto_parser = subparsers.add_parser("auto", aliases=["autogen"], help="Создать миграцию по моделям")
auto_parser.add_argument("message", nargs="?", help="Название миграции")
auto_parser.add_argument("-m", "--message-option", dest="message_option", help="Название миграции")
auto_parser.set_defaults(action=run_auto_revision)
current_parser = subparsers.add_parser("current", aliases=["cur"], help="Показать текущую версию БД")
current_parser.set_defaults(action=run_current)
history_parser = subparsers.add_parser("history", aliases=["hist"], help="Показать историю миграций")
history_parser.set_defaults(action=run_history)
heads_parser = subparsers.add_parser("heads", help="Показать последние версии веток")
heads_parser.set_defaults(action=run_heads)
status_parser = subparsers.add_parser("status", aliases=["st"], help="Показать текущую версию и heads")
status_parser.set_defaults(action=run_status)
return parser
# Просмотр справки по миграциям
def show_help(_config: Optional[Config], _args: argparse.Namespace) -> None:
print(HELP_TEXT)
# Применение миграций до версии
def run_upgrade(config: Config, args: argparse.Namespace) -> None:
print(f"Применяю миграции до версии: {args.revision}")
command.upgrade(config, args.revision)
print("Готово")
# Откат миграции до версии
def run_downgrade(config: Config, args: argparse.Namespace) -> None:
print(f"Откатываю миграции до версии: {args.revision}")
command.downgrade(config, args.revision)
print("Готово")
# Создание пустой миграции
def run_revision(config: Config, args: argparse.Namespace) -> None:
message = get_revision_message(args)
print(f"Создаю миграцию: {message}")
config.attributes["autogenerate_empty"] = False
command.revision(config, message=message, autogenerate=args.autogenerate)
if config.attributes.get("autogenerate_empty"):
print("Изменений в моделях не найдено, файл миграции не создан")
return
print("Готово")
# Создание миграции по моделям
def run_auto_revision(config: Config, args: argparse.Namespace) -> None:
message = get_revision_message(args)
print(f"Создаю миграцию по моделям: {message}")
config.attributes["autogenerate_empty"] = False
command.revision(config, message=message, autogenerate=True)
if config.attributes.get("autogenerate_empty"):
print("Изменений в моделях не найдено, файл миграции не создан")
return
print("Готово")
# Просмотр текущей версии БД
def run_current(config: Config, _args: argparse.Namespace) -> None:
print("Текущая версия БД:")
command.current(config)
# Просмотр истории миграций
def run_history(config: Config, _args: argparse.Namespace) -> None:
print("История миграций:")
command.history(config)
# Просмотр heads миграций
def run_heads(config: Config, _args: argparse.Namespace) -> None:
print("Последние версии миграций:")
command.heads(config)
# Просмотр текущей версии и heads
def run_status(config: Config, args: argparse.Namespace) -> None:
run_current(config, args)
print()
run_heads(config, args)
# Получение названия миграции
def get_revision_message(args: argparse.Namespace) -> str:
message: Optional[str] = args.message_option or args.message
if not message:
raise SystemExit("Укажи название миграции. Пример: python migrate.py auto \"add users\"")
return message
# Получение Alembic-конфига
def get_config() -> Config:
from tgbot.database.migration_runner import get_alembic_config
return get_alembic_config()
# Запуск CLI миграций
def main() -> None:
parser = build_parser()
args = parser.parse_args()
if args.command is None:
show_help(None, args)
return
if args.action == show_help:
show_help(None, args)
return
if args.action in (run_revision, run_auto_revision):
get_revision_message(args)
config = get_config()
args.action(config, args)
if __name__ == "__main__":
main()
-184
View File
@@ -1,184 +0,0 @@
# - *- coding: utf- 8 - *-
import asyncio
from logging.config import fileConfig
from typing import Any, List, Optional
from alembic import context
from alembic.operations import ops
from sqlalchemy import pool, text
from sqlalchemy.engine import Connection
from sqlalchemy.schema import DefaultClause
from sqlalchemy.ext.asyncio import async_engine_from_config
from tgbot.database.core import Base
import tgbot.database # noqa: F401
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
target_metadata = Base.metadata
INDEX_DIFF_NOISE = {"ix_storage_payments_id", "ix_storage_settings_id"}
# Отбрасывание SQLite-шума из автогенерации
def _filter_autogenerated_ops(container: Any) -> None:
filtered_ops = []
for operation in container.ops:
if isinstance(operation, ops.AlterColumnOp):
continue
if _is_index_noise(operation):
continue
if isinstance(operation, ops.ModifyTableOps):
_filter_autogenerated_ops(operation)
if operation.ops:
filtered_ops.append(operation)
continue
filtered_ops.append(operation)
container.ops = filtered_ops
# Подготовка новых колонок к безопасному SQLite ADD COLUMN
def _prepare_sqlite_add_columns(container: Any) -> None:
for operation in container.ops:
if isinstance(operation, ops.AddColumnOp):
_apply_server_default(operation.column)
continue
if isinstance(operation, ops.ModifyTableOps):
_prepare_sqlite_add_columns(operation)
# Перенос default модели в server_default миграции
def _apply_server_default(column: Any) -> None:
if column.nullable or column.server_default is not None or column.default is None:
return
default_value = _get_simple_default(column.default.arg)
if default_value is None:
return
column.server_default = DefaultClause(text(default_value))
# Получение SQL-литерала для простых default-значений
def _get_simple_default(value: Any) -> Optional[str]:
if callable(value):
return None
if isinstance(value, bool):
return "1" if value else "0"
if isinstance(value, (int, float)):
return str(value)
if isinstance(value, str):
return repr(value)
return None
# Игнорирование индексов, которые SQLite отражает иначе
def _is_index_noise(operation: Any) -> bool:
if not isinstance(operation, (ops.CreateIndexOp, ops.DropIndexOp)):
return False
return getattr(operation, "index_name", "") in INDEX_DIFF_NOISE
# Ограничение сравнения SQLite только реальными добавлениями/удалениями
def _include_object(object_: Any, name: str, type_: str, reflected: bool, compare_to: Any) -> bool:
if type_ == "column" and compare_to is not None:
return False
if type_ == "index" and name in INDEX_DIFF_NOISE:
return False
return True
# Очистка автосозданной миграции перед записью
def _process_revision_directives(_context: Any, _revision: Any, directives: List[Any]) -> None:
if not directives:
return
script = directives[0]
_prepare_sqlite_add_columns(script.upgrade_ops)
_filter_autogenerated_ops(script.upgrade_ops)
_filter_autogenerated_ops(script.downgrade_ops)
if not script.upgrade_ops.ops and not script.downgrade_ops.ops:
config.attributes["autogenerate_empty"] = True
directives.clear()
# Запуск миграций без подключения
def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
render_as_batch=True,
compare_type=False,
include_object=_include_object,
process_revision_directives=_process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
# Запуск миграций на соединении
def do_run_migrations(connection: Connection) -> None:
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True,
compare_type=False,
include_object=_include_object,
process_revision_directives=_process_revision_directives,
)
with context.begin_transaction():
context.run_migrations()
# Запуск миграций через async-engine
async def run_async_migrations() -> None:
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
poolclass=pool.NullPool,
)
connection = connectable.connect()
try:
await connection.start()
await connection.run_sync(do_run_migrations)
finally:
await connection.close()
await connectable.dispose()
if context.is_offline_mode():
run_migrations_offline()
else:
external_connection = config.attributes.get("connection")
if external_connection is not None:
do_run_migrations(external_connection)
else:
asyncio.run(run_async_migrations())
-24
View File
@@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}
@@ -1,515 +0,0 @@
"""Начальная схема Телеграм-магазина
Revision ID: 0001_shop_schema
Revises:
Create Date: 2026-05-29 00:00:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0001_shop_schema"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
# Проверка наличия таблицы
def _table_exists(table_name: str) -> bool:
bind = op.get_bind()
result = bind.execute(
sa.text(
"""
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name = :table_name
"""
),
{"table_name": table_name},
)
return result.scalar_one_or_none() is not None
# Проверка наличия колонки
def _column_exists(table_name: str, column_name: str) -> bool:
bind = op.get_bind()
result = bind.execute(sa.text(f"PRAGMA table_info({table_name})"))
return column_name in [row.name for row in result]
# Добавление колонки, если ее нет
def _add_column(table_name: str, column_name: str, column_sql: str) -> None:
if not _column_exists(table_name, column_name):
op.execute(sa.text(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_sql}"))
# Создание индекса, если его нет
def _create_index(index_name: str, table_name: str, columns: str, *, unique: bool = False) -> None:
unique_sql = "UNIQUE " if unique else ""
op.execute(sa.text(f"CREATE {unique_sql}INDEX IF NOT EXISTS {index_name} ON {table_name} ({columns})"))
# Нормализация текстовых bool-значений
def _normalize_boolean_text(table_name: str, column_name: str, default_value: str) -> None:
bind = op.get_bind()
bind.execute(
sa.text(
f"""
UPDATE {table_name}
SET {column_name} = CASE LOWER(CAST(COALESCE({column_name}, :default_value) AS TEXT))
WHEN '1' THEN 'True'
WHEN 'true' THEN 'True'
WHEN 'yes' THEN 'True'
WHEN 'on' THEN 'True'
WHEN 'да' THEN 'True'
WHEN '0' THEN 'False'
WHEN 'false' THEN 'False'
WHEN 'no' THEN 'False'
WHEN 'off' THEN 'False'
WHEN 'нет' THEN 'False'
ELSE :default_value
END
"""
),
{"default_value": default_value},
)
# Сбор SQL для переноса текстовой колонки
def _copy_expr(table_name: str, column_name: str, default_value: str) -> str:
if _column_exists(table_name, column_name):
return f"COALESCE({column_name}, '{default_value}')"
return f"'{default_value}'"
# Сбор SQL для переноса числовой колонки
def _copy_int_expr(table_name: str, column_name: str, default_value: int = 0) -> str:
if _column_exists(table_name, column_name):
return f"COALESCE({column_name}, {default_value})"
return str(default_value)
# Создание или правка таблицы пользователей
def _ensure_users() -> None:
bind = op.get_bind()
if not _table_exists("storage_users"):
op.create_table(
"storage_users",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("user_login", sa.String(length=255), nullable=False, server_default=""),
sa.Column("user_name", sa.String(length=255), nullable=False, server_default=""),
sa.Column("user_surname", sa.String(length=255), nullable=False, server_default=""),
sa.Column("user_fullname", sa.String(length=511), nullable=False, server_default=""),
sa.Column("user_balance", sa.Float(), nullable=False, server_default="0"),
sa.Column("user_refill", sa.Float(), nullable=False, server_default="0"),
sa.Column("user_give", sa.Float(), nullable=False, server_default="0"),
sa.Column("user_unix", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_users", "user_login", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_users", "user_name", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_users", "user_surname", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_users", "user_fullname", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_users", "user_balance", "REAL NOT NULL DEFAULT 0")
_add_column("storage_users", "user_refill", "REAL NOT NULL DEFAULT 0")
_add_column("storage_users", "user_give", "REAL NOT NULL DEFAULT 0")
_add_column("storage_users", "user_unix", "INTEGER NOT NULL DEFAULT 0")
bind.execute(sa.text("UPDATE storage_users SET user_login = COALESCE(user_login, '')"))
bind.execute(sa.text("UPDATE storage_users SET user_name = COALESCE(user_name, '')"))
bind.execute(sa.text("UPDATE storage_users SET user_surname = COALESCE(user_surname, '')"))
bind.execute(
sa.text(
"""
UPDATE storage_users
SET user_fullname = CASE
WHEN user_fullname IS NULL OR user_fullname = '' THEN TRIM(user_name || ' ' || user_surname)
ELSE user_fullname
END
"""
)
)
bind.execute(sa.text("UPDATE storage_users SET user_balance = COALESCE(user_balance, 0)"))
bind.execute(sa.text("UPDATE storage_users SET user_refill = COALESCE(user_refill, 0)"))
bind.execute(sa.text("UPDATE storage_users SET user_give = COALESCE(user_give, 0)"))
bind.execute(sa.text("UPDATE storage_users SET user_unix = COALESCE(user_unix, 0)"))
bind.execute(sa.text("DELETE FROM storage_users WHERE user_id IS NULL"))
bind.execute(
sa.text(
"""
DELETE
FROM storage_users
WHERE user_id IS NOT NULL
AND rowid NOT IN (
SELECT MAX(rowid)
FROM storage_users
WHERE user_id IS NOT NULL
GROUP BY user_id
)
"""
)
)
_create_index("ix_storage_users_user_id", "storage_users", "user_id", unique=True)
# Создание или правка таблицы настроек
def _ensure_settings() -> None:
bind = op.get_bind()
if not _table_exists("storage_settings"):
op.create_table(
"storage_settings",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("status_work", sa.String(length=16), nullable=False, server_default="True"),
sa.Column("status_refill", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("status_buy", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("notification_refill", sa.String(length=16), nullable=False, server_default="True"),
sa.Column("notification_buy", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_faq", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_support", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_bot", sa.String(length=255), nullable=False, server_default="None"),
sa.Column("misc_hosting_text", sa.String(length=64), nullable=False, server_default="telegraph"),
sa.Column("misc_token_telegraph", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_discord_webhook_url", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_discord_webhook_name", sa.String(length=255), nullable=False, server_default="None"),
sa.Column("misc_hide_category", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_hide_position", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_profit_day", sa.Integer(), nullable=False, server_default="0"),
sa.Column("misc_profit_week", sa.Integer(), nullable=False, server_default="0"),
sa.Column("misc_profit_month", sa.Integer(), nullable=False, server_default="0"),
sa.PrimaryKeyConstraint("id"),
)
else:
bind.execute(sa.text("DROP TABLE IF EXISTS storage_settings_new"))
op.create_table(
"storage_settings_new",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("status_work", sa.String(length=16), nullable=False, server_default="True"),
sa.Column("status_refill", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("status_buy", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("notification_refill", sa.String(length=16), nullable=False, server_default="True"),
sa.Column("notification_buy", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_faq", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_support", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_bot", sa.String(length=255), nullable=False, server_default="None"),
sa.Column("misc_hosting_text", sa.String(length=64), nullable=False, server_default="telegraph"),
sa.Column("misc_token_telegraph", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_discord_webhook_url", sa.String(), nullable=False, server_default="None"),
sa.Column("misc_discord_webhook_name", sa.String(length=255), nullable=False, server_default="None"),
sa.Column("misc_hide_category", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_hide_position", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("misc_profit_day", sa.Integer(), nullable=False, server_default="0"),
sa.Column("misc_profit_week", sa.Integer(), nullable=False, server_default="0"),
sa.Column("misc_profit_month", sa.Integer(), nullable=False, server_default="0"),
sa.PrimaryKeyConstraint("id"),
)
bind.execute(
sa.text(
f"""
INSERT INTO storage_settings_new (
id,
status_work,
status_refill,
status_buy,
notification_refill,
notification_buy,
misc_faq,
misc_support,
misc_bot,
misc_hosting_text,
misc_token_telegraph,
misc_discord_webhook_url,
misc_discord_webhook_name,
misc_hide_category,
misc_hide_position,
misc_profit_day,
misc_profit_week,
misc_profit_month
)
SELECT
1,
{_copy_expr("storage_settings", "status_work", "True")},
{_copy_expr("storage_settings", "status_refill", "False")},
{_copy_expr("storage_settings", "status_buy", "False")},
{_copy_expr("storage_settings", "notification_refill", "True")},
{_copy_expr("storage_settings", "notification_buy", "False")},
{_copy_expr("storage_settings", "misc_faq", "None")},
{_copy_expr("storage_settings", "misc_support", "None")},
{_copy_expr("storage_settings", "misc_bot", "None")},
{_copy_expr("storage_settings", "misc_hosting_text", "telegraph")},
{_copy_expr("storage_settings", "misc_token_telegraph", "None")},
{_copy_expr("storage_settings", "misc_discord_webhook_url", "None")},
{_copy_expr("storage_settings", "misc_discord_webhook_name", "None")},
{_copy_expr("storage_settings", "misc_hide_category", "False")},
{_copy_expr("storage_settings", "misc_hide_position", "False")},
{_copy_int_expr("storage_settings", "misc_profit_day")},
{_copy_int_expr("storage_settings", "misc_profit_week")},
{_copy_int_expr("storage_settings", "misc_profit_month")}
FROM storage_settings
LIMIT 1
"""
)
)
bind.execute(sa.text("DROP TABLE storage_settings"))
bind.execute(sa.text("ALTER TABLE storage_settings_new RENAME TO storage_settings"))
bind.execute(
sa.text(
"""
INSERT INTO storage_settings (id)
SELECT 1
WHERE NOT EXISTS (SELECT 1 FROM storage_settings WHERE id = 1)
"""
)
)
for column, default in (
("status_work", "True"),
("status_refill", "False"),
("status_buy", "False"),
("notification_refill", "True"),
("notification_buy", "False"),
("misc_hide_category", "False"),
("misc_hide_position", "False"),
):
_normalize_boolean_text("storage_settings", column, default)
_create_index("ix_storage_settings_id", "storage_settings", "id", unique=True)
# Создание или правка таблицы платежей
def _ensure_payments() -> None:
bind = op.get_bind()
if not _table_exists("storage_payments"):
op.create_table(
"storage_payments",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("cryptobot_token", sa.String(), nullable=False, server_default="None"),
sa.Column("yoomoney_token", sa.String(), nullable=False, server_default="None"),
sa.Column("status_cryptobot", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("status_yoomoney", sa.String(length=16), nullable=False, server_default="False"),
sa.PrimaryKeyConstraint("id"),
)
else:
bind.execute(sa.text("DROP TABLE IF EXISTS storage_payments_new"))
op.create_table(
"storage_payments_new",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("cryptobot_token", sa.String(), nullable=False, server_default="None"),
sa.Column("yoomoney_token", sa.String(), nullable=False, server_default="None"),
sa.Column("status_cryptobot", sa.String(length=16), nullable=False, server_default="False"),
sa.Column("status_yoomoney", sa.String(length=16), nullable=False, server_default="False"),
sa.PrimaryKeyConstraint("id"),
)
bind.execute(
sa.text(
f"""
INSERT INTO storage_payments_new (
id,
cryptobot_token,
yoomoney_token,
status_cryptobot,
status_yoomoney
)
SELECT
1,
{_copy_expr("storage_payments", "cryptobot_token", "None")},
{_copy_expr("storage_payments", "yoomoney_token", "None")},
{_copy_expr("storage_payments", "status_cryptobot", "False")},
{_copy_expr("storage_payments", "status_yoomoney", "False")}
FROM storage_payments
LIMIT 1
"""
)
)
bind.execute(sa.text("DROP TABLE storage_payments"))
bind.execute(sa.text("ALTER TABLE storage_payments_new RENAME TO storage_payments"))
bind.execute(
sa.text(
"""
INSERT INTO storage_payments (id)
SELECT 1
WHERE NOT EXISTS (SELECT 1 FROM storage_payments WHERE id = 1)
"""
)
)
_normalize_boolean_text("storage_payments", "status_cryptobot", "False")
_normalize_boolean_text("storage_payments", "status_yoomoney", "False")
_create_index("ix_storage_payments_id", "storage_payments", "id", unique=True)
# Создание или правка таблицы категорий
def _ensure_category() -> None:
if not _table_exists("storage_category"):
op.create_table(
"storage_category",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("category_id", sa.BigInteger(), nullable=False),
sa.Column("category_name", sa.String(length=255), nullable=False),
sa.Column("category_unix", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_category", "category_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_category", "category_name", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_category", "category_unix", "INTEGER NOT NULL DEFAULT 0")
_create_index("ix_storage_category_category_id", "storage_category", "category_id")
# Создание или правка таблицы позиций
def _ensure_position() -> None:
if not _table_exists("storage_position"):
op.create_table(
"storage_position",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("category_id", sa.BigInteger(), nullable=False),
sa.Column("position_id", sa.BigInteger(), nullable=False),
sa.Column("position_name", sa.String(length=255), nullable=False),
sa.Column("position_price", sa.Float(), nullable=False, server_default="0"),
sa.Column("position_desc", sa.Text(), nullable=False, server_default="None"),
sa.Column("position_photo", sa.Text(), nullable=False, server_default="None"),
sa.Column("position_unix", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_position", "category_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_position", "position_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_position", "position_name", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_position", "position_price", "REAL NOT NULL DEFAULT 0")
_add_column("storage_position", "position_desc", "TEXT NOT NULL DEFAULT 'None'")
_add_column("storage_position", "position_photo", "TEXT NOT NULL DEFAULT 'None'")
_add_column("storage_position", "position_unix", "INTEGER NOT NULL DEFAULT 0")
_create_index("ix_storage_position_category_id", "storage_position", "category_id")
_create_index("ix_storage_position_position_id", "storage_position", "position_id")
# Создание или правка таблицы товаров
def _ensure_item() -> None:
if not _table_exists("storage_item"):
op.create_table(
"storage_item",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("category_id", sa.BigInteger(), nullable=False),
sa.Column("position_id", sa.BigInteger(), nullable=False),
sa.Column("item_id", sa.BigInteger(), nullable=False),
sa.Column("item_unix", sa.Integer(), nullable=False),
sa.Column("item_data", sa.Text(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_item", "user_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_item", "category_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_item", "position_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_item", "item_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_item", "item_unix", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_item", "item_data", "TEXT NOT NULL DEFAULT ''")
_create_index("ix_storage_item_user_id", "storage_item", "user_id")
_create_index("ix_storage_item_category_id", "storage_item", "category_id")
_create_index("ix_storage_item_position_id", "storage_item", "position_id")
_create_index("ix_storage_item_item_id", "storage_item", "item_id")
# Создание или правка таблицы покупок
def _ensure_purchases() -> None:
if not _table_exists("storage_purchases"):
op.create_table(
"storage_purchases",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("user_balance_before", sa.Float(), nullable=False, server_default="0"),
sa.Column("user_balance_after", sa.Float(), nullable=False, server_default="0"),
sa.Column("purchase_receipt", sa.BigInteger(), nullable=False),
sa.Column("purchase_data", sa.Text(), nullable=False),
sa.Column("purchase_count", sa.Integer(), nullable=False, server_default="1"),
sa.Column("purchase_price", sa.Float(), nullable=False, server_default="0"),
sa.Column("purchase_price_one", sa.Float(), nullable=False, server_default="0"),
sa.Column("purchase_position_id", sa.BigInteger(), nullable=False),
sa.Column("purchase_position_name", sa.String(length=255), nullable=False),
sa.Column("purchase_category_id", sa.BigInteger(), nullable=False),
sa.Column("purchase_category_name", sa.String(length=255), nullable=False),
sa.Column("purchase_unix", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_purchases", "user_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_purchases", "user_balance_before", "REAL NOT NULL DEFAULT 0")
_add_column("storage_purchases", "user_balance_after", "REAL NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_receipt", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_data", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_purchases", "purchase_count", "INTEGER NOT NULL DEFAULT 1")
_add_column("storage_purchases", "purchase_price", "REAL NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_price_one", "REAL NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_position_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_position_name", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_purchases", "purchase_category_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_purchases", "purchase_category_name", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_purchases", "purchase_unix", "INTEGER NOT NULL DEFAULT 0")
_create_index("ix_storage_purchases_user_id", "storage_purchases", "user_id")
_create_index("ix_storage_purchases_purchase_receipt", "storage_purchases", "purchase_receipt")
_create_index("ix_storage_purchases_purchase_position_id", "storage_purchases", "purchase_position_id")
_create_index("ix_storage_purchases_purchase_category_id", "storage_purchases", "purchase_category_id")
# Создание или правка таблицы пополнений
def _ensure_refill() -> None:
if not _table_exists("storage_refill"):
op.create_table(
"storage_refill",
sa.Column("increment", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("refill_receipt", sa.BigInteger(), nullable=False),
sa.Column("refill_comment", sa.String(length=255), nullable=False, server_default=""),
sa.Column("refill_amount", sa.Float(), nullable=False, server_default="0"),
sa.Column("refill_method", sa.String(length=64), nullable=False),
sa.Column("refill_unix", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("increment"),
)
else:
_add_column("storage_refill", "user_id", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_refill", "refill_receipt", "INTEGER NOT NULL DEFAULT 0")
_add_column("storage_refill", "refill_comment", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_refill", "refill_amount", "REAL NOT NULL DEFAULT 0")
_add_column("storage_refill", "refill_method", "TEXT NOT NULL DEFAULT ''")
_add_column("storage_refill", "refill_unix", "INTEGER NOT NULL DEFAULT 0")
_create_index("ix_storage_refill_user_id", "storage_refill", "user_id")
_create_index("ix_storage_refill_refill_receipt", "storage_refill", "refill_receipt")
# Применение начальной схемы магазина
def upgrade() -> None:
_ensure_users()
_ensure_settings()
_ensure_payments()
_ensure_category()
_ensure_position()
_ensure_item()
_ensure_purchases()
_ensure_refill()
# Откат начальной схемы магазина
def downgrade() -> None:
op.drop_table("storage_refill")
op.drop_table("storage_purchases")
op.drop_table("storage_item")
op.drop_table("storage_position")
op.drop_table("storage_category")
op.drop_table("storage_payments")
op.drop_table("storage_settings")
op.drop_table("storage_users")
@@ -1,32 +0,0 @@
"""add column 'status_stars' into storage_payments
Revision ID: 492769059249
Revises: 0001_shop_schema
Create Date: 2026-05-30 19:15:00.214301+03:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '492769059249'
down_revision: Union[str, None] = '0001_shop_schema'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_payments', schema=None) as batch_op:
batch_op.add_column(sa.Column('status_stars', sa.String(length=16), server_default=sa.text("'False'"), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_payments', schema=None) as batch_op:
batch_op.drop_column('status_stars')
# ### end Alembic commands ###
@@ -1,32 +0,0 @@
"""add column 'stars_course' into storage_payment
Revision ID: 700275128e27
Revises: 492769059249
Create Date: 2026-05-30 19:28:42.490458+03:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '700275128e27'
down_revision: Union[str, None] = '492769059249'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_payments', schema=None) as batch_op:
batch_op.add_column(sa.Column('stars_course', sa.Float(), server_default=sa.text('(1.5)'), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_payments', schema=None) as batch_op:
batch_op.drop_column('stars_course')
# ### end Alembic commands ###
@@ -1,32 +0,0 @@
"""add column 'misc_method_prod' into 'storage_settings'
Revision ID: 36380377b0e6
Revises: 700275128e27
Create Date: 2026-06-01 19:34:35.858117+03:00
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = '36380377b0e6'
down_revision: Union[str, None] = '700275128e27'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_settings', schema=None) as batch_op:
batch_op.add_column(sa.Column('misc_method_prod', sa.String(length=16), server_default=sa.text("'skip'"), nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('storage_settings', schema=None) as batch_op:
batch_op.drop_column('misc_method_prod')
# ### end Alembic commands ###
-2
View File
@@ -13,7 +13,6 @@ dependencies = [
"aiofiles>=25.1,<26.0", "aiofiles>=25.1,<26.0",
"aiohttp>=3.13.5,<4.0", "aiohttp>=3.13.5,<4.0",
"aiosqlite>=0.20,<1.0", "aiosqlite>=0.20,<1.0",
"alembic>=1.13,<2.0",
"cachetools>=7.1,<8.0", "cachetools>=7.1,<8.0",
"certifi>=2025.8,<2027.0", "certifi>=2025.8,<2027.0",
"colorama>=0.4,<1.0", "colorama>=0.4,<1.0",
@@ -28,4 +27,3 @@ dependencies = [
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["tgbot*"] include = ["tgbot*"]
exclude = ["migrations*"]
-1
View File
@@ -3,7 +3,6 @@ aiogram==3.28.2
aiofiles==25.1.0 aiofiles==25.1.0
aiohttp==3.13.5 aiohttp==3.13.5
aiosqlite==0.22.1 aiosqlite==0.22.1
alembic==1.18.4
cachetools==7.1.4 cachetools==7.1.4
certifi==2026.5.20 certifi==2026.5.20
colorama==0.4.6 colorama==0.4.6
+2
View File
@@ -0,0 +1,2 @@
@echo off
powershell -ExecutionPolicy Bypass -File "%~dp0start.ps1"
+52
View File
@@ -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
+35 -32
View File
@@ -28,16 +28,29 @@ class Settings(BaseSettings):
default=True, default=True,
validation_alias="BOT_DATABASE_EXPORT", validation_alias="BOT_DATABASE_EXPORT",
) )
status_notification: bool = Field(default=True, validation_alias="BOT_STATUS_NOTIFICATION") status_notification: bool = Field(
default=True, validation_alias="BOT_STATUS_NOTIFICATION"
)
timezone: str = Field(default="Europe/Moscow", validation_alias="BOT_TIMEZONE") timezone: str = Field(default="Europe/Moscow", validation_alias="BOT_TIMEZONE")
database_path: str = Field(default="tgbot/data/database.db", validation_alias="PATH_DATABASE") database_path: str = Field(
default="tgbot/data/database.db", validation_alias="PATH_DATABASE"
)
logs_path: str = Field(default="tgbot/data/logs.log", validation_alias="PATH_LOGS") logs_path: str = Field(default="tgbot/data/logs.log", validation_alias="PATH_LOGS")
user_cache_ttl: int = Field(default=300, ge=0, validation_alias="BOT_USER_CACHE_TTL") user_cache_ttl: int = Field(
throttle_rate: float = Field(default=0.5, ge=0, validation_alias="BOT_THROTTLE_RATE") default=300, ge=0, validation_alias="BOT_USER_CACHE_TTL"
yoomoney_client_id: str = Field(default="", validation_alias="YOOMONEY_CLIENT_ID") )
throttle_rate: float = Field(
default=0.5, ge=0, validation_alias="BOT_THROTTLE_RATE"
)
# Очистка строковых значений из окружения # Очистка строковых значений из окружения
@field_validator("bot_token", "timezone", "database_path", "logs_path", "yoomoney_client_id", mode="before") @field_validator(
"bot_token",
"timezone",
"database_path",
"logs_path",
mode="before",
)
@classmethod @classmethod
def _strip_string(cls, value: object) -> str: def _strip_string(cls, value: object) -> str:
return str(value or "").strip() return str(value or "").strip()
@@ -52,11 +65,15 @@ class Settings(BaseSettings):
if isinstance(value, int): if isinstance(value, int):
values = [value] values = [value]
elif isinstance(value, str): elif isinstance(value, str):
values = [admin_id for admin_id in value.replace(" ", "").split(",") if admin_id] values = [
admin_id for admin_id in value.replace(" ", "").split(",") if admin_id
]
elif isinstance(value, (list, tuple, set)): elif isinstance(value, (list, tuple, set)):
values = list(value) values = list(value)
else: else:
raise ValueError("BOT_ADMIN_IDS должен быть числом или списком чисел через запятую") raise ValueError(
"BOT_ADMIN_IDS должен быть числом или списком чисел через запятую"
)
admin_ids = [] admin_ids = []
@@ -64,10 +81,14 @@ class Settings(BaseSettings):
try: try:
parsed_id = int(admin_id) parsed_id = int(admin_id)
except (TypeError, ValueError) as error: except (TypeError, ValueError) as error:
raise ValueError("BOT_ADMIN_IDS должен содержать только Телеграм ID через запятую") from error raise ValueError(
"BOT_ADMIN_IDS должен содержать только Телеграм ID через запятую"
) from error
if parsed_id <= 0: if parsed_id <= 0:
raise ValueError("BOT_ADMIN_IDS должен содержать Телеграм ID больше нуля") raise ValueError(
"BOT_ADMIN_IDS должен содержать Телеграм ID больше нуля"
)
admin_ids.append(parsed_id) admin_ids.append(parsed_id)
@@ -80,7 +101,9 @@ class Settings(BaseSettings):
try: try:
timezone(value) timezone(value)
except UnknownTimeZoneError as error: except UnknownTimeZoneError as error:
raise ValueError("BOT_TIMEZONE должен быть корректной временной зоной, например Europe/Moscow") from error raise ValueError(
"BOT_TIMEZONE должен быть корректной временной зоной, например Europe/Moscow"
) from error
return value return value
@@ -126,7 +149,6 @@ BOT_DATABASE_EXPORT = settings.database_export
BOT_TIMEZONE = settings.timezone BOT_TIMEZONE = settings.timezone
BOT_USER_CACHE_TTL = settings.user_cache_ttl BOT_USER_CACHE_TTL = settings.user_cache_ttl
BOT_THROTTLE_RATE = settings.throttle_rate BOT_THROTTLE_RATE = settings.throttle_rate
YOOMONEY_CLIENT_ID = settings.yoomoney_client_id
BOT_SCHEDULER = AsyncIOScheduler(timezone=BOT_TIMEZONE) BOT_SCHEDULER = AsyncIOScheduler(timezone=BOT_TIMEZONE)
BOT_VERSION = 4.2 BOT_VERSION = 4.2
@@ -146,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> бота, он ничем не сможет помочь. Бот является
открытым и распространяется публично. Абсолютно любой желающий мог его запустить.
""")
+27 -1
View File
@@ -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
+77 -4
View File
@@ -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
@@ -14,17 +15,89 @@ class PaymentsModel(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1) id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
cryptobot_token: Mapped[str] = mapped_column(String, nullable=False, default="None") cryptobot_token: Mapped[str] = mapped_column(String, nullable=False, default="None")
yoomoney_token: Mapped[str] = mapped_column(String, nullable=False, default="None")
stars_course: Mapped[float] = mapped_column(Float, nullable=False, default=1.5) stars_course: Mapped[float] = mapped_column(Float, nullable=False, default=1.5)
status_cryptobot: Mapped[str] = mapped_column(String(16), nullable=False, default="False") status_cryptobot: Mapped[str] = mapped_column(
status_yoomoney: Mapped[str] = mapped_column(String(16), nullable=False, default="False") String(16), nullable=False, default="False"
status_stars: Mapped[str] = mapped_column(String(16), nullable=False, default="False") )
status_stars: Mapped[str] = mapped_column(
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):
+23 -17
View File
@@ -13,14 +13,17 @@ from tgbot.utils.const_functions import get_unix
class PositionModel(Base): class PositionModel(Base):
__tablename__ = "storage_position" __tablename__ = "storage_position"
increment: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) increment: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
category_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True) category_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
position_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True) position_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
position_name: Mapped[str] = mapped_column(String(255), nullable=False) position_name: Mapped[str] = mapped_column(String(255), nullable=False)
position_price: Mapped[float] = mapped_column(Float, nullable=False, default=0) position_price: Mapped[float] = mapped_column(Float, nullable=False, default=0)
position_desc: Mapped[str] = mapped_column(Text, nullable=False, default="None") position_desc: Mapped[str] = mapped_column(Text, nullable=False, default="None")
position_photo: Mapped[str] = mapped_column(Text, nullable=False, default="None") position_unix: Mapped[int] = mapped_column(
position_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix) Integer, nullable=False, default=get_unix
)
ModelBase = Position ModelBase = Position
@@ -37,13 +40,12 @@ class Positionx(BaseRepository[PositionModel, Position]):
# Добавление позиции товара # Добавление позиции товара
async def add( async def add(
self, self,
category_id: int, category_id: int,
position_id: int, position_id: int,
position_name: str, position_name: str,
position_price: float, position_price: float,
position_desc: str, position_desc: str,
position_photo: str,
) -> Position: ) -> Position:
return await self._insert( return await self._insert(
category_id=category_id, category_id=category_id,
@@ -51,12 +53,13 @@ class Positionx(BaseRepository[PositionModel, Position]):
position_name=position_name, position_name=position_name,
position_price=position_price, position_price=position_price,
position_desc=position_desc, position_desc=position_desc,
position_photo=position_photo,
position_unix=get_unix(), position_unix=get_unix(),
) )
# Обновление позиции по 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 = {"position_id": where} where = {"position_id": where}
@@ -68,12 +71,15 @@ class Positionx(BaseRepository[PositionModel, Position]):
from tgbot.database.db_item import ItemModel from tgbot.database.db_item import ItemModel
item_table = ItemModel.__table__ item_table = ItemModel.__table__
statement = ( statement = select(
select(item_table.c.position_id, func.count(item_table.c.increment).label("item_count")) item_table.c.position_id,
.group_by(item_table.c.position_id) func.count(item_table.c.increment).label("item_count"),
) ).group_by(item_table.c.position_id)
async with session_scope() as session: async with session_scope() as session:
rows = await session.execute(statement) rows = await session.execute(statement)
return {int(position_id): int(item_count) for position_id, item_count in rows.all()} return {
int(position_id): int(item_count)
for position_id, item_count in rows.all()
}
+728
View File
@@ -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)
+69 -1
View File
@@ -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()
+34 -11
View File
@@ -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
@@ -14,24 +14,47 @@ class SettingsModel(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1) id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
status_work: Mapped[str] = mapped_column(String(16), nullable=False, default="True") status_work: Mapped[str] = mapped_column(String(16), nullable=False, default="True")
status_refill: Mapped[str] = mapped_column(String(16), nullable=False, default="False") status_refill: Mapped[str] = mapped_column(
String(16), nullable=False, default="False"
)
status_buy: Mapped[str] = mapped_column(String(16), nullable=False, default="False") status_buy: Mapped[str] = mapped_column(String(16), nullable=False, default="False")
notification_refill: Mapped[str] = mapped_column(String(16), nullable=False, default="True") notification_refill: Mapped[str] = mapped_column(
notification_buy: Mapped[str] = mapped_column(String(16), nullable=False, default="False") String(16), nullable=False, default="True"
)
notification_buy: Mapped[str] = mapped_column(
String(16), nullable=False, default="False"
)
misc_faq: Mapped[str] = mapped_column(String, nullable=False, default="None") misc_faq: Mapped[str] = mapped_column(String, nullable=False, default="None")
misc_support: Mapped[str] = mapped_column(String, nullable=False, default="None") misc_support: Mapped[str] = mapped_column(String, nullable=False, default="None")
misc_bot: Mapped[str] = mapped_column(String(255), nullable=False, default="None") misc_bot: Mapped[str] = mapped_column(String(255), nullable=False, default="None")
misc_hosting_text: Mapped[str] = mapped_column(String(64), nullable=False, default="telegraph") misc_hosting_text: Mapped[str] = mapped_column(
misc_token_telegraph: Mapped[str] = mapped_column(String, nullable=False, default="None") String(64), nullable=False, default="telegraph"
misc_discord_webhook_url: Mapped[str] = mapped_column(String, nullable=False, default="None") )
misc_discord_webhook_name: Mapped[str] = mapped_column(String(255), nullable=False, default="None") misc_token_telegraph: Mapped[str] = mapped_column(
misc_hide_category: Mapped[str] = mapped_column(String(16), nullable=False, default="False") String, nullable=False, default="None"
misc_hide_position: Mapped[str] = mapped_column(String(16), nullable=False, default="False") )
misc_method_prod: Mapped[str] = mapped_column(String(16), nullable=False, default="skip") misc_hide_category: Mapped[str] = mapped_column(
String(16), nullable=False, default="False"
)
misc_hide_position: Mapped[str] = mapped_column(
String(16), nullable=False, default="False"
)
misc_method_prod: Mapped[str] = mapped_column(
String(16), nullable=False, default="skip"
)
misc_profit_day: Mapped[int] = mapped_column(Integer, nullable=False, default=0) misc_profit_day: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
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
View File
@@ -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}
+49 -5
View File
@@ -1,5 +1,6 @@
# - *- coding: utf- 8 - *- # - *- coding: utf- 8 - *-
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
@dataclass @dataclass
@@ -18,7 +19,6 @@ class Position:
position_name: str position_name: str
position_price: float position_price: float
position_desc: str position_desc: str
position_photo: str
position_unix: int position_unix: int
@@ -37,12 +37,16 @@ class Item:
class Payments: class Payments:
id: int id: int
cryptobot_token: str cryptobot_token: str
yoomoney_token: str
stars_course: float stars_course: float
status_cryptobot: str status_cryptobot: str
status_yoomoney: 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:
@@ -86,8 +90,6 @@ class Settings:
misc_bot: str misc_bot: str
misc_hosting_text: str misc_hosting_text: str
misc_token_telegraph: str misc_token_telegraph: str
misc_discord_webhook_url: str
misc_discord_webhook_name: str
misc_hide_category: str misc_hide_category: str
misc_hide_position: str misc_hide_position: str
misc_method_prod: str misc_method_prod: str
@@ -95,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:
@@ -108,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]
-55
View File
@@ -1,55 +0,0 @@
# - *- coding: utf- 8 - *-
import asyncio
from pathlib import Path
from typing import Optional
from alembic import command
from alembic.config import Config
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import AsyncEngine
from tgbot.database.core import database_url
PROJECT_ROOT = Path(__file__).resolve().parents[2]
ALEMBIC_INI = PROJECT_ROOT / "alembic.ini"
MIGRATIONS_DIR = PROJECT_ROOT / "migrations"
# Сбор Alembic-конфига от корня проекта
def get_alembic_config(url: str = database_url) -> Config:
config = Config(str(ALEMBIC_INI))
config.set_main_option("script_location", str(MIGRATIONS_DIR))
config.set_main_option("sqlalchemy.url", url)
return config
# Применение миграций до последней версии
async def run_migrations(engine: Optional[AsyncEngine] = None) -> None:
if engine is None:
config = get_alembic_config()
await asyncio.to_thread(command.upgrade, config, "head")
return
config = get_alembic_config(str(engine.url))
connection = engine.connect()
await connection.start()
transaction = connection.begin()
await transaction.start()
try:
await connection.run_sync(_upgrade_with_connection, config)
except Exception:
await transaction.rollback()
raise
else:
await transaction.commit()
finally:
await connection.close()
# Запуск Alembic на готовом соединении
def _upgrade_with_connection(connection: Connection, config: Config) -> None:
config.attributes["connection"] = connection
command.upgrade(config, "head")
+21 -3
View File
@@ -5,8 +5,7 @@ from typing import Any, Dict, Generic, List, Optional, Type, TypeVar
from sqlalchemy import delete as sqlalchemy_delete from sqlalchemy import delete as sqlalchemy_delete
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy import update as sqlalchemy_update from sqlalchemy import update as sqlalchemy_update
from tgbot.database.core import Base, database_path, session_scope from tgbot.database.core import Base, database_path, engine, session_scope
from tgbot.database.migration_runner import run_migrations
from tgbot.utils.misc.bot_logging import bot_logger from tgbot.utils.misc.bot_logging import bot_logger
ModelTranslator = TypeVar("ModelTranslator", bound=Base) ModelTranslator = TypeVar("ModelTranslator", bound=Base)
@@ -151,7 +150,26 @@ class BaseRepository(Generic[ModelTranslator, EntityTranslator]):
# Применение миграций и создание дефолтных строк # Применение миграций и создание дефолтных строк
async def prepare_database() -> None: async def prepare_database() -> None:
database_path.parent.mkdir(parents=True, exist_ok=True) database_path.parent.mkdir(parents=True, exist_ok=True)
await run_migrations()
# Импорт всех моделей до create_all, чтобы они зарегистрировались в Base.metadata
import tgbot.database.db_category # noqa: F401
import tgbot.database.db_item # noqa: F401
import tgbot.database.db_payments # noqa: F401
import tgbot.database.db_position # noqa: F401
import tgbot.database.db_purchases # noqa: F401
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)
from tgbot.database.db_payments import Paymentsx from tgbot.database.db_payments import Paymentsx
from tgbot.database.db_settings import Settingsx from tgbot.database.db_settings import Settingsx
+90 -37
View File
@@ -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()
@@ -73,9 +84,15 @@ async def payment_cryptobot_finl() -> InlineKeyboardMarkup:
assets_symbol = "" assets_symbol = ""
if get_payments.status_cryptobot == "True": if get_payments.status_cryptobot == "True":
status_kb = ikb(f"{assets_symbol} | Статус: Включено ✅", data="payment_cryptobot_status:False") status_kb = ikb(
f"{assets_symbol} | Статус: Включено ✅",
data="payment_cryptobot_status:False",
)
else: else:
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_cryptobot_status:True") status_kb = ikb(
f"{assets_symbol} | Статус: Выключено ❌",
data="payment_cryptobot_status:True",
)
keyboard.row( keyboard.row(
ikb("Информация ♻️", data="payment_cryptobot_check"), ikb("Информация ♻️", data="payment_cryptobot_check"),
@@ -92,28 +109,34 @@ async def payment_cryptobot_finl() -> InlineKeyboardMarkup:
return keyboard.as_markup() return keyboard.as_markup()
# Управление - ЮMoney # Управление - Lolzteam
async def payment_yoomoney_finl() -> InlineKeyboardMarkup: async def payment_lolzteam_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
if get_payments.yoomoney_token == "None": if get_payments.lolzteam_token == "None":
assets_symbol = "" assets_symbol = ""
else: else:
assets_symbol = "" assets_symbol = ""
if get_payments.status_yoomoney == "True": if get_payments.status_lolzteam == "True":
status_kb = ikb(f"{assets_symbol} | Статус: Включено ✅", data="payment_yoomoney_status:False") status_kb = ikb(
f"{assets_symbol} | Статус: Включено ✅",
data="payment_lolzteam_status:False",
)
else: else:
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_yoomoney_status:True") status_kb = ikb(
f"{assets_symbol} | Статус: Выключено ❌",
data="payment_lolzteam_status:True",
)
keyboard.row( keyboard.row(
ikb("Информация ♻️", data="payment_yoomoney_check"), ikb("Информация ♻️", data="payment_lolzteam_check"),
).row( ).row(
ikb("Баланс 💰", data="payment_yoomoney_balance"), ikb("Баланс 💰", data="payment_lolzteam_balance"),
).row( ).row(
ikb("Изменить 🖍", data="payment_yoomoney_edit"), ikb("Изменить 🖍", data="payment_lolzteam_edit"),
).row( ).row(
ikb("", data="..."), ikb("", data="..."),
).row( ).row(
@@ -132,9 +155,13 @@ async def payment_stars_finl() -> InlineKeyboardMarkup:
assets_symbol = "" assets_symbol = ""
if get_payments.status_stars == "True": if get_payments.status_stars == "True":
status_kb = ikb(f"{assets_symbol} | Статус: Включено ✅", data="payment_stars_status:False") status_kb = ikb(
f"{assets_symbol} | Статус: Включено ✅", data="payment_stars_status:False"
)
else: else:
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_stars_status:True") status_kb = ikb(
f"{assets_symbol} | Статус: Выключено ❌", data="payment_stars_status:True"
)
keyboard.row( keyboard.row(
ikb("Информация ♻️", data="payment_stars_check"), ikb("Информация ♻️", data="payment_stars_check"),
@@ -169,16 +196,9 @@ async def settings_finl() -> InlineKeyboardMarkup:
if get_settings.misc_support == "None": if get_settings.misc_support == "None":
support_kb = ikb("Не установлена ❌", data="settings_edit_support") support_kb = ikb("Не установлена ❌", data="settings_edit_support")
else: else:
support_kb = ikb(f"@{get_settings.misc_support}", data="settings_edit_support") support_kb = ikb(
f"@{get_settings.misc_support}", data="settings_edit_support"
# Вебхук дискорда )
if get_settings.misc_discord_webhook_url == "None":
discord_webhook_kb = ikb("Отсутствует ❌", data="settings_edit_discord_webhook")
else:
discord_webhook_kb = ikb(f"{get_settings.misc_discord_webhook_name}", data="settings_edit_discord_webhook")
# Текстовый хостинг по умолчанию
hosting_text_default_kb = ikb(get_settings.misc_hosting_text.title(), data="settings_edit_hosting_text")
# Скрытие категорий без товаров # Скрытие категорий без товаров
if get_settings.misc_hide_category == "True": if get_settings.misc_hide_category == "True":
@@ -198,20 +218,38 @@ 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="..."), faq_kb, ikb("❔ FAQ", data="..."),
faq_kb,
).row( ).row(
ikb("☎️ Поддержка", data="..."), support_kb, ikb("☎️ Поддержка", data="..."),
support_kb,
).row( ).row(
ikb("🧿 Дискорд Webhook", url="https://teletype.in/@djimbox/djimboshop-discord"), discord_webhook_kb, ikb("🎁 Категории без товаров", data="..."),
hide_category_kb,
).row( ).row(
ikb("🗯 Текстовый хостинг", data="..."), hosting_text_default_kb, ikb("🎁 Позиции без товаров", data="..."),
hide_position_kb,
).row( ).row(
ikb("🎁 Категории без товаров", data="..."), hide_category_kb, ikb("🎁 Метод добавления", data="..."),
method_prod_kb,
).row( ).row(
ikb("🎁 Позиции без товаров", data="..."), hide_position_kb, ikb("🤝 Бонус за приглашение", data="..."),
referral_bonus_kb,
).row( ).row(
ikb("🎁 Метод добавления", data="..."), method_prod_kb, ikb("📈 Доход с пополнений", data="..."),
deposit_percent_kb,
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -226,8 +264,11 @@ 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("Включены 🔔", data="settings_notification_refill:False") notification_refill_kb = ikb(
"Включены 🔔", data="settings_notification_refill:False"
)
if get_settings.status_buy == "False": if get_settings.status_buy == "False":
status_buy_kb = ikb("Выключены ❌", data="settings_status_buy:True") status_buy_kb = ikb("Выключены ❌", data="settings_status_buy:True")
@@ -235,21 +276,33 @@ 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":
notification_refill_kb = ikb("Выключены 🔕", data="settings_notification_refill:True") notification_refill_kb = ikb(
"Выключены 🔕", data="settings_notification_refill:True"
)
keyboard.row( keyboard.row(
ikb("⛔ Тех. работы", data="..."), status_work_kb, ikb("⛔ Тех. работы", data="..."),
status_work_kb,
).row( ).row(
ikb("💰 Пополнения", data="..."), status_refill_kb, ikb("💰 Пополнения", data="..."),
status_refill_kb,
).row( ).row(
ikb("🎁 Покупки", data="..."), status_buy_kb, ikb("🎁 Покупки", data="..."),
status_buy_kb,
).row( ).row(
ikb("📢 Увед. о покупках", data="..."), notification_buy_kb, ikb("🤝 Реферальная система", data="..."),
status_referral_kb,
).row( ).row(
ikb("📢 Увед. о пополнениях", data="..."), notification_refill_kb, ikb("📢 Увед. о покупках", data="..."),
notification_buy_kb,
).row(
ikb("📢 Увед. о пополнениях", data="..."),
notification_refill_kb,
) )
return keyboard.as_markup() return keyboard.as_markup()
+36 -15
View File
@@ -10,7 +10,9 @@ from tgbot.utils.const_functions import ikb
################################################################################ ################################################################################
################################### КАТЕГОРИИ ################################## ################################### КАТЕГОРИИ ##################################
# Изменение категории # Изменение категории
async def category_edit_open_finl(bot: Bot, category_id: int, remover: int) -> InlineKeyboardMarkup: async def category_edit_open_finl(
bot: Bot, category_id: int, remover: int
) -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
get_bot = await bot.get_me() get_bot = await bot.get_me()
@@ -19,7 +21,10 @@ async def category_edit_open_finl(bot: Bot, category_id: int, remover: int) -> I
ikb("▪️ Изм. Название", data=f"category_edit_name:{category_id}:{remover}"), ikb("▪️ Изм. Название", data=f"category_edit_name:{category_id}:{remover}"),
ikb("▪️ Добавить позицию", data=f"position_add_open:{category_id}"), ikb("▪️ Добавить позицию", data=f"position_add_open:{category_id}"),
).row( ).row(
ikb("▪️ Скопировать ссылку", copy=f"t.me/{get_bot.username}?start=c_{category_id}"), ikb(
"▪️ Скопировать ссылку",
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(
ikb("🔙 Вернуться", data=f"category_edit_swipe:{remover}"), ikb("🔙 Вернуться", data=f"category_edit_swipe:{remover}"),
@@ -34,8 +39,11 @@ def category_edit_delete_finl(category_id: int, remover: int) -> InlineKeyboardM
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
keyboard.row( keyboard.row(
ikb("✅ Да, удалить", data=f"category_edit_delete_confirm:{category_id}:{remover}"), ikb(
ikb("❌ Нет, отменить", data=f"category_edit_open:{category_id}:{remover}") "✅ Да, удалить",
data=f"category_edit_delete_confirm:{category_id}:{remover}",
),
ikb("❌ Нет, отменить", data=f"category_edit_open:{category_id}:{remover}"),
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -55,7 +63,9 @@ def category_edit_cancel_finl(category_id: int, remover: int) -> InlineKeyboardM
################################################################################ ################################################################################
#################################### ПОЗИЦИИ ################################### #################################### ПОЗИЦИИ ###################################
# Кнопки при открытии позиции для изменения # Кнопки при открытии позиции для изменения
async def position_edit_open_finl(bot: Bot, position_id: int, remover: int) -> InlineKeyboardMarkup: async def position_edit_open_finl(
bot: Bot, position_id: int, remover: int
) -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
get_position = await Positionx().get_required(position_id=position_id) get_position = await Positionx().get_required(position_id=position_id)
@@ -66,7 +76,6 @@ async def position_edit_open_finl(bot: Bot, position_id: int, remover: int) -> I
ikb("▪️ Изм. Цену", data=f"position_edit_price:{position_id}:{remover}"), ikb("▪️ Изм. Цену", data=f"position_edit_price:{position_id}:{remover}"),
).row( ).row(
ikb("▪️ Изм. Описание", data=f"position_edit_desc:{position_id}:{remover}"), ikb("▪️ Изм. Описание", data=f"position_edit_desc:{position_id}:{remover}"),
ikb("▪️ Изм. Фото", data=f"position_edit_photo:{position_id}:{remover}"),
).row( ).row(
ikb("▪️ Добавить Товары", data=f"item_add_position_open:{position_id}"), ikb("▪️ Добавить Товары", data=f"item_add_position_open:{position_id}"),
ikb("▪️ Выгрузить Товары", data=f"position_edit_items:{position_id}:{remover}"), ikb("▪️ Выгрузить Товары", data=f"position_edit_items:{position_id}:{remover}"),
@@ -74,10 +83,16 @@ async def position_edit_open_finl(bot: Bot, position_id: int, remover: int) -> I
ikb("▪️ Очистить Товары", data=f"position_edit_clear:{position_id}:{remover}"), ikb("▪️ Очистить Товары", data=f"position_edit_clear:{position_id}:{remover}"),
ikb("▪️ Удалить Товар", data=f"item_delete_swipe:{position_id}:0"), ikb("▪️ Удалить Товар", data=f"item_delete_swipe:{position_id}:0"),
).row( ).row(
ikb("▪️ Скопировать ссылку", copy=f"t.me/{get_bot.username}?start=p_{position_id}"), ikb(
"▪️ Скопировать ссылку",
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(
ikb("🔙 Вернуться", data=f"position_edit_swipe:{get_position.category_id}:{remover}"), ikb(
"🔙 Вернуться",
data=f"position_edit_swipe:{get_position.category_id}:{remover}",
),
ikb("▪️ Обновить", data=f"position_edit_open:{position_id}:{remover}"), ikb("▪️ Обновить", data=f"position_edit_open:{position_id}:{remover}"),
) )
@@ -89,8 +104,11 @@ def position_edit_delete_finl(position_id: int, remover: int) -> InlineKeyboardM
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
keyboard.row( keyboard.row(
ikb("✅ Да, удалить", data=f"position_edit_delete_confirm:{position_id}:{remover}"), ikb(
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}") "✅ Да, удалить",
data=f"position_edit_delete_confirm:{position_id}:{remover}",
),
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}"),
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -101,8 +119,11 @@ def position_edit_clear_finl(position_id: int, remover: int) -> InlineKeyboardMa
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
keyboard.row( keyboard.row(
ikb("✅ Да, очистить", data=f"position_edit_clear_confirm:{position_id}:{remover}"), ikb(
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}") "✅ Да, очистить",
data=f"position_edit_clear_confirm:{position_id}:{remover}",
),
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}"),
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -168,7 +189,7 @@ def products_removes_categories_finl() -> InlineKeyboardMarkup:
keyboard.row( keyboard.row(
ikb("✅ Да, удалить все", data="prod_removes_categories_confirm"), ikb("✅ Да, удалить все", data="prod_removes_categories_confirm"),
ikb("❌ Нет, отменить", data="prod_removes_return") ikb("❌ Нет, отменить", data="prod_removes_return"),
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -180,7 +201,7 @@ def products_removes_positions_finl() -> InlineKeyboardMarkup:
keyboard.row( keyboard.row(
ikb("✅ Да, удалить все", data="prod_removes_positions_confirm"), ikb("✅ Да, удалить все", data="prod_removes_positions_confirm"),
ikb("❌ Нет, отменить", data="prod_removes_return") ikb("❌ Нет, отменить", data="prod_removes_return"),
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -192,7 +213,7 @@ def products_removes_items_finl() -> InlineKeyboardMarkup:
keyboard.row( keyboard.row(
ikb("✅ Да, удалить все", data="prod_removes_items_confirm"), ikb("✅ Да, удалить все", data="prod_removes_items_confirm"),
ikb("❌ Нет, отменить", data="prod_removes_return") ikb("❌ Нет, отменить", data="prod_removes_return"),
) )
return keyboard.as_markup() return keyboard.as_markup()
+43 -6
View File
@@ -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,8 +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_yoomoney == "True": if get_payments.status_lolzteam == "True":
keyboard.row(ikb("🔮 ЮMoney", data="user_refill_method:Yoomoney")) 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"))
@@ -54,7 +89,9 @@ async def refill_method_finl() -> InlineKeyboardMarkup:
# Проверка платежа # Проверка платежа
def refill_bill_finl(pay_link: str, pay_receipt: Union[str, int], pay_method: str) -> InlineKeyboardMarkup: def refill_bill_finl(
pay_link: str, pay_receipt: Union[str, int], pay_method: str
) -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
keyboard.row( keyboard.row(
@@ -74,8 +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_yoomoney == "True": if get_payments.status_lolzteam == "True":
keyboard.row(ikb("🔮 ЮMoney", data="user_refill_method:Yoomoney")) 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"))
+26 -10
View File
@@ -11,16 +11,24 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("🎁 Купить"), rkb("👤 Профиль"), rkb("🧮 Наличие товаров"), rkb("🎁 Купить"),
rkb("👤 Профиль"),
rkb("🧮 Наличие товаров"),
).row( ).row(
rkb("☎️ Поддержка"), rkb("❔ FAQ"), rkb("🤝 Реферальная система"),
).row(
rkb("☎️ Поддержка"),
rkb("❔ FAQ"),
) )
if user_id in get_admins(): if user_id in get_admins():
keyboard.row( keyboard.row(
rkb("🎁 Управление товарами"), rkb("📊 Статистика"), rkb("🎁 Управление товарами"),
rkb("📊 Статистика"),
).row( ).row(
rkb("⚙️ Настройки"), rkb("🔆 Общие функции"), rkb("🔑 Платежные системы"), rkb("⚙️ Настройки"),
rkb("🔆 Общие функции"),
rkb("🔑 Платежные системы"),
) )
return keyboard.as_markup(resize_keyboard=True) return keyboard.as_markup(resize_keyboard=True)
@@ -31,7 +39,9 @@ def payments_frep() -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("🔷 CryptoBot"), rkb("🔮 ЮMoney"), rkb("⭐️ Звёзды"), rkb("🔷 CryptoBot"),
rkb("🟢 Lolzteam"),
rkb("⭐️ Звёзды"),
).row( ).row(
rkb("🔙 Главное меню"), rkb("🔙 Главное меню"),
) )
@@ -44,7 +54,8 @@ def functions_frep() -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("🔍 Поиск"), rkb("📢 Рассылка"), rkb("🔍 Поиск"),
rkb("📢 Рассылка"),
).row( ).row(
rkb("🔙 Главное меню"), rkb("🔙 Главное меню"),
) )
@@ -57,7 +68,8 @@ def settings_frep() -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("🖍 Изменить данные"), rkb("🕹 Выключатели"), rkb("🖍 Изменить данные"),
rkb("🕹 Выключатели"),
).row( ).row(
rkb("🔙 Главное меню"), rkb("🔙 Главное меню"),
) )
@@ -70,11 +82,15 @@ def items_frep() -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("📁 Создать позицию "), rkb("🗃 Создать категорию ➕"), rkb("📁 Создать позицию "),
rkb("🗃 Создать категорию ➕"),
).row( ).row(
rkb("📁 Изменить позицию 🖍"), rkb("🗃 Изменить категорию 🖍"), rkb("📁 Изменить позицию 🖍"),
rkb("🗃 Изменить категорию 🖍"),
).row( ).row(
rkb("🔙 Главное меню"), rkb("🎁 Добавить товары "), rkb("❌ Удаление"), rkb("🔙 Главное меню"),
rkb("🎁 Добавить товары "),
rkb("❌ Удаление"),
) )
return keyboard.as_markup(resize_keyboard=True) return keyboard.as_markup(resize_keyboard=True)
+67 -2
View File
@@ -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
View File
@@ -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) # Админ роутер
+175 -23
View File
@@ -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>")
+156 -74
View File
@@ -4,12 +4,16 @@ from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from tgbot.database import Paymentsx from tgbot.database import Paymentsx
from tgbot.keyboards.inline_admin import payment_yoomoney_finl, close_finl, payment_cryptobot_finl, payment_stars_finl from tgbot.keyboards.inline_admin import (
close_finl,
payment_cryptobot_finl,
payment_lolzteam_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.services.api_yoomoney import YoomoneyAPI
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_logging import bot_logger
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__) router = Router(name=__name__)
@@ -17,7 +21,9 @@ router = Router(name=__name__)
# Управление - CryptoBot # Управление - CryptoBot
@router.message(F.text == "🔷 CryptoBot") @router.message(F.text == "🔷 CryptoBot")
async def payments_cryptobot_open(message: Message, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_open(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await message.answer( await message.answer(
@@ -26,14 +32,16 @@ async def payments_cryptobot_open(message: Message, bot: Bot, state: FSM, arSess
) )
# Управление - ЮMoney # Управление - Lolzteam
@router.message(F.text == "🔮 ЮMoney") @router.message(F.text == "🟢 Lolzteam")
async def payments_yoomoney_open(message: Message, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_open(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await message.answer( await message.answer(
"<b>🔮 Управление - ЮMoney</b>", "<b>🟢 Управление - Lolzteam</b>",
reply_markup=await payment_yoomoney_finl(), reply_markup=await payment_lolzteam_finl(),
) )
@@ -52,7 +60,9 @@ async def payments_stars_open(message: Message, bot: Bot, state: FSM, arSession:
################################### CRYPTOBOT ################################## ################################### CRYPTOBOT ##################################
# Баланс - CryptoBot # Баланс - CryptoBot
@router.callback_query(F.data == "payment_cryptobot_balance") @router.callback_query(F.data == "payment_cryptobot_balance")
async def payments_cryptobot_balance(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_balance(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
response = await ( response = await (
await CryptobotAPI.connect( await CryptobotAPI.connect(
bot=bot, bot=bot,
@@ -70,7 +80,9 @@ async def payments_cryptobot_balance(call: CallbackQuery, bot: Bot, state: FSM,
# Информация - CryptoBot # Информация - CryptoBot
@router.callback_query(F.data == "payment_cryptobot_check") @router.callback_query(F.data == "payment_cryptobot_check")
async def payments_cryptobot_check(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_check(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
status, response = await ( status, response = await (
await CryptobotAPI.connect( await CryptobotAPI.connect(
bot=bot, bot=bot,
@@ -88,7 +100,9 @@ async def payments_cryptobot_check(call: CallbackQuery, bot: Bot, state: FSM, ar
# Изменение - CryptoBot # Изменение - CryptoBot
@router.callback_query(F.data == "payment_cryptobot_edit") @router.callback_query(F.data == "payment_cryptobot_edit")
async def payments_cryptobot_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_edit(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.set_state("here_cryptobot_token") await state.set_state("here_cryptobot_token")
await call.message.edit_text( await call.message.edit_text(
ded(f""" ded(f"""
@@ -102,13 +116,17 @@ async def payments_cryptobot_edit(call: CallbackQuery, bot: Bot, state: FSM, arS
# Выключатель - CryptoBot # Выключатель - CryptoBot
@router.callback_query(F.data.startswith("payment_cryptobot_status:")) @router.callback_query(F.data.startswith("payment_cryptobot_status:"))
async def payments_cryptobot_status(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_status(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
if get_status == "True" and get_payments.cryptobot_token == "None": if get_status == "True" and get_payments.cryptobot_token == "None":
return await call.answer("❌ Токен данной платежной системы не был добавлен", True) return await call.answer(
"❌ Токен данной платежной системы не был добавлен", True
)
await Paymentsx().update(status_cryptobot=get_status) await Paymentsx().update(status_cryptobot=get_status)
@@ -121,12 +139,16 @@ async def payments_cryptobot_status(call: CallbackQuery, bot: Bot, state: FSM, a
############################## ПРИНЯТИЕ CRYPTOBOT ############################## ############################## ПРИНЯТИЕ CRYPTOBOT ##############################
# Принятие токена Cryptobot # Принятие токена Cryptobot
@router.message(StateFilter("here_cryptobot_token")) @router.message(StateFilter("here_cryptobot_token"))
async def payments_cryptobot_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def payments_cryptobot_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
get_token = message.text get_token = message.text
await state.clear() await state.clear()
cache_message = await message.answer("<b>🔷 Проверка введённых CryptoBot данных... 🔄</b>") cache_message = await message.answer(
"<b>🔷 Проверка введённых CryptoBot данных... 🔄</b>"
)
status, response = await ( status, response = await (
await CryptobotAPI.connect( await CryptobotAPI.connect(
@@ -140,9 +162,13 @@ async def payments_cryptobot_get(message: Message, bot: Bot, state: FSM, arSessi
if status: if status:
await Paymentsx().update(cryptobot_token=get_token) await Paymentsx().update(cryptobot_token=get_token)
await cache_message.edit_text("<b>🔷 CryptoBot кошелёк был успешно изменён ✅</b>") await cache_message.edit_text(
"<b>🔷 CryptoBot кошелёк был успешно изменён ✅</b>"
)
else: else:
await cache_message.edit_text("<b>🔷 Не удалось изменить CryptoBot кошелёк ❌</b>") await cache_message.edit_text(
"<b>🔷 Не удалось изменить CryptoBot кошелёк ❌</b>"
)
await message.answer( await message.answer(
"<b>🔷 Управление - CryptoBot</b>", "<b>🔷 Управление - CryptoBot</b>",
@@ -151,12 +177,14 @@ async def payments_cryptobot_get(message: Message, bot: Bot, state: FSM, arSessi
################################################################################ ################################################################################
#################################### ЮMoney #################################### ################################### LOLZTEAM ##################################
# Баланс - ЮMoney # Баланс - Lolzteam
@router.callback_query(F.data == "payment_yoomoney_balance") @router.callback_query(F.data == "payment_lolzteam_balance")
async def payments_yoomoney_balance(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_lolzteam_balance(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
response = await ( response = await (
await YoomoneyAPI.connect( await LolzteamAPI.connect(
bot=bot, bot=bot,
arSession=arSession, arSession=arSession,
update=call, update=call,
@@ -170,11 +198,13 @@ async def payments_yoomoney_balance(call: CallbackQuery, bot: Bot, state: FSM, a
) )
# Информация - ЮMoney # Информация - Lolzteam
@router.callback_query(F.data == "payment_yoomoney_check") @router.callback_query(F.data == "payment_lolzteam_check")
async def payments_yoomoney_check(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payment_lolzteam_check(
response = await ( call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
await YoomoneyAPI.connect( ):
status, response = await (
await LolzteamAPI.connect(
bot=bot, bot=bot,
arSession=arSession, arSession=arSession,
update=call, update=call,
@@ -188,75 +218,117 @@ async def payments_yoomoney_check(call: CallbackQuery, bot: Bot, state: FSM, arS
) )
# Изменение - ЮMoney # Изменение - Lolzteam
@router.callback_query(F.data == "payment_yoomoney_edit") @router.callback_query(F.data == "payment_lolzteam_edit")
async def payments_yoomoney_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payment_lolzteam_edit(
response = await ( call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
await YoomoneyAPI.connect( ):
bot=bot, await state.set_state("here_lolzteam_token")
arSession=arSession,
)
).authorization_get()
await state.set_state("here_yoomoney_token")
await call.message.edit_text( await call.message.edit_text(
ded(f""" ded(f"""
<b>🔮 Изменение ЮMoney кошелька - <a href='https://teletype.in/@djimbox/djimboshop-yoomoney'>Инструкция</a></b> <b>🟢 Изменение Lolzteam - <a href='https://lolz.live/account/api'>API</a></b>
Отправьте ссылку/код из адресной строки Создайте Приложение в "Lolzteam", сгенерируйте токен (basic, invoice, market) и отправьте
{response}
"""), """),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
# Выключатель - ЮMoney # Выключатель - Lolzteam
@router.callback_query(F.data.startswith("payment_yoomoney_status:")) @router.callback_query(F.data.startswith("payment_lolzteam_status:"))
async def payments_yoomoney_status(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payment_lolzteam_status(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
if get_status == "True" and get_payments.yoomoney_token == "None": if get_status == "True" and get_payments.lolzteam_token == "None":
return await call.answer("❌ Токен данной платежной системы не был добавлен", True) return await call.answer(
"❌ Токен данной платежной системы не был добавлен", True
)
await Paymentsx().update(status_yoomoney=get_status) await Paymentsx().update(status_lolzteam=get_status)
await call.message.edit_text( await call.message.edit_text(
"<b>🔮 Управление - ЮMoney</b>", "<b>🟢 Управление - Lolzteam</b>",
reply_markup=await payment_yoomoney_finl(), reply_markup=await payment_lolzteam_finl(),
) )
################################ ПРИНЯТИЕ ЮMONEY ############################### ############################## ПРИНЯТИЕ LOLZTEAM ##############################
# Принятие токена ЮMoney # Принятие токена Lolzteam
@router.message(StateFilter("here_yoomoney_token")) @router.message(StateFilter("here_lolzteam_token"))
async def payments_yoomoney_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def payments_lolzteam_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
get_code = message.text get_token = message.text
try: cache_message = await message.answer(
get_code = get_code[get_code.index("code=") + 5:].replace(" ", "") "<b>🟢 Проверка введённых Lolzteam данных... 🔄</b>"
except Exception: )
bot_logger.debug("ЮMoney code= не найден в сообщении, пробую использовать текст как код", exc_info=True)
cache_message = await message.answer("<b>🔮 Проверка введённых ЮMoney данных... 🔄</b>") status, response = await (
await LolzteamAPI.connect(
status, token, response = await (
await YoomoneyAPI.connect(
bot=bot, bot=bot,
arSession=arSession, arSession=arSession,
update=message,
skipping_error=True,
token=get_token,
merchant_id="None",
) )
).authorization_enter(str(get_code)) ).check()
if status: if status:
await Paymentsx().update(yoomoney_token=token) await Paymentsx().update(lolzteam_token=get_token)
await cache_message.edit_text(response) 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() 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( await message.answer(
"<b>🔮 Управление - ЮMoney</b>", "<b>🟢 Управление - Lolzteam</b>",
reply_markup=await payment_yoomoney_finl(), reply_markup=await payment_lolzteam_finl(),
) )
@@ -264,7 +336,9 @@ async def payments_yoomoney_get(message: Message, bot: Bot, state: FSM, arSessio
#################################### ЗВЁЗДЫ #################################### #################################### ЗВЁЗДЫ ####################################
# Баланс - Звёзды # Баланс - Звёзды
@router.callback_query(F.data == "payment_stars_balance") @router.callback_query(F.data == "payment_stars_balance")
async def payments_stars_balance(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_stars_balance(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
response = await ( response = await (
await StarsAPI.connect( await StarsAPI.connect(
bot=bot, bot=bot,
@@ -282,7 +356,9 @@ async def payments_stars_balance(call: CallbackQuery, bot: Bot, state: FSM, arSe
# Информация - Звёзды # Информация - Звёзды
@router.callback_query(F.data == "payment_stars_check") @router.callback_query(F.data == "payment_stars_check")
async def payments_stars_check(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_stars_check(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
status, response = await ( status, response = await (
await StarsAPI.connect( await StarsAPI.connect(
bot=bot, bot=bot,
@@ -300,7 +376,9 @@ async def payments_stars_check(call: CallbackQuery, bot: Bot, state: FSM, arSess
# Изменение - Звёзды # Изменение - Звёзды
@router.callback_query(F.data == "payment_stars_edit") @router.callback_query(F.data == "payment_stars_edit")
async def payments_stars_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_stars_edit(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
await state.set_state("here_stars_course") await state.set_state("here_stars_course")
@@ -317,7 +395,9 @@ async def payments_stars_edit(call: CallbackQuery, bot: Bot, state: FSM, arSessi
# Выключатель - Звёзды # Выключатель - Звёзды
@router.callback_query(F.data.startswith("payment_stars_status:")) @router.callback_query(F.data.startswith("payment_stars_status:"))
async def payments_stars_status(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def payments_stars_status(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
await Paymentsx().update(status_stars=get_status) await Paymentsx().update(status_stars=get_status)
@@ -331,7 +411,9 @@ async def payments_stars_status(call: CallbackQuery, bot: Bot, state: FSM, arSes
############################# ПРИНЯТИЕ КУРСА ЗВЁЗД ############################# ############################# ПРИНЯТИЕ КУРСА ЗВЁЗД #############################
# Принятие курса Telegram Stars # Принятие курса Telegram Stars
@router.message(StateFilter("here_stars_course")) @router.message(StateFilter("here_stars_course"))
async def payments_stars_course_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def payments_stars_course_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if not is_number(message.text): if not is_number(message.text):
return await message.answer( return await message.answer(
"<b>❌ Введите число. Например: <code>1.5</code></b>" "<b>❌ Введите число. Например: <code>1.5</code></b>"
+176 -149
View File
@@ -26,12 +26,23 @@ from tgbot.keyboards.inline_admin_products import (
products_removes_items_finl, products_removes_items_finl,
item_add_finish_finl, item_add_finish_finl,
) )
from tgbot.services.api_discord import DiscordAPI
from tgbot.services.api_hosting_text import HostingAPI from tgbot.services.api_hosting_text import HostingAPI
from tgbot.utils.const_functions import clear_list, is_number, to_number, del_message, ded, clear_html, gen_id from tgbot.utils.const_functions import (
clear_list,
is_number,
to_number,
del_message,
ded,
clear_html,
gen_id,
)
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.text_functions import category_open_admin, position_open_admin, item_open_admin from tgbot.utils.text_functions import (
category_open_admin,
position_open_admin,
item_open_admin,
)
router = Router(name=__name__) router = Router(name=__name__)
@@ -123,8 +134,10 @@ async def prod_removes(message: Message, bot: Bot, state: FSM, arSession: ARS):
################################################################################ ################################################################################
############################### СОЗДАНИЕ КАТЕГОРИИ ############################# ############################### СОЗДАНИЕ КАТЕГОРИИ #############################
# Принятие названия категории для её создания # Принятие названия категории для её создания
@router.message(F.text, StateFilter('here_category_name')) @router.message(F.text, StateFilter("here_category_name"))
async def prod_category_add_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_category_add_name_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if len(message.text) > 50: if len(message.text) > 50:
return await message.answer( return await message.answer(
"<b>❌ Название не может превышать 50 символов</b>\n" "<b>❌ Название не может превышать 50 символов</b>\n"
@@ -143,7 +156,9 @@ async def prod_category_add_name_get(message: Message, bot: Bot, state: FSM, arS
############################### ИЗМЕНЕНИЕ КАТЕГОРИИ ############################ ############################### ИЗМЕНЕНИЕ КАТЕГОРИИ ############################
# Страница выбора категорий для редактирования # Страница выбора категорий для редактирования
@router.callback_query(F.data.startswith("category_edit_swipe:")) @router.callback_query(F.data.startswith("category_edit_swipe:"))
async def prod_category_edit_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
await call.message.edit_text( await call.message.edit_text(
@@ -154,7 +169,9 @@ async def prod_category_edit_swipe(call: CallbackQuery, bot: Bot, state: FSM, ar
# Выбор текущей категории для редактирования # Выбор текущей категории для редактирования
@router.callback_query(F.data.startswith("category_edit_open:")) @router.callback_query(F.data.startswith("category_edit_open:"))
async def prod_category_edit_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -167,7 +184,9 @@ async def prod_category_edit_open(call: CallbackQuery, bot: Bot, state: FSM, arS
############################ САМО ИЗМЕНЕНИЕ КАТЕГОРИИ ########################## ############################ САМО ИЗМЕНЕНИЕ КАТЕГОРИИ ##########################
# Изменение названия категории # Изменение названия категории
@router.callback_query(F.data.startswith("category_edit_name:")) @router.callback_query(F.data.startswith("category_edit_name:"))
async def prod_category_edit_name(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_name(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -184,10 +203,12 @@ async def prod_category_edit_name(call: CallbackQuery, bot: Bot, state: FSM, arS
# Принятие нового названия для категории # Принятие нового названия для категории
@router.message(F.text, StateFilter('here_category_edit_name')) @router.message(F.text, StateFilter("here_category_edit_name"))
async def prod_category_edit_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_name_get(
category_id = (await state.get_data())['here_category_id'] message: Message, bot: Bot, state: FSM, arSession: ARS
remover = (await state.get_data())['here_remover'] ):
category_id = (await state.get_data())["here_category_id"]
remover = (await state.get_data())["here_remover"]
if len(message.text) > 50: if len(message.text) > 50:
return await message.answer( return await message.answer(
@@ -204,7 +225,9 @@ async def prod_category_edit_name_get(message: Message, bot: Bot, state: FSM, ar
# Удаление категории # Удаление категории
@router.callback_query(F.data.startswith("category_edit_delete:")) @router.callback_query(F.data.startswith("category_edit_delete:"))
async def prod_category_edit_delete(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_delete(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -216,7 +239,9 @@ async def prod_category_edit_delete(call: CallbackQuery, bot: Bot, state: FSM, a
# Подтверждение удаления категории # Подтверждение удаления категории
@router.callback_query(F.data.startswith("category_edit_delete_confirm:")) @router.callback_query(F.data.startswith("category_edit_delete_confirm:"))
async def prod_category_edit_delete_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_category_edit_delete_confirm(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -241,7 +266,9 @@ async def prod_category_edit_delete_confirm(call: CallbackQuery, bot: Bot, state
############################### ДОБАВЛЕНИЕ ПОЗИЦИИ ############################# ############################### ДОБАВЛЕНИЕ ПОЗИЦИИ #############################
# Cтраницы выбора категорий для расположения позиции # Cтраницы выбора категорий для расположения позиции
@router.callback_query(F.data.startswith("position_add_swipe:")) @router.callback_query(F.data.startswith("position_add_swipe:"))
async def prod_position_add_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_add_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
await call.message.edit_text( await call.message.edit_text(
@@ -252,7 +279,9 @@ async def prod_position_add_swipe(call: CallbackQuery, bot: Bot, state: FSM, arS
# Выбор категории для создания позиции # Выбор категории для создания позиции
@router.callback_query(F.data.startswith("position_add_open:")) @router.callback_query(F.data.startswith("position_add_open:"))
async def prod_position_add_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_add_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
await state.update_data(here_category_id=category_id) await state.update_data(here_category_id=category_id)
@@ -262,8 +291,10 @@ async def prod_position_add_open(call: CallbackQuery, bot: Bot, state: FSM, arSe
# Принятие названия для создания позиции # Принятие названия для создания позиции
@router.message(F.text, StateFilter('here_position_name')) @router.message(F.text, StateFilter("here_position_name"))
async def prod_position_add_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_position_add_name_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if len(message.text) > 50: if len(message.text) > 50:
return await message.answer( return await message.answer(
"<b>❌ Название не может превышать 50 символов</b>\n" "<b>❌ Название не может превышать 50 символов</b>\n"
@@ -277,12 +308,13 @@ async def prod_position_add_name_get(message: Message, bot: Bot, state: FSM, arS
# Принятие цены позиции для её создания # Принятие цены позиции для её создания
@router.message(F.text, StateFilter('here_position_price')) @router.message(F.text, StateFilter("here_position_price"))
async def prod_position_add_price_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_position_add_price_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if not is_number(message.text): if not is_number(message.text):
return await message.answer( return await message.answer(
"<b>❌ Данные были введены неверно</b>\n" "<b>❌ Данные были введены неверно</b>\n" "📁 Введите цену для позиции",
"📁 Введите цену для позиции",
) )
if to_number(message.text) > 10_000_000 or to_number(message.text) < 0: if to_number(message.text) > 10_000_000 or to_number(message.text) < 0:
@@ -291,11 +323,10 @@ async def prod_position_add_price_get(message: Message, bot: Bot, state: FSM, ar
"📁 Введите цену для позиции", "📁 Введите цену для позиции",
) )
category_id = (await state.get_data())['here_category_id'] category_id = (await state.get_data())["here_category_id"]
position_name = (await state.get_data())['here_position_name'] position_name = (await state.get_data())["here_position_name"]
position_price = to_number(message.text) position_price = to_number(message.text)
position_desc = "None" position_desc = "None"
position_photo = "None"
position_id = gen_id(12) position_id = gen_id(12)
await state.clear() await state.clear()
@@ -305,7 +336,6 @@ async def prod_position_add_price_get(message: Message, bot: Bot, state: FSM, ar
position_name=position_name, position_name=position_name,
position_price=position_price, position_price=position_price,
position_desc=position_desc, position_desc=position_desc,
position_photo=position_photo,
) )
await position_open_admin(bot, position_id, message.from_user.id) await position_open_admin(bot, position_id, message.from_user.id)
@@ -315,7 +345,9 @@ async def prod_position_add_price_get(message: Message, bot: Bot, state: FSM, ar
############################ РЕДАКТИРОВАНИЕ ПОЗИЦИИ ############################ ############################ РЕДАКТИРОВАНИЕ ПОЗИЦИИ ############################
# Страницы выбора категории для редактирования позиции # Страницы выбора категории для редактирования позиции
@router.callback_query(F.data.startswith("position_edit_category_swipe:")) @router.callback_query(F.data.startswith("position_edit_category_swipe:"))
async def prod_position_edit_category_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_category_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
await call.message.edit_text( await call.message.edit_text(
@@ -326,7 +358,9 @@ async def prod_position_edit_category_swipe(call: CallbackQuery, bot: Bot, state
# Открытие категории для выбора позиции # Открытие категории для выбора позиции
@router.callback_query(F.data.startswith("position_edit_category_open:")) @router.callback_query(F.data.startswith("position_edit_category_open:"))
async def prod_position_edit_category_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_category_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
get_category = await Categoryx().get_required(category_id=category_id) get_category = await Categoryx().get_required(category_id=category_id)
@@ -338,12 +372,16 @@ async def prod_position_edit_category_open(call: CallbackQuery, bot: Bot, state:
reply_markup=await position_edit_swipe_fp(0, category_id), reply_markup=await position_edit_swipe_fp(0, category_id),
) )
else: else:
await call.answer(f"📁 Позиции в категории {get_category.category_name} отсутствуют") await call.answer(
f"📁 Позиции в категории {get_category.category_name} отсутствуют"
)
# Страницы выбора позиции для редактирования # Страницы выбора позиции для редактирования
@router.callback_query(F.data.startswith("position_edit_swipe:")) @router.callback_query(F.data.startswith("position_edit_swipe:"))
async def prod_position_edit_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -357,7 +395,9 @@ async def prod_position_edit_swipe(call: CallbackQuery, bot: Bot, state: FSM, ar
# Выбор позиции для редактирования # Выбор позиции для редактирования
@router.callback_query(F.data.startswith("position_edit_open:")) @router.callback_query(F.data.startswith("position_edit_open:"))
async def prod_position_edit_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -370,7 +410,9 @@ async def prod_position_edit_open(call: CallbackQuery, bot: Bot, state: FSM, arS
############################ САМО ИЗМЕНЕНИЕ ПОЗИЦИИ ############################ ############################ САМО ИЗМЕНЕНИЕ ПОЗИЦИИ ############################
# Изменение названия позиции # Изменение названия позиции
@router.callback_query(F.data.startswith("position_edit_name:")) @router.callback_query(F.data.startswith("position_edit_name:"))
async def prod_position_edit_name(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_name(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -386,10 +428,12 @@ async def prod_position_edit_name(call: CallbackQuery, bot: Bot, state: FSM, arS
# Принятие названия позиции для её изменения # Принятие названия позиции для её изменения
@router.message(F.text, StateFilter('here_position_edit_name')) @router.message(F.text, StateFilter("here_position_edit_name"))
async def prod_position_edit_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_name_get(
position_id = (await state.get_data())['here_position_id'] message: Message, bot: Bot, state: FSM, arSession: ARS
remover = (await state.get_data())['here_remover'] ):
position_id = (await state.get_data())["here_position_id"]
remover = (await state.get_data())["here_remover"]
if len(message.text) > 50: if len(message.text) > 50:
return await message.answer( return await message.answer(
@@ -406,7 +450,9 @@ async def prod_position_edit_name_get(message: Message, bot: Bot, state: FSM, ar
# Изменение цены позиции # Изменение цены позиции
@router.callback_query(F.data.startswith("position_edit_price:")) @router.callback_query(F.data.startswith("position_edit_price:"))
async def prod_position_edit_price(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_price(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -422,10 +468,12 @@ async def prod_position_edit_price(call: CallbackQuery, bot: Bot, state: FSM, ar
# Принятие цены позиции для её изменения # Принятие цены позиции для её изменения
@router.message(F.text, StateFilter('here_position_edit_price')) @router.message(F.text, StateFilter("here_position_edit_price"))
async def prod_position_edit_price_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_price_get(
position_id = (await state.get_data())['here_position_id'] message: Message, bot: Bot, state: FSM, arSession: ARS
remover = (await state.get_data())['here_remover'] ):
position_id = (await state.get_data())["here_position_id"]
remover = (await state.get_data())["here_remover"]
if not is_number(message.text): if not is_number(message.text):
return await message.answer( return await message.answer(
@@ -449,7 +497,9 @@ async def prod_position_edit_price_get(message: Message, bot: Bot, state: FSM, a
# Изменение описания позиции # Изменение описания позиции
@router.callback_query(F.data.startswith("position_edit_desc:")) @router.callback_query(F.data.startswith("position_edit_desc:"))
async def prod_position_edit_desc(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_desc(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -469,10 +519,12 @@ async def prod_position_edit_desc(call: CallbackQuery, bot: Bot, state: FSM, arS
# Принятие описания позиции для её изменения # Принятие описания позиции для её изменения
@router.message(F.text, StateFilter('here_position_edit_desc')) @router.message(F.text, StateFilter("here_position_edit_desc"))
async def prod_position_edit_desc_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_desc_get(
position_id = (await state.get_data())['here_position_id'] message: Message, bot: Bot, state: FSM, arSession: ARS
remover = (await state.get_data())['here_remover'] ):
position_id = (await state.get_data())["here_position_id"]
remover = (await state.get_data())["here_remover"]
if len(message.text) > 1200: if len(message.text) > 1200:
return await message.answer( return await message.answer(
@@ -510,74 +562,11 @@ async def prod_position_edit_desc_get(message: Message, bot: Bot, state: FSM, ar
await position_open_admin(bot, position_id, message.from_user.id) await position_open_admin(bot, position_id, message.from_user.id)
# Изменение изображения позиции
@router.callback_query(F.data.startswith("position_edit_photo:"))
async def prod_position_edit_photo(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2])
get_settings = await Settingsx().get()
if get_settings.misc_discord_webhook_url == "None":
return await call.answer("🧿 Отсутствует Дискорд вебхук, добавьте его в настройках", True)
await state.update_data(here_position_id=position_id)
await state.update_data(here_remover=remover)
await state.set_state("here_position_edit_photo")
await del_message(call.message)
await call.message.answer(
"<b>📁 Отправьте новое изображение для позиции</b>\n"
"❕ Отправьте <code>0</code> чтобы пропустить.",
reply_markup=position_edit_cancel_finl(position_id, remover),
)
# Принятие нового фото для позиции
@router.message((F.text == "0") | F.photo, StateFilter('here_position_edit_photo'))
async def prod_position_edit_photo_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
position_id = (await state.get_data())['here_position_id']
remover = (await state.get_data())['here_remover']
get_settings = await Settingsx().get()
if get_settings.misc_discord_webhook_url == "None":
return await message.answer(
"<b>🧿 Отсутствует Дискорд вебхук, добавьте его в настройках</b>"
)
position_photo = "None"
if message.photo is not None:
cache_message = await message.answer(
"<b>♻️ Подождите, фотография загружается...</b>"
)
file_path = (await bot.get_file(message.photo[-1].file_id)).file_path
photo_path = await bot.download_file(file_path)
pay_image_status, pay_image_url = await (
await DiscordAPI.connect(
bot=bot,
arSession=arSession,
update=message,
)
).upload_photo(photo_path.read())
if pay_image_status:
position_photo = pay_image_url
await del_message(cache_message)
await state.clear()
await Positionx().update(position_id, position_photo=position_photo)
await position_open_admin(bot, position_id, message.from_user.id)
# Выгрузка товаров # Выгрузка товаров
@router.callback_query(F.data.startswith("position_edit_items:")) @router.callback_query(F.data.startswith("position_edit_items:"))
async def prod_position_edit_items(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_items(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -607,7 +596,9 @@ async def prod_position_edit_items(call: CallbackQuery, bot: Bot, state: FSM, ar
# Удаление позиции # Удаление позиции
@router.callback_query(F.data.startswith("position_edit_delete:")) @router.callback_query(F.data.startswith("position_edit_delete:"))
async def prod_position_edit_delete(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_delete(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -621,7 +612,9 @@ async def prod_position_edit_delete(call: CallbackQuery, bot: Bot, state: FSM, a
# Подтверждение удаления позиции # Подтверждение удаления позиции
@router.callback_query(F.data.startswith("position_edit_delete_confirm:")) @router.callback_query(F.data.startswith("position_edit_delete_confirm:"))
async def prod_position_edit_delete_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_delete_confirm(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -636,7 +629,9 @@ async def prod_position_edit_delete_confirm(call: CallbackQuery, bot: Bot, state
if len(get_positions) >= 1: if len(get_positions) >= 1:
await call.message.edit_text( await call.message.edit_text(
"<b>📁 Выберите позицию для изменения 🖍</b>", "<b>📁 Выберите позицию для изменения 🖍</b>",
reply_markup=await position_edit_swipe_fp(remover, get_position.category_id), reply_markup=await position_edit_swipe_fp(
remover, get_position.category_id
),
) )
else: else:
await del_message(call.message) await del_message(call.message)
@@ -644,7 +639,9 @@ async def prod_position_edit_delete_confirm(call: CallbackQuery, bot: Bot, state
# Очистка позиции # Очистка позиции
@router.callback_query(F.data.startswith("position_edit_clear:")) @router.callback_query(F.data.startswith("position_edit_clear:"))
async def prod_position_edit_clear(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_clear(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -657,7 +654,9 @@ async def prod_position_edit_clear(call: CallbackQuery, bot: Bot, state: FSM, ar
# Согласие на очистку позиции # Согласие на очистку позиции
@router.callback_query(F.data.startswith("position_edit_clear_confirm:")) @router.callback_query(F.data.startswith("position_edit_clear_confirm:"))
async def prod_position_edit_clear_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_edit_clear_confirm(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -672,7 +671,9 @@ async def prod_position_edit_clear_confirm(call: CallbackQuery, bot: Bot, state:
############################### ДОБАВЛЕНИЕ ТОВАРОВ ############################# ############################### ДОБАВЛЕНИЕ ТОВАРОВ #############################
# Страницы выбора категории для добавления товара # Страницы выбора категории для добавления товара
@router.callback_query(F.data.startswith("item_add_category_swipe:")) @router.callback_query(F.data.startswith("item_add_category_swipe:"))
async def prod_item_add_category_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_add_category_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
await call.message.edit_text( await call.message.edit_text(
@@ -683,7 +684,9 @@ async def prod_item_add_category_swipe(call: CallbackQuery, bot: Bot, state: FSM
# Открытие категории для выбора позиции # Открытие категории для выбора позиции
@router.callback_query(F.data.startswith("item_add_category_open:")) @router.callback_query(F.data.startswith("item_add_category_open:"))
async def prod_item_add_category_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_add_category_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -698,12 +701,16 @@ async def prod_item_add_category_open(call: CallbackQuery, bot: Bot, state: FSM,
reply_markup=await item_add_position_swipe_fp(0, category_id), reply_markup=await item_add_position_swipe_fp(0, category_id),
) )
else: else:
await call.answer(f"🎁 Позиции в категории {get_category.category_name} отсутствуют") await call.answer(
f"🎁 Позиции в категории {get_category.category_name} отсутствуют"
)
# Страницы выбора позиции для добавления товара # Страницы выбора позиции для добавления товара
@router.callback_query(F.data.startswith("item_add_position_swipe:")) @router.callback_query(F.data.startswith("item_add_position_swipe:"))
async def prod_item_add_position_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_add_position_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -714,8 +721,10 @@ async def prod_item_add_position_swipe(call: CallbackQuery, bot: Bot, state: FSM
# Выбор позиции для добавления товаров # Выбор позиции для добавления товаров
@router.callback_query(F.data.startswith("item_add_position_open:"), flags={'rate': 0}) @router.callback_query(F.data.startswith("item_add_position_open:"), flags={"rate": 0})
async def prod_item_add_position_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_add_position_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
get_position = await Positionx().get_required(position_id=position_id) get_position = await Positionx().get_required(position_id=position_id)
@@ -734,9 +743,9 @@ async def prod_item_add_position_open(call: CallbackQuery, bot: Bot, state: FSM,
<b>🎁 Отправляйте данные товаров</b> <b>🎁 Отправляйте данные товаров</b>
Товары разделяются одной пустой строчкой. Пример: Товары разделяются одной пустой строчкой. Пример:
<code>Данные товара... <code>Данные товара...
Данные товара... Данные товара...
Данные товара...</code> Данные товара...</code>
"""), """),
reply_markup=item_add_finish_finl(position_id), reply_markup=item_add_finish_finl(position_id),
@@ -755,12 +764,16 @@ async def prod_item_add_position_open(call: CallbackQuery, bot: Bot, state: FSM,
# Завершение загрузки товаров # Завершение загрузки товаров
@router.callback_query(F.data.startswith('item_add_position_finish:'), flags={'rate': 0}) @router.callback_query(
async def prod_item_add_finish(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): F.data.startswith("item_add_position_finish:"), flags={"rate": 0}
)
async def prod_item_add_finish(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
try: try:
count_items = (await state.get_data())['here_add_item_count'] count_items = (await state.get_data())["here_add_item_count"]
except Exception: except Exception:
bot_logger.debug("В state нет счетчика добавленных товаров", exc_info=True) bot_logger.debug("В state нет счетчика добавленных товаров", exc_info=True)
count_items = 0 count_items = 0
@@ -777,7 +790,7 @@ async def prod_item_add_finish(call: CallbackQuery, bot: Bot, state: FSM, arSess
# Принятие данных товара # Принятие данных товара
@router.message(F.text, StateFilter('here_add_items'), flags={'rate': 0}) @router.message(F.text, StateFilter("here_add_items"), flags={"rate": 0})
async def prod_item_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def prod_item_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
cache_message = await message.answer("<b>⌛ Ждите, товары добавляются..</b>") cache_message = await message.answer("<b>⌛ Ждите, товары добавляются..</b>")
@@ -788,9 +801,9 @@ async def prod_item_add_get(message: Message, bot: Bot, state: FSM, arSession: A
else: else:
get_items = clear_list(message.text.split("\n")) get_items = clear_list(message.text.split("\n"))
item_count = (await state.get_data())['here_add_item_count'] item_count = (await state.get_data())["here_add_item_count"]
category_id = (await state.get_data())['here_add_item_category_id'] category_id = (await state.get_data())["here_add_item_category_id"]
position_id = (await state.get_data())['here_add_item_position_id'] position_id = (await state.get_data())["here_add_item_position_id"]
await state.update_data(here_add_item_count=item_count + len(get_items)) await state.update_data(here_add_item_count=item_count + len(get_items))
@@ -811,7 +824,9 @@ async def prod_item_add_get(message: Message, bot: Bot, state: FSM, arSession: A
############################### УДАЛЕНИЕ ТОВАРОВ ############################### ############################### УДАЛЕНИЕ ТОВАРОВ ###############################
# Страницы удаления товаров # Страницы удаления товаров
@router.callback_query(F.data.startswith("item_delete_swipe:")) @router.callback_query(F.data.startswith("item_delete_swipe:"))
async def prod_item_delete_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_delete_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -826,12 +841,16 @@ async def prod_item_delete_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSe
reply_markup=await item_delete_swipe_fp(remover, position_id), reply_markup=await item_delete_swipe_fp(remover, position_id),
) )
else: else:
await call.answer(f"🎁 Товары в позиции {get_position.position_name} отсутствуют", True) await call.answer(
f"🎁 Товары в позиции {get_position.position_name} отсутствуют", True
)
# Удаление товара # Удаление товара
@router.callback_query(F.data.startswith("item_delete_open:")) @router.callback_query(F.data.startswith("item_delete_open:"))
async def prod_item_delete_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_delete_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
item_id = int(call.data.split(":")[1]) item_id = int(call.data.split(":")[1])
await del_message(call.message) await del_message(call.message)
@@ -840,7 +859,9 @@ async def prod_item_delete_open(call: CallbackQuery, bot: Bot, state: FSM, arSes
# Подтверждение удаления товара # Подтверждение удаления товара
@router.callback_query(F.data.startswith("item_delete_confirm:")) @router.callback_query(F.data.startswith("item_delete_confirm:"))
async def prod_item_delete_confirm_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_item_delete_confirm_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
item_id = int(call.data.split(":")[1]) item_id = int(call.data.split(":")[1])
get_item = await Itemx().get_required(item_id=item_id) get_item = await Itemx().get_required(item_id=item_id)
@@ -862,7 +883,9 @@ async def prod_item_delete_confirm_open(call: CallbackQuery, bot: Bot, state: FS
############################### УДАЛЕНИЕ РАЗДЕЛОВ ############################## ############################### УДАЛЕНИЕ РАЗДЕЛОВ ##############################
# Возвращение к меню удаления разделов # Возвращение к меню удаления разделов
@router.callback_query(F.data == "prod_removes_return") @router.callback_query(F.data == "prod_removes_return")
async def prod_removes_return(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_removes_return(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await call.message.edit_text( await call.message.edit_text(
@@ -873,7 +896,9 @@ async def prod_removes_return(call: CallbackQuery, bot: Bot, state: FSM, arSessi
# Удаление всех категорий # Удаление всех категорий
@router.callback_query(F.data == "prod_removes_categories") @router.callback_query(F.data == "prod_removes_categories")
async def prod_removes_categories(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_removes_categories(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_categories = len(await Categoryx().get_all()) get_categories = len(await Categoryx().get_all())
get_positions = len(await Positionx().get_all()) get_positions = len(await Positionx().get_all())
get_items = len(await Itemx().get_all()) get_items = len(await Itemx().get_all())
@@ -891,7 +916,9 @@ async def prod_removes_categories(call: CallbackQuery, bot: Bot, state: FSM, arS
# Подтверждение удаления всех категорий (позиций и товаров включительно) # Подтверждение удаления всех категорий (позиций и товаров включительно)
@router.callback_query(F.data == "prod_removes_categories_confirm") @router.callback_query(F.data == "prod_removes_categories_confirm")
async def prod_removes_categories_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_removes_categories_confirm(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_categories = len(await Categoryx().get_all()) get_categories = len(await Categoryx().get_all())
get_positions = len(await Positionx().get_all()) get_positions = len(await Positionx().get_all())
get_items = len(await Itemx().get_all()) get_items = len(await Itemx().get_all())
@@ -900,19 +927,19 @@ async def prod_removes_categories_confirm(call: CallbackQuery, bot: Bot, state:
await Positionx().clear() await Positionx().clear()
await Itemx().clear() await Itemx().clear()
await call.message.edit_text( await call.message.edit_text(ded(f"""
ded(f"""
<b> Вы успешно удалили все категории</b> <b> Вы успешно удалили все категории</b>
🗃 Категорий: <code>{get_categories}шт</code> 🗃 Категорий: <code>{get_categories}шт</code>
📁 Позиций: <code>{get_positions}шт</code> 📁 Позиций: <code>{get_positions}шт</code>
🎁 Товаров: <code>{get_items}шт</code> 🎁 Товаров: <code>{get_items}шт</code>
""") """))
)
# Удаление всех позиций # Удаление всех позиций
@router.callback_query(F.data == "prod_removes_positions") @router.callback_query(F.data == "prod_removes_positions")
async def prod_removes_positions(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_removes_positions(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_positions = len(await Positionx().get_all()) get_positions = len(await Positionx().get_all())
get_items = len(await Itemx().get_all()) get_items = len(await Itemx().get_all())
@@ -928,20 +955,20 @@ async def prod_removes_positions(call: CallbackQuery, bot: Bot, state: FSM, arSe
# Подтверждение удаления всех позиций (товаров включительно) # Подтверждение удаления всех позиций (товаров включительно)
@router.callback_query(F.data == "prod_removes_positions_confirm") @router.callback_query(F.data == "prod_removes_positions_confirm")
async def prod_position_remove(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def prod_position_remove(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_positions = len(await Positionx().get_all()) get_positions = len(await Positionx().get_all())
get_items = len(await Itemx().get_all()) get_items = len(await Itemx().get_all())
await Positionx().clear() await Positionx().clear()
await Itemx().clear() await Itemx().clear()
await call.message.edit_text( await call.message.edit_text(ded(f"""
ded(f"""
<b> Вы успешно удалили все позиции</b> <b> Вы успешно удалили все позиции</b>
📁 Позиций: <code>{get_positions}шт</code> 📁 Позиций: <code>{get_positions}шт</code>
🎁 Товаров: <code>{get_items}шт</code> 🎁 Товаров: <code>{get_items}шт</code>
""") """))
)
# Удаление всех товаров # Удаление всех товаров
+124 -124
View File
@@ -1,12 +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.services.api_discord import DiscordDJ, DiscordAPI from tgbot.utils.const_functions import ded, is_number, to_number
from tgbot.utils.const_functions import ded
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
@@ -40,7 +41,9 @@ async def settings_status_edit(message: Message, bot: Bot, state: FSM, arSession
################################## ВЫКЛЮЧАТЕЛИ ################################# ################################## ВЫКЛЮЧАТЕЛИ #################################
# Включение/выключение тех работ # Включение/выключение тех работ
@router.callback_query(F.data.startswith("settings_status_work:")) @router.callback_query(F.data.startswith("settings_status_work:"))
async def settings_status_work(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_status_work(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
@@ -65,7 +68,9 @@ async def settings_status_work(call: CallbackQuery, bot: Bot, state: FSM, arSess
# Включение/выключение покупок # Включение/выключение покупок
@router.callback_query(F.data.startswith("settings_status_buy:")) @router.callback_query(F.data.startswith("settings_status_buy:"))
async def settings_status_buy(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_status_buy(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
@@ -90,7 +95,9 @@ async def settings_status_buy(call: CallbackQuery, bot: Bot, state: FSM, arSessi
# Включение/выключение пополнений # Включение/выключение пополнений
@router.callback_query(F.data.startswith("settings_status_refill:")) @router.callback_query(F.data.startswith("settings_status_refill:"))
async def settings_status_refill(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_status_refill(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
@@ -111,9 +118,36 @@ async def settings_status_refill(call: CallbackQuery, bot: Bot, state: FSM, arSe
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(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_notification_buy(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
@@ -136,7 +170,9 @@ async def settings_notification_buy(call: CallbackQuery, bot: Bot, state: FSM, a
# Включение/выключение уведомлений о пополнениях # Включение/выключение уведомлений о пополнениях
@router.callback_query(F.data.startswith("settings_notification_refill:")) @router.callback_query(F.data.startswith("settings_notification_refill:"))
async def settings_notification_refill(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_notification_refill(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
@@ -165,20 +201,20 @@ async def settings_faq_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession
await state.clear() await state.clear()
await state.set_state("here_settings_faq") await state.set_state("here_settings_faq")
await call.message.edit_text( await call.message.edit_text(ded("""
ded("""
<b> Введите новый текст для FAQ</b> <b> Введите новый текст для FAQ</b>
Вы можете использовать заготовленный синтаксис и HTML разметку: Вы можете использовать заготовленный синтаксис и HTML разметку:
<code>{username}</code> - логин пользоваля <code>{username}</code> - логин пользоваля
<code>{user_id}</code> - айди пользователя <code>{user_id}</code> - айди пользователя
<code>{firstname}</code> - имя пользователя <code>{firstname}</code> - имя пользователя
""") """))
)
# Изменение поддержки # Изменение поддержки
@router.callback_query(F.data == "settings_edit_support") @router.callback_query(F.data == "settings_edit_support")
async def settings_support_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_support_edit(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await state.set_state("here_settings_support") await state.set_state("here_settings_support")
@@ -190,7 +226,9 @@ async def settings_support_edit(call: CallbackQuery, bot: Bot, state: FSM, arSes
# Изменение отображения/скрытия категорий без товаров # Изменение отображения/скрытия категорий без товаров
@router.callback_query(F.data.startswith("settings_edit_hide_category:")) @router.callback_query(F.data.startswith("settings_edit_hide_category:"))
async def settings_edit_hide_category(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_edit_hide_category(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
status = call.data.split(":")[1] status = call.data.split(":")[1]
await Settingsx().update(misc_hide_category=status) await Settingsx().update(misc_hide_category=status)
@@ -203,7 +241,9 @@ async def settings_edit_hide_category(call: CallbackQuery, bot: Bot, state: FSM,
# Изменение отображения/скрытия позиций без товаров # Изменение отображения/скрытия позиций без товаров
@router.callback_query(F.data.startswith("settings_edit_hide_position:")) @router.callback_query(F.data.startswith("settings_edit_hide_position:"))
async def settings_edit_hide_position(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_edit_hide_position(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
status = call.data.split(":")[1] status = call.data.split(":")[1]
await Settingsx().update(misc_hide_position=status) await Settingsx().update(misc_hide_position=status)
@@ -216,7 +256,9 @@ async def settings_edit_hide_position(call: CallbackQuery, bot: Bot, state: FSM,
# Изменение метода добавления товаров # Изменение метода добавления товаров
@router.callback_query(F.data.startswith("settings_edit_method_prod:")) @router.callback_query(F.data.startswith("settings_edit_method_prod:"))
async def settings_edit_method_prod(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def settings_edit_method_prod(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
method = call.data.split(":")[1] method = call.data.split(":")[1]
await Settingsx().update(misc_method_prod=method) await Settingsx().update(misc_method_prod=method)
@@ -227,49 +269,6 @@ async def settings_edit_method_prod(call: CallbackQuery, bot: Bot, state: FSM, a
) )
# Изменение дискорд вебхука
@router.callback_query(F.data == "settings_edit_discord_webhook")
async def settings_discord_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
await state.clear()
get_discord_public_webhook = await (
DiscordDJ(
arSession=arSession,
bot=bot,
)
).export_webhook()
await state.set_state("here_settings_discord_webhook")
await call.message.edit_text(
ded(f"""
<b>🧿 Отправьте новый вебхук дискорда</b>
Для удаления вебхука введите <code>0</code>
Вы можете использовать публичный вебхук, но ответственность за его использование лежит только на вас
Публичный вебхук: <code>{get_discord_public_webhook}</code>
""")
)
# Изменение текстового хостинга по умолчанию
@router.callback_query(F.data == "settings_edit_hosting_text")
async def settings_edit_hosting_text(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
get_settings = await Settingsx().get()
if get_settings.misc_hosting_text == "telegraph":
await Settingsx().update(misc_hosting_text="pastie")
elif get_settings.misc_hosting_text == "pastie":
await Settingsx().update(misc_hosting_text="friendpaste")
elif get_settings.misc_hosting_text == "friendpaste":
await Settingsx().update(misc_hosting_text="snippet")
elif get_settings.misc_hosting_text == "snippet":
await Settingsx().update(misc_hosting_text="telegraph")
await call.message.edit_text(
"<b>🖍 Изменение данных бота</b>",
reply_markup=await settings_finl(),
)
################################################################################ ################################################################################
################################ ПРИНЯТИЕ ДАННЫХ ############################### ################################ ПРИНЯТИЕ ДАННЫХ ###############################
# Принятие FAQ # Принятие FAQ
@@ -282,8 +281,7 @@ async def settings_faq_get(message: Message, bot: Bot, state: FSM, arSession: AR
except Exception: except Exception:
bot_logger.debug("Некорректная HTML-разметка FAQ", exc_info=True) bot_logger.debug("Некорректная HTML-разметка FAQ", exc_info=True)
return await message.answer( return await message.answer(
"<b>❌ Ошибка синтаксиса HTML</b>\n" "<b>❌ Ошибка синтаксиса HTML</b>\n" "❔ Введите новый текст для FAQ",
"❔ Введите новый текст для FAQ",
) )
await state.clear() await state.clear()
@@ -296,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
@@ -310,67 +374,3 @@ async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession
"<b>🖍 Изменение данных бота</b>", "<b>🖍 Изменение данных бота</b>",
reply_markup=await settings_finl(), reply_markup=await settings_finl(),
) )
# Принятие дискорд вебхука
@router.message(F.text, StateFilter("here_settings_discord_webhook"))
async def settings_discord_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
get_discord_webhook = message.text
# Удаление вебхука
if get_discord_webhook == "0":
await Settingsx().update(
misc_discord_webhook_url="None",
misc_discord_webhook_name="None",
)
return await message.answer(
"<b>⚙️ Настройки бота</b>",
reply_markup=await settings_finl(),
)
# Добавление нового вебхука
cache_message = await message.answer("<b>♻️ Проверка дискорд вебхука..</b>")
if "api" in get_discord_webhook and "webhooks" in get_discord_webhook:
discord_webhook_status, discord_webhook_name = await (
await DiscordAPI.connect(
bot=bot,
arSession=arSession,
update=message,
webhook_url=get_discord_webhook,
skipping_error=True,
)
).check()
if discord_webhook_status:
await state.clear()
await Settingsx().update(
misc_discord_webhook_url=message.text,
misc_discord_webhook_name=discord_webhook_name,
)
return await cache_message.edit_text(
"<b>⚙️ Настройки бота</b>",
reply_markup=await settings_finl(),
)
# Обработка ошибки добавления вебхука
get_discord_public_webhook = await (
DiscordDJ(
arSession=arSession,
bot=bot,
)
).export_webhook()
await cache_message.edit_text(
ded(f"""
<b> Указан некорректный вебхук</b>
🧿 Отправьте новый вебхук дискорда
Для удаления вебхука введите <code>0</code>
Вы можете использовать публичный вебхук, но ответственность за его использование лежит только на вас
Публичный вебхук: <code>{get_discord_public_webhook}</code>
""")
)
+26 -19
View File
@@ -14,21 +14,20 @@ from tgbot.utils.text_functions import position_open_user
# Игнор-колбэки покупок # Игнор-колбэки покупок
prohibit_buy = ( prohibit_buy = (
'buy_category_swipe', "buy_category_swipe",
'buy_category_open', "buy_category_open",
'buy_position_swipe', "buy_position_swipe",
'buy_position_open', "buy_position_open",
'buy_item_open', "buy_item_open",
'buy_item_confirm', "buy_item_confirm",
) )
# Игнор-колбэки пополнений # Игнор-колбэки пополнений
prohibit_refill = ( prohibit_refill = (
'user_refill', "user_refill",
'user_refill_method', "user_refill_method",
'Pay:Cryptobot', "Pay:Cryptobot",
'Pay:Yoomoney', "Pay:",
'Pay:',
) )
router = Router(name=__name__) router = Router(name=__name__)
@@ -54,7 +53,9 @@ async def filter_work_message(message: Message, bot: Bot, state: FSM, arSession:
# Фильтр на технические работы - колбэк # Фильтр на технические работы - колбэк
@router.callback_query(IsWork()) @router.callback_query(IsWork())
async def filter_work_callback(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def filter_work_callback(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await call.answer("⛔ Бот находится на технических работах.", True) await call.answer("⛔ Бот находится на технических работах.", True)
@@ -64,7 +65,7 @@ async def filter_work_callback(call: CallbackQuery, bot: Bot, state: FSM, arSess
################################# СТАТУС ПОКУПОК ############################### ################################# СТАТУС ПОКУПОК ###############################
# Фильтр на доступность покупок - сообщение # Фильтр на доступность покупок - сообщение
@router.message(IsBuy(), F.text == "🎁 Купить") @router.message(IsBuy(), F.text == "🎁 Купить")
@router.message(IsBuy(), StateFilter('here_item_count')) @router.message(IsBuy(), StateFilter("here_item_count"))
async def filter_buy_message(message: Message, bot: Bot, state: FSM, arSession: ARS): async def filter_buy_message(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -73,7 +74,9 @@ async def filter_buy_message(message: Message, bot: Bot, state: FSM, arSession:
# Фильтр на доступность покупок - колбэк # Фильтр на доступность покупок - колбэк
@router.callback_query(IsBuy(), F.data.startswith(prohibit_buy)) @router.callback_query(IsBuy(), F.data.startswith(prohibit_buy))
async def filter_buy_callback(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def filter_buy_callback(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await call.answer("⛔ Покупки временно отключены.", True) await call.answer("⛔ Покупки временно отключены.", True)
@@ -82,7 +85,7 @@ async def filter_buy_callback(call: CallbackQuery, bot: Bot, state: FSM, arSessi
################################################################################ ################################################################################
############################### СТАТУС ПОПОЛНЕНИЙ ############################## ############################### СТАТУС ПОПОЛНЕНИЙ ##############################
# Фильтр на доступность пополнения - сообщение # Фильтр на доступность пополнения - сообщение
@router.message(IsRefill(), StateFilter('here_refill_amount')) @router.message(IsRefill(), StateFilter("here_refill_amount"))
async def filter_refill_message(message: Message, bot: Bot, state: FSM, arSession: ARS): async def filter_refill_message(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -91,7 +94,9 @@ async def filter_refill_message(message: Message, bot: Bot, state: FSM, arSessio
# Фильтр на доступность пополнения - колбэк # Фильтр на доступность пополнения - колбэк
@router.callback_query(IsRefill(), F.data.startswith(prohibit_refill)) @router.callback_query(IsRefill(), F.data.startswith(prohibit_refill))
async def filter_refill_callback(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def filter_refill_callback(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await call.answer("⛔ Пополнение временно отключено.", True) await call.answer("⛔ Пополнение временно отключено.", True)
@@ -100,7 +105,7 @@ async def filter_refill_callback(call: CallbackQuery, bot: Bot, state: FSM, arSe
################################################################################ ################################################################################
#################################### ПРОЧЕЕ #################################### #################################### ПРОЧЕЕ ####################################
# Открытие главного меню # Открытие главного меню
@router.message(F.text.in_(('🔙 Главное меню', '/start'))) @router.message(F.text.in_(("🔙 Главное меню", "/start")))
async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS): async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -115,11 +120,13 @@ async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS):
# Открытие диплинков # Открытие диплинков
@router.message(F.text.startswith('/start ')) @router.message(F.text.startswith("/start "))
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():
+13 -25
View File
@@ -5,7 +5,7 @@ from aiogram import Router, Bot, F
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from tgbot.data.config import BOT_VERSION, get_text_desc, get_text_warning from tgbot.data.config import BOT_VERSION, get_text_desc
from tgbot.database import Purchasesx, Settingsx from tgbot.database import Purchasesx, Settingsx
from tgbot.keyboards.inline_user import user_support_finl from tgbot.keyboards.inline_user import user_support_finl
from tgbot.keyboards.inline_user_page import * from tgbot.keyboards.inline_user_page import *
@@ -60,7 +60,7 @@ async def user_available(message: Message, bot: Bot, state: FSM, arSession: ARS)
# Открытие FAQ # Открытие FAQ
@router.message(F.text.in_(('❔ FAQ', '/faq'))) @router.message(F.text.in_(("❔ FAQ", "/faq")))
async def user_faq(message: Message, bot: Bot, state: FSM, arSession: ARS): async def user_faq(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -68,15 +68,7 @@ async def user_faq(message: Message, bot: Bot, state: FSM, arSession: ARS):
if get_settings.misc_faq == "None": if get_settings.misc_faq == "None":
return await message.answer( return await message.answer(
ded(f""" ded(f"""❔ Текст FAQ не указан."""),
Текст FAQ не указан. Измените его в настройках бота.
{get_text_desc()}
{get_text_warning()}
"""),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
@@ -87,7 +79,7 @@ async def user_faq(message: Message, bot: Bot, state: FSM, arSession: ARS):
# Открытие сообщения с ссылкой на поддержку # Открытие сообщения с ссылкой на поддержку
@router.message(F.text.in_(('☎️ Поддержка', '/support'))) @router.message(F.text.in_(("☎️ Поддержка", "/support")))
async def user_support(message: Message, bot: Bot, state: FSM, arSession: ARS): async def user_support(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -95,15 +87,7 @@ async def user_support(message: Message, bot: Bot, state: FSM, arSession: ARS):
if get_settings.misc_support == "None": if get_settings.misc_support == "None":
return await message.answer( return await message.answer(
ded(f""" ded(f"""☎️ Контакты поддержки не указаны."""),
Контакты поддержки не указаны. Измените их в настройках бота.
{get_text_desc()}
{get_text_warning()}
"""),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
@@ -114,7 +98,7 @@ async def user_support(message: Message, bot: Bot, state: FSM, arSession: ARS):
# Получение версии бота # Получение версии бота
@router.message(Command(commands=['version'])) @router.message(Command(commands=["version"]))
async def admin_version(message: Message, bot: Bot, state: FSM, arSession: ARS): async def admin_version(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -122,7 +106,7 @@ async def admin_version(message: Message, bot: Bot, state: FSM, arSession: ARS):
# Получение информации о боте # Получение информации о боте
@router.message(Command(commands=['dj_desc'])) @router.message(Command(commands=["dj_desc"]))
async def admin_desc(message: Message, bot: Bot, state: FSM, arSession: ARS): async def admin_desc(message: Message, bot: Bot, state: FSM, arSession: ARS):
await state.clear() await state.clear()
@@ -132,7 +116,9 @@ async def admin_desc(message: Message, bot: Bot, state: FSM, arSession: ARS):
################################################################################ ################################################################################
# Переход к профилю # Переход к профилю
@router.callback_query(F.data == "user_profile") @router.callback_query(F.data == "user_profile")
async def user_profile_return(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_profile_return(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear() await state.clear()
await del_message(call.message) await del_message(call.message)
@@ -176,7 +162,9 @@ async def user_purchases(call: CallbackQuery, bot: Bot, state: FSM, arSession: A
# Страницы наличия товаров # Страницы наличия товаров
@router.callback_query(F.data.startswith("user_available_swipe:")) @router.callback_query(F.data.startswith("user_available_swipe:"))
async def user_available_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_available_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
items_available, remover_max, remover_now = await get_items_available(remover) items_available, remover_max, remover_now = await get_items_available(remover)
+46 -15
View File
@@ -5,10 +5,21 @@ 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 Positionx, Userx, Categoryx, Itemx, Paymentsx, Purchasesx, Settingsx from tgbot.database import (
Positionx,
Userx,
Categoryx,
Itemx,
Paymentsx,
Purchasesx,
Settingsx,
)
from tgbot.keyboards.inline_user import refill_method_buy_finl from tgbot.keyboards.inline_user import refill_method_buy_finl
from tgbot.keyboards.inline_user_page import * from tgbot.keyboards.inline_user_page import *
from tgbot.keyboards.inline_user_products import products_buy_confirm_finl, products_return_finl from tgbot.keyboards.inline_user_products import (
products_buy_confirm_finl,
products_return_finl,
)
from tgbot.keyboards.reply_main import menu_frep from tgbot.keyboards.reply_main import menu_frep
from tgbot.utils.const_functions import ded, del_message, convert_date, send_admins from tgbot.utils.const_functions import ded, del_message, convert_date, send_admins
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
@@ -20,7 +31,9 @@ router = Router(name=__name__)
# Страницы выбора категории для покупки товара # Страницы выбора категории для покупки товара
@router.callback_query(F.data.startswith("buy_category_swipe:")) @router.callback_query(F.data.startswith("buy_category_swipe:"))
async def user_buy_category_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_buy_category_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
remover = int(call.data.split(":")[1]) remover = int(call.data.split(":")[1])
await call.message.edit_text( await call.message.edit_text(
@@ -31,7 +44,9 @@ async def user_buy_category_swipe(call: CallbackQuery, bot: Bot, state: FSM, arS
# Открытие категории с выбором позиции для покупки товара # Открытие категории с выбором позиции для покупки товара
@router.callback_query(F.data.startswith("buy_category_open:")) @router.callback_query(F.data.startswith("buy_category_open:"))
async def user_buy_category_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_buy_category_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -55,7 +70,9 @@ async def user_buy_category_open(call: CallbackQuery, bot: Bot, state: FSM, arSe
# Страницы выбора позиции для покупки товара # Страницы выбора позиции для покупки товара
@router.callback_query(F.data.startswith("buy_position_swipe:")) @router.callback_query(F.data.startswith("buy_position_swipe:"))
async def user_buy_position_swipe(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_buy_position_swipe(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
category_id = int(call.data.split(":")[1]) category_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -70,7 +87,9 @@ async def user_buy_position_swipe(call: CallbackQuery, bot: Bot, state: FSM, arS
# Открытие позиции для покупки товара # Открытие позиции для покупки товара
@router.callback_query(F.data.startswith("buy_position_open:")) @router.callback_query(F.data.startswith("buy_position_open:"))
async def user_buy_position_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def user_buy_position_open(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
position_id = int(call.data.split(":")[1]) position_id = int(call.data.split(":")[1])
remover = int(call.data.split(":")[2]) remover = int(call.data.split(":")[2])
@@ -94,7 +113,7 @@ async def user_buy_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: AR
# Проверка, имеется ли на балансе пользователя достаточно средств # Проверка, имеется ли на балансе пользователя достаточно средств
if get_user.user_balance < get_position.position_price: if get_user.user_balance < get_position.position_price:
if get_payments.status_cryptobot == "True" or get_payments.status_yoomoney == "True": if get_payments.status_cryptobot == "True":
await call.message.answer( await call.message.answer(
"<b>❗ На вашем счёте недостаточно средств</b>\n" "<b>❗ На вашем счёте недостаточно средств</b>\n"
"💰 Выберите способ пополнения баланса", "💰 Выберите способ пополнения баланса",
@@ -103,7 +122,9 @@ async def user_buy_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: AR
return await call.answer(cache_time=5) return await call.answer(cache_time=5)
else: else:
return await call.answer("❗ У вас недостаточно средств. Пополните баланс", True) return await call.answer(
"❗ У вас недостаточно средств. Пополните баланс", True
)
if len(get_items) < 1: if len(get_items) < 1:
return await call.answer("❗ Товаров нет в наличии", True) return await call.answer("❗ Товаров нет в наличии", True)
@@ -133,7 +154,9 @@ async def user_buy_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: AR
Количество: <code>1шт</code> Количество: <code>1шт</code>
Сумма к покупке: <code>{get_position.position_price}</code> Сумма к покупке: <code>{get_position.position_price}</code>
"""), """),
reply_markup=products_buy_confirm_finl(position_id, get_position.category_id, 1), reply_markup=products_buy_confirm_finl(
position_id, get_position.category_id, 1
),
) )
else: else:
await state.update_data(here_buy_position_id=position_id) await state.update_data(here_buy_position_id=position_id)
@@ -155,7 +178,7 @@ async def user_buy_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: AR
# Принятие количества товаров для покупки # Принятие количества товаров для покупки
@router.message(F.text, StateFilter("here_item_count")) @router.message(F.text, StateFilter("here_item_count"))
async def user_buy_count(message: Message, bot: Bot, state: FSM, arSession: ARS): async def user_buy_count(message: Message, bot: Bot, state: FSM, arSession: ARS):
position_id = (await state.get_data())['here_buy_position_id'] position_id = (await state.get_data())["here_buy_position_id"]
get_position = await Positionx().get_required(position_id=position_id) get_position = await Positionx().get_required(position_id=position_id)
get_user = await Userx().get_required(user_id=message.from_user.id) get_user = await Userx().get_required(user_id=message.from_user.id)
@@ -191,7 +214,9 @@ async def user_buy_count(message: Message, bot: Bot, state: FSM, arSession: ARS)
# Если товаров нет в наличии # Если товаров нет в наличии
if len(get_items) < 1: if len(get_items) < 1:
await state.clear() await state.clear()
return await message.answer("<b>🎁 Товар который вы хотели купить, закончился</b>") return await message.answer(
"<b>🎁 Товар который вы хотели купить, закончился</b>"
)
# Если введено кол-во товаров меньше 1 или меньше кол-ва имеющегося в наличии # Если введено кол-во товаров меньше 1 или меньше кол-ва имеющегося в наличии
if get_count < 1 or get_count > len(get_items): if get_count < 1 or get_count > len(get_items):
@@ -217,7 +242,9 @@ async def user_buy_count(message: Message, bot: Bot, state: FSM, arSession: ARS)
Количество: <code>{get_count}шт</code> Количество: <code>{get_count}шт</code>
Сумма к покупке: <code>{amount_pay}</code> Сумма к покупке: <code>{amount_pay}</code>
"""), """),
reply_markup=products_buy_confirm_finl(position_id, get_position.category_id, get_count), reply_markup=products_buy_confirm_finl(
position_id, get_position.category_id, get_count
),
) )
@@ -240,9 +267,13 @@ async def user_buy_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession:
elif purchase_result == "POSITION_NOT_FOUND": elif purchase_result == "POSITION_NOT_FOUND":
return await call.message.edit_text("<b>❌ Позиция не была найдена</b>") return await call.message.edit_text("<b>❌ Позиция не была найдена</b>")
elif purchase_result == "NOT_ENOUGH_ITEMS": elif purchase_result == "NOT_ENOUGH_ITEMS":
return await call.message.edit_text("<b>❌ В наличии недостаточно товаров. Попробуйте другое кол-во</b>") return await call.message.edit_text(
"<b>❌ В наличии недостаточно товаров. Попробуйте другое кол-во</b>"
)
elif purchase_result == "NOT_ENOUGH_BALANCE": elif purchase_result == "NOT_ENOUGH_BALANCE":
return await call.message.edit_text("<b>❌ На вашем балансе недостаточно средств</b>") return await call.message.edit_text(
"<b>❌ На вашем балансе недостаточно средств</b>"
)
get_user = await Userx().get_required(user_id=call.from_user.id) get_user = await Userx().get_required(user_id=call.from_user.id)
get_settings = await Settingsx().get() get_settings = await Settingsx().get()
@@ -272,5 +303,5 @@ async def user_buy_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession:
Пользователь: <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> Пользователь: <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>{purchase_result.position_name} | {purchase_result.purchase_count}шт | {purchase_result.purchase_price}</code> Товар: <code>{purchase_result.position_name} | {purchase_result.purchase_count}шт | {purchase_result.purchase_price}</code>
Чек: <code>#{purchase_result.receipt}</code> Чек: <code>#{purchase_result.receipt}</code>
""") """),
) )
+359
View File
@@ -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>
"""),
)
+169 -91
View File
@@ -5,17 +5,18 @@ 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.services.api_yoomoney import YoomoneyAPI
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
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 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,9 +27,9 @@ async def refill_method_list(call: CallbackQuery, bot: Bot, state: FSM, arSessio
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
if ( if (
get_payments.status_cryptobot == "False" and get_payments.status_cryptobot == "False"
get_payments.status_yoomoney == "False" and and get_payments.status_lolzteam == "False"
get_payments.status_stars == "False" and get_payments.status_stars == "False"
): ):
return await call.answer("❗️ Пополнения временно недоступны", True) return await call.answer("❗️ Пополнения временно недоступны", True)
@@ -40,17 +41,25 @@ async def refill_method_list(call: CallbackQuery, bot: Bot, state: FSM, arSessio
# Выбор способа пополнения # Выбор способа пополнения
@router.callback_query(F.data.startswith("user_refill_method:")) @router.callback_query(F.data.startswith("user_refill_method:"))
async def refill_method_select(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def refill_method_select(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
refill_method = call.data.split(":")[1] refill_method = call.data.split(":")[1]
get_payments = await Paymentsx().get() get_payments = await Paymentsx().get()
if refill_method == "Cryptobot" and get_payments.status_cryptobot == "False": if refill_method == "Cryptobot" and get_payments.status_cryptobot == "False":
return await call.answer("❌ Пополнение данным способом временно недоступно", True) return await call.answer(
elif refill_method == "Yoomoney" and get_payments.status_yoomoney == "False": "❌ Пополнение данным способом временно недоступно", True
return await call.answer("❌ Пополнение данным способом временно недоступно", 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("❌ Пополнение данным способом временно недоступно", True) return await call.answer(
"❌ Пополнение данным способом временно недоступно", True
)
await state.update_data(here_refill_method=refill_method) await state.update_data(here_refill_method=refill_method)
@@ -75,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>
💰 Введите сумму для пополнения средств 💰 Введите сумму для пополнения средств
"""), """),
) )
@@ -83,7 +92,7 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
cache_message = await message.answer("<b>♻️ Подождите, платёж генерируется..</b>") cache_message = await message.answer("<b>♻️ Подождите, платёж генерируется..</b>")
refill_amount = to_number(message.text) refill_amount = to_number(message.text)
refill_method = (await state.get_data())['here_refill_method'] refill_method = (await state.get_data())["here_refill_method"]
await state.clear() await state.clear()
# Генерация платежа # Генерация платежа
@@ -95,14 +104,16 @@ 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)
elif refill_method == "Yoomoney":
elif refill_method == "Lolzteam":
bill_message, bill_link, bill_receipt = await ( bill_message, bill_link, bill_receipt = await (
await YoomoneyAPI.connect( await LolzteamAPI.connect(
bot=bot, bot=bot,
arSession=arSession, arSession=arSession,
update=cache_message, update=cache_message,
) )
).bill(refill_amount) ).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(
@@ -111,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>"
@@ -130,48 +142,13 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
################################################################################ ################################################################################
############################### ПРОВЕРКА ПЛАТЕЖЕЙ ############################## ############################### ПРОВЕРКА ПЛАТЕЖЕЙ ##############################
# Проверка оплаты - ЮMoney
@router.callback_query(F.data.startswith('Pay:Yoomoney'))
async def refill_check_yoomoney(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
pay_method = call.data.split(":")[1]
pay_receipt = call.data.split(":")[2]
pay_status, pay_amount = await (
await YoomoneyAPI.connect(
bot=bot,
arSession=arSession,
update=call,
)
).bill_check(pay_receipt)
if pay_status == 0:
refill_status = await refill_success(
bot=bot,
call=call,
pay_method=pay_method,
pay_amount=pay_amount,
pay_receipt=int(pay_receipt),
pay_comment=pay_receipt,
)
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)
else:
await call.answer(f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.", True, cache_time=5)
# Проверка оплаты - Cryptobot # Проверка оплаты - Cryptobot
@router.callback_query(F.data.startswith('Pay:Cryptobot')) @router.callback_query(F.data.startswith("Pay:Cryptobot"))
async def refill_check_cryptobot(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def refill_check_cryptobot(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
pay_method = call.data.split(":")[1] pay_method = call.data.split(":")[1]
pay_comment = call.data.split(":")[2] pay_comment = call.data.split(":")[2]
@@ -194,22 +171,87 @@ async def refill_check_cryptobot(call: CallbackQuery, bot: Bot, state: FSM, arSe
if refill_status == "ALREADY": if refill_status == "ALREADY":
await call.answer("❗ Ваше пополнение уже зачислено.", True, cache_time=60) await call.answer("❗ Ваше пополнение уже зачислено.", True, cache_time=60)
await delete_refill_message(bot, call.message.chat.id, call.message.message_id) await delete_refill_message(
bot, call.message.chat.id, call.message.message_id
)
elif refill_status == "USER_NOT_FOUND": elif refill_status == "USER_NOT_FOUND":
await call.answer("❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30) await call.answer(
"❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30
)
elif pay_status == 1: elif pay_status == 1:
await call.answer("❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30) await call.answer(
"❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30
)
elif pay_status == 2: elif pay_status == 2:
await call.answer("❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5) await call.answer(
"❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5
)
elif pay_status == 3: elif pay_status == 3:
await call.answer("❗️ Вы не успели оплатить счёт", True, cache_time=5) await call.answer("❗️ Вы не успели оплатить счёт", True, cache_time=5)
await call.message.edit_reply_markup() await call.message.edit_reply_markup()
else: else:
await call.answer(f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.", True, cache_time=5) await call.answer(
f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.",
True,
cache_time=5,
)
# Проверка оплаты - 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):
pay_method = call.data.split(":")[1] pay_method = call.data.split(":")[1]
pay_receipt = int(call.data.split(":")[2]) pay_receipt = int(call.data.split(":")[2])
@@ -233,22 +275,36 @@ async def refill_check_stars(call: CallbackQuery, bot: Bot, state: FSM, arSessio
if refill_status == "ALREADY": if refill_status == "ALREADY":
await call.answer("❗ Ваше пополнение уже зачислено.", True, cache_time=60) await call.answer("❗ Ваше пополнение уже зачислено.", True, cache_time=60)
await delete_refill_message(bot, call.message.chat.id, call.message.message_id) await delete_refill_message(
bot, call.message.chat.id, call.message.message_id
)
elif refill_status == "USER_NOT_FOUND": elif refill_status == "USER_NOT_FOUND":
await call.answer("❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30) await call.answer(
"❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30
)
elif pay_status == 1: elif pay_status == 1:
await call.answer("❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30) await call.answer(
"❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30
)
elif pay_status == 2: elif pay_status == 2:
await call.answer("❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5) await call.answer(
"❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5
)
else: else:
await call.answer(f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.", True, cache_time=5) await call.answer(
f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.",
True,
cache_time=5,
)
################################################################################ ################################################################################
#################################### ЗВЁЗДЫ #################################### #################################### ЗВЁЗДЫ ####################################
# Подтверждение платежа Telegram Stars # Подтверждение платежа Telegram Stars
@router.pre_checkout_query() @router.pre_checkout_query()
async def refill_stars_pre_checkout(query: PreCheckoutQuery, bot: Bot, state: FSM, arSession: ARS): async def refill_stars_pre_checkout(
query: PreCheckoutQuery, bot: Bot, state: FSM, arSession: ARS
):
await ( await (
await StarsAPI.connect( await StarsAPI.connect(
bot=bot, bot=bot,
@@ -277,7 +333,9 @@ async def refill_stars_success(message: Message, bot: Bot, state: FSM, arSession
stars_api.parse_successful_payment(payment) stars_api.parse_successful_payment(payment)
) )
except ValueError: except ValueError:
bot_logger.warning("Некорректная successful_payment для Telegram Stars", exc_info=True) bot_logger.warning(
"Некорректная successful_payment для Telegram Stars", exc_info=True
)
return return
refill_status, receipt = await save_refill_success( refill_status, receipt = await save_refill_success(
@@ -293,12 +351,10 @@ async def refill_stars_success(message: Message, bot: Bot, state: FSM, arSession
if bill_chat_id == message.chat.id: if bill_chat_id == message.chat.id:
await delete_refill_message(bot, bill_chat_id, bill_message_id) await delete_refill_message(bot, bill_chat_id, bill_message_id)
await message.answer( await message.answer(ded(f"""
ded(f"""
<b>💰 Вы пополнили баланс на сумму <code>{pay_amount}</code>. Удачи <b>💰 Вы пополнили баланс на сумму <code>{pay_amount}</code>. Удачи
🧾 Чек: <code>#{receipt}</code></b> 🧾 Чек: <code>#{receipt}</code></b>
""") """))
)
elif refill_status == "ALREADY": elif refill_status == "ALREADY":
if bill_chat_id == message.chat.id: if bill_chat_id == message.chat.id:
await delete_refill_message(bot, bill_chat_id, bill_message_id) await delete_refill_message(bot, bill_chat_id, bill_message_id)
@@ -312,12 +368,12 @@ async def refill_stars_success(message: Message, bot: Bot, state: FSM, arSession
#################################### ПРОЧЕЕ #################################### #################################### ПРОЧЕЕ ####################################
# Зачисление средств # Зачисление средств
async def refill_success( async def refill_success(
bot: Bot, bot: Bot,
call: CallbackQuery, call: CallbackQuery,
pay_method: str, pay_method: str,
pay_amount: float, pay_amount: float,
pay_receipt: Optional[int] = None, pay_receipt: Optional[int] = None,
pay_comment: Optional[str] = None, pay_comment: Optional[str] = None,
): ):
user_id = call.from_user.id user_id = call.from_user.id
@@ -338,19 +394,19 @@ async def refill_success(
if response_success != "ok": if response_success != "ok":
return response_success return response_success
await call.message.answer( await call.message.answer(ded(f"""
ded(f"""
<b>💰 Вы пополнили баланс на сумму <code>{pay_amount}</code>. Удачи <b>💰 Вы пополнили баланс на сумму <code>{pay_amount}</code>. Удачи
🧾 Чек: <code>#{receipt}</code></b> 🧾 Чек: <code>#{receipt}</code></b>
""") """))
)
await delete_refill_message(bot, call.message.chat.id, call.message.message_id) await delete_refill_message(bot, call.message.chat.id, call.message.message_id)
return response_success return response_success
# Удаление сообщения со счётом # Удаление сообщения со счётом
async def delete_refill_message(bot: Bot, chat_id: Optional[int], message_id: Optional[int]) -> None: async def delete_refill_message(
bot: Bot, chat_id: Optional[int], message_id: Optional[int]
) -> None:
if chat_id is None or message_id is None: if chat_id is None or message_id is None:
return return
@@ -362,17 +418,17 @@ async def delete_refill_message(bot: Bot, chat_id: Optional[int], message_id: Op
# Сохранение пополнения и уведомление админов # Сохранение пополнения и уведомление админов
async def save_refill_success( async def save_refill_success(
bot: Bot, bot: Bot,
user_id: int, user_id: int,
pay_method: str, pay_method: str,
pay_amount: float, pay_amount: float,
pay_receipt: int, pay_receipt: int,
pay_comment: str, pay_comment: str,
) -> Tuple[str, int]: ) -> Tuple[str, int]:
if pay_method == "Yoomoney": if pay_method == "Cryptobot":
text_method = "ЮMoney"
elif 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:
@@ -401,7 +457,29 @@ async def save_refill_success(
Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={user_id}'>{get_user.user_name}</a> | <code>{user_id}</code> Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={user_id}'>{get_user.user_name}</a> | <code>{user_id}</code>
Сумма пополнения: <code>{pay_amount}</code> <code>({text_method})</code> Сумма пополнения: <code>{pay_amount}</code> <code>({text_method})</code>
Чек: <code>#{pay_receipt}</code> Чек: <code>#{pay_receipt}</code>
""") """),
) )
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
+69 -46
View File
@@ -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:
-239
View File
@@ -1,239 +0,0 @@
# - *- coding: utf- 8 - *-
import asyncio
import json
from io import BytesIO
from typing import Any, Dict, Optional, Tuple, Union
import ujson
from aiogram import Bot
from aiogram.types import CallbackQuery, Message
from aiohttp import ClientConnectorCertificateError, FormData
from tgbot.database import Settingsx
from tgbot.utils.const_functions import gen_id, send_errors
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
class DiscordAPI:
# Настройка клиента Discord webhook
def __init__(
self,
bot: Bot,
arSession: ARS,
update: Optional[Union[Message, CallbackQuery]] = None,
webhook_url: str = "None",
adding: bool = False,
skipping_error: bool = False,
):
self.bot = bot
self.arSession = arSession
self.update = update
self.adding = adding
self.skipping_error = skipping_error
self.webhook_username = "Djimbo Shop | Free Bot"
self.base_url = "https://djimbo.dev/dsapi"
webhook_url = self._normalize_webhook_url(webhook_url)
if "/" in webhook_url:
self.webhook_id = webhook_url.split("/")[0]
self.webhook_token = webhook_url.split("/")[1]
else:
self.webhook_id = ""
self.webhook_token = ""
# Нормализация Discord webhook URL
@staticmethod
def _normalize_webhook_url(webhook_url: str) -> str:
if webhook_url is None:
return "None"
if webhook_url.startswith("https://discord.com/api/"):
webhook_url = webhook_url[33:]
if webhook_url.startswith("discord.com/api/webhooks/"):
webhook_url = webhook_url[25:]
return webhook_url
# Инициализация данных
@classmethod
async def connect(
cls,
bot: Bot,
arSession: ARS,
update: Optional[Union[Message, CallbackQuery]] = None,
webhook_url: Optional[str] = None,
skipping_error: bool = False,
) -> "DiscordAPI":
adding = webhook_url is not None
if webhook_url is None:
settings = await Settingsx().get()
webhook_url = settings.misc_discord_webhook_url
adding = False
return cls(
bot=bot,
arSession=arSession,
update=update,
webhook_url=webhook_url,
adding=adding,
skipping_error=skipping_error,
)
# Рассылка админам о нерабочем вебхуке
async def error_account_admin(self, error_code: str = "Unknown"):
bot_logger.warning("Discord webhook недоступен: %s", error_code)
if not self.skipping_error:
await send_admins(
self.bot,
f"<b>🧿 Дискорд вебхук недоступен. Как можно быстрее его замените</b>\n"
f"❗️ Ошибка: <code>{error_code}</code>"
)
# Проверка вебхука
async def check(self) -> Tuple[bool, str]:
request_url = f"{self.base_url}/webhooks/{self.webhook_id}/{self.webhook_token}"
status, response = await self._request(
request_url=request_url,
request_method="GET",
)
if status and "channel_id" in response:
discord_channel_id = response['channel_id']
discord_hook_name = response['name']
return True, discord_hook_name
return False, ""
# Загрузка фотографий
async def upload_photo(self, photo_data: Union[BytesIO, bytes], photo_name: Optional[str] = None) -> Tuple[
bool, str]:
request_url = f"{self.base_url}/webhooks/{self.webhook_id}/{self.webhook_token}"
if photo_name is None:
photo_name = str(gen_id(24))
if not photo_name.endswith(".png") and not photo_name.endswith(".jpg"):
photo_name = f"{photo_name}.png"
send_json = {
'username': self.webhook_username,
'content': '',
}
data = FormData()
data.add_field('file', photo_data, filename=photo_name)
data.add_field('payload_json', ujson.dumps(send_json))
await asyncio.sleep(1)
status, response = await self._request(
request_url=request_url,
request_method="POST",
request_data=data,
)
if "id" in response:
channel_id = response['channel_id']
message_id = response['id']
get_discord_forevercdn = await (
DiscordDJ(
arSession=self.arSession,
bot=self.bot,
)
).export_forevercdn()
photo_url = f"{get_discord_forevercdn}/attachments/{channel_id}/{message_id}"
return True, photo_url
return False, "None"
# Запрос
async def _request(
self,
request_url: str,
request_method: str,
request_data: Optional[Union[Dict[str, Any], FormData]] = None,
) -> Tuple[bool, Any]:
session = await self.arSession.get_session()
await asyncio.sleep(1)
try:
response = await session.request(
method=request_method,
url=request_url,
data=request_data,
headers={"Accept-Encoding": "gzip, deflate"},
)
response_data = json.loads((await response.read()).decode())
if response.status == 200:
return True, response_data
else:
await self.error_account_admin(f"{response.status} - {str(response_data)}")
return False, response_data
except ClientConnectorCertificateError:
bot_logger.warning("Ошибка SSL при запросе Discord webhook", exc_info=True)
await self.error_account_admin("CERTIFICATE_VERIFY_FAILED")
return False, "CERTIFICATE_VERIFY_FAILED"
except Exception as ex:
bot_logger.warning("Ошибка запроса Discord webhook", exc_info=True)
await self.error_account_admin(str(ex))
return False, str(ex)
# Извлечение ссылок для работы с локальным АПИ дискорда
class DiscordDJ:
# Настройка клиента сервисных ссылок Discord
def __init__(self, arSession: ARS, bot: Bot):
self.arSession = arSession
self.bot = bot
self.const_url = "https://djimbo.dev/autoshop_discord.json"
# Экспорт публичного дискорд вебхука
async def export_webhook(self) -> str:
session = await self.arSession.get_session()
try:
response = await session.get(
self.const_url,
headers={"Accept-Encoding": "gzip, deflate"},
)
response_data = json.loads((await response.read()).decode())
except Exception as ex:
await send_errors(self.bot, 7729051, f"Error getting Discord Webhook - {ex}")
return "None"
else:
return response_data['webhook']
# Экспорт ссылки на постоянный CDN
async def export_forevercdn(self) -> str:
session = await self.arSession.get_session()
try:
response = await session.get(
self.const_url,
headers={"Accept-Encoding": "gzip, deflate"},
)
response_data = json.loads((await response.read()).decode())
except Exception as ex:
await send_errors(self.bot, 7729051, f"Error getting Discord ForeverCDN - {ex}")
return "None"
else:
return response_data['forevercdn']
+6 -88
View File
@@ -16,19 +16,16 @@ from tgbot.utils.misc.bot_models import ARS
@dataclass @dataclass
class ModelHostingText: class ModelHostingText:
telegraph: str = "telegraph" telegraph: str = "telegraph"
pastie: str = "pastie"
friendpaste: str = "friendpaste"
snippet: str = "snippet"
# API для работы с текстовыми хостингами # API для работы с текстовыми хостингами
class HostingAPI: class HostingAPI:
# Настройка клиента текстового хостинга # Настройка клиента текстового хостинга
def __init__( def __init__(
self, self,
arSession: ARS, arSession: ARS,
bot: Bot, bot: Bot,
text_hosting: str, text_hosting: str,
): ):
self.arSession = arSession self.arSession = arSession
self.bot = bot self.bot = bot
@@ -42,7 +39,7 @@ class HostingAPI:
return cls( return cls(
arSession=arSession, arSession=arSession,
bot=bot, bot=bot,
text_hosting=settings.misc_hosting_text, # telegraph, pastie, friendpaste, snippet text_hosting=settings.misc_hosting_text, # telegraph
) )
# Загрузка текста на хостинг # Загрузка текста на хостинг
@@ -67,89 +64,10 @@ class HostingAPI:
await send_errors(self.bot, 4044321, str(response)) await send_errors(self.bot, 4044321, str(response))
status_success = False status_success = False
############################################################
########################## PASTIE ##########################
elif self.text_hosting == ModelHostingText.pastie:
try:
response = await session.request(
method="post",
url="http://pastie.org/pastes/create/",
data={
'language': 'plaintext',
'content': text,
},
)
except Exception as ex:
bot_logger.warning("Ошибка загрузки текста в Pastie", exc_info=True)
await send_errors(self.bot, 4719012, str(ex))
status_success = False
else:
cache_link = response.url
if "create" in str(cache_link) or str(cache_link) == "http://pastie.org/":
status_success = False
else:
return_link = cache_link
############################################################
######################## FRIENDPASTE #######################
elif self.text_hosting == ModelHostingText.friendpaste:
try:
response = await session.request(
method="post",
url="https://www.friendpaste.com/",
json={
'language': 'text',
'title': '',
'snippet': text,
},
)
cache_link = json.loads((await response.read()).decode())['url']
except Exception as ex:
bot_logger.warning("Ошибка загрузки текста в FriendPaste", exc_info=True)
await send_errors(self.bot, 8434711, str(ex))
status_success = False
else:
return_link = cache_link
############################################################
######################### SNIPPET ##########################
elif self.text_hosting == ModelHostingText.snippet:
try:
response = await session.request(
method="post",
url="https://snippet.host/",
data={
'title': '',
'content': text,
'visibility': '1',
'expires': 'never',
'language': 'plain text'
},
)
cache_link = response.url
except Exception as ex:
bot_logger.warning("Ошибка загрузки текста в Snippet", exc_info=True)
await send_errors(self.bot, 8900012, str(ex))
status_success = False
else:
return_link = cache_link
if status_success: if status_success:
return str(return_link) return str(return_link)
# pastie -> friendpaste self.text_hosting = ModelHostingText.telegraph
if self.text_hosting == ModelHostingText.pastie:
self.text_hosting = ModelHostingText.friendpaste
# friendpaste -> snippet
elif self.text_hosting == ModelHostingText.friendpaste:
self.text_hosting = ModelHostingText.snippet
# snippet -> telegraph
elif self.text_hosting == ModelHostingText.snippet:
self.text_hosting = ModelHostingText.telegraph
# telegraph -> pastie
elif self.text_hosting == ModelHostingText.telegraph:
self.text_hosting = ModelHostingText.pastie
if attempt < max_attempts - 1: if attempt < max_attempts - 1:
await asyncio.sleep(3) await asyncio.sleep(3)
+303
View File
@@ -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)
-339
View File
@@ -1,339 +0,0 @@
# - *- coding: utf- 8 - *-
import json
from typing import Any, Dict, Optional, Tuple, Union
from urllib.parse import urlencode
from aiogram import Bot
from aiogram.types import CallbackQuery, Message
from aiohttp import ClientConnectorCertificateError
from tgbot.data.config import YOOMONEY_CLIENT_ID
from tgbot.database import Paymentsx
from tgbot.utils.const_functions import ded, 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
# АПИ для работы с ЮMoney
class YoomoneyAPI:
# Настройка клиента ЮMoney
def __init__(
self,
bot: Bot,
arSession: ARS,
update: Optional[Union[Message, CallbackQuery]] = None,
token: str = "None",
adding: bool = False,
skipping_error: bool = False,
):
self.token = token
self.adding = adding
self.base_url = 'https://yoomoney.ru/api/'
self.headers = {
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/x-www-form-urlencoded',
}
self.bot = bot
self.arSession = arSession
self.update = update
self.skipping_error = skipping_error
# Инициализация данных
@classmethod
async def connect(
cls,
bot: Bot,
arSession: ARS,
update: Optional[Union[Message, CallbackQuery]] = None,
token: Optional[str] = None,
skipping_error: bool = False,
) -> "YoomoneyAPI":
adding = token is not None
if token is None:
payments = await Paymentsx().get()
token = payments.yoomoney_token
adding = False
return cls(
bot=bot,
arSession=arSession,
update=update,
token=token,
adding=adding,
skipping_error=skipping_error,
)
# Уведомления о неработоспособности кассы/кошелька
async def error_notification(self, error_code: str = "Unknown"):
bot_logger.warning("ЮMoney недоступен: %s", error_code)
if not self.skipping_error:
if self.adding and self.update is not None:
await self.update.edit_text(
f"<b>🔮 Не удалось добавить ЮMoney кассу ❌</b>\n"
f"❗️ Ошибка: <code>{error_code}</code>"
)
else:
await send_admins(
self.bot,
f"<b>🔮 ЮMoney недоступен. Как можно быстрее его замените</b>\n"
f"❗️ Ошибка: <code>{error_code}</code>"
)
# Проверка кассы/кошелька
async def check(self) -> str:
status, response = await self._request("account-info")
if status:
if len(response) >= 1:
if response['identified']:
text_identified = "Присутствует"
else:
text_identified = "Отсутствует"
if response['account_status'] == "identified":
text_status = "Идентифицированный счет"
elif response['account_status'] == "anonymous":
text_status = "Анонимный счет"
elif response['account_status'] == "named":
text_status = "Именной счет"
else:
text_status = response['account_status']
if response['account_type'] == "personal":
text_type = "Пользовательский счет"
elif response['account_type'] == "professional":
text_type = "Профессиональный счет"
else:
text_type = response['account_type']
return ded(f"""
<b>🔮 ЮMoney кошелёк полностью функционирует </b>
Кошелёк: <code>{response['account']}</code>
Идентификация: <code>{text_identified}</code>
Статус аккаунта: <code>{text_status}</code>
Тип счета: <code>{text_type}</code>
""")
return "<b>🔮 Не удалось проверить ЮMoney кошелёк ❌</b>"
# Получение баланса
async def balance(self) -> str:
status, response = await self._request("account-info")
if status:
wallet_balance = response['balance']
wallet_status, wallet_number = await self.account_info()
if wallet_status:
return ded(f"""
<b>🔮 Баланс ЮMoney кошелька составляет</b>
Кошелёк: <code>{wallet_number}</code>
Баланс: <code>{wallet_balance}</code>
""")
return "<b>🔮 Не удалось получить баланс ЮMoney кошелька ❌</b>"
# Информация об аккаунте
async def account_info(self) -> Tuple[bool, str]:
status, response = await self._request("account-info")
try:
return True, response['account']
except Exception:
bot_logger.warning("Не удалось получить номер ЮMoney", exc_info=True)
return False, ""
# Получение ссылки на авторизацию
async def authorization_get(self) -> str:
if not YOOMONEY_CLIENT_ID:
return "В .env не заполнен YOOMONEY_CLIENT_ID"
session = await self.arSession.get_session()
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
url = "https://yoomoney.ru/oauth/authorize?" + urlencode({
"client_id": YOOMONEY_CLIENT_ID,
"response_type": "code",
"redirect_uri": "https://yoomoney.ru",
"scope": "account-info operation-history operation-details",
})
response = await session.post(
url=url,
headers=headers,
)
return str(response.url)
# Принятие кода авторизации и получение токена
async def authorization_enter(self, get_code: str) -> Tuple[bool, str, str]:
if not YOOMONEY_CLIENT_ID:
return False, "", "<b>❌ В .env не заполнен YOOMONEY_CLIENT_ID</b>"
session = await self.arSession.get_session()
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
url = "https://yoomoney.ru/oauth/token?" + urlencode({
"code": get_code,
"client_id": YOOMONEY_CLIENT_ID,
"grant_type": "authorization_code",
"redirect_uri": "https://yoomoney.ru",
})
response = await session.post(
url=url,
headers=headers,
)
response_data = json.loads((await response.read()).decode())
if "error" in response_data:
error = response_data['error']
if error == "invalid_request":
return_message = ded(f"""
<b> Требуемые параметры запроса отсутствуют или имеют неправильные или недопустимые значения</b>
""")
elif error == "unauthorized_client":
return_message = ded(f"""
<b> Недопустимое значение параметра 'client_id' или 'client_secret', или приложение
не имеет права запрашивать авторизацию (например, ЮMoney заблокировал его 'client_id')</b>
""")
elif error == "invalid_grant":
return_message = ded(f"""
<b> В выпуске 'access_token' отказано. ЮMoney не выпускал временный токен,
срок действия токена истек или этот временный токен уже выдан
'access_token' (повторный запрос токена авторизации с тем же временным токеном)</b>
""")
else:
return_message = f"Unknown error: {error}"
return False, "", return_message
elif response_data['access_token'] == "":
return False, "", "<b>❌ Не удалось получить токен. Попробуйте всё снова</b>"
return True, response_data['access_token'], "<b>🔮 ЮMoney кошелёк был успешно изменён ✅</b>"
# Создание счета на оплату
async def bill(self, pay_amount: Union[float, int]) -> Tuple[Union[str, bool], str, str]:
session = await self.arSession.get_session()
bill_receipt = str(gen_id(12))
url = "https://yoomoney.ru/quickpay/confirm.xml?"
wallet_status, wallet_number = await self.account_info()
if wallet_status:
pay_amount_bill = pay_amount + (pay_amount * 0.031)
if float(pay_amount_bill) < 2:
pay_amount_bill = 2.04
payload = {
'receiver': wallet_number,
'quickpay_form': 'button',
'targets': 'Добровольное пожертвование',
'paymentType': 'SB',
'sum': pay_amount_bill,
'label': bill_receipt,
}
for value in payload:
url += str(value).replace("_", "-") + "=" + str(payload[value])
url += "&"
bill_link = str((await session.post(url[:-1].replace(" ", "%20"))).url)
bill_message = ded(f"""
<b>💰 Пополнение баланса</b>
Для пополнения баланса, нажмите на кнопку ниже
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
У вас имеется 60 минут на оплату счета
Сумма пополнения: <code>{pay_amount}</code>
После оплаты, нажмите на <code>Проверить оплату</code>
""")
return bill_message, bill_link, bill_receipt
return False, "", ""
# Проверка счета на оплату
async def bill_check(self, bill_receipt: Optional[Union[str, int]] = None, records: int = 1) -> Tuple[int, float]:
data = {
'type': 'deposition',
'details': 'true',
}
if bill_receipt is not None:
data['label'] = bill_receipt
if records is not None:
data['records'] = str(records)
status, response = await self._request("operation-history", data)
pay_status, pay_amount, pay_currency = 1, 0, 0
if status:
pay_status = 2
if len(response['operations']) >= 1:
pay_currency = response['operations'][0]['amount_currency']
pay_amount = response['operations'][0]['amount']
pay_status = 3
if pay_currency == "RUB":
pay_status = 0
return pay_status, pay_amount
# Генерация запроса
async def _request(
self,
method: str,
data: Optional[Dict[str, Any]] = None,
) -> Tuple[bool, Any]:
session = await self.arSession.get_session()
url = self.base_url + method
try:
response = await session.post(
url=url,
headers=self.headers,
data=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 при запросе ЮMoney", exc_info=True)
await self.error_notification("CERTIFICATE_VERIFY_FAILED")
return False, "CERTIFICATE_VERIFY_FAILED"
except Exception as ex:
bot_logger.warning("Ошибка запроса ЮMoney", exc_info=True)
await self.error_notification(str(ex))
return False, str(ex)
+37 -38
View File
@@ -29,13 +29,13 @@ def rkb(text: str) -> KeyboardButton:
# Быстрая сборка inline-кнопки # Быстрая сборка inline-кнопки
def ikb( def ikb(
text: str, text: str,
data: Optional[str] = None, data: Optional[str] = None,
url: Optional[str] = None, url: Optional[str] = None,
switch: Optional[str] = None, switch: Optional[str] = None,
web: Optional[str] = None, web: Optional[str] = None,
copy: Optional[str] = None, copy: Optional[str] = None,
login: Optional[str] = None, login: Optional[str] = None,
) -> InlineKeyboardButton: ) -> InlineKeyboardButton:
if data is not None: if data is not None:
return InlineKeyboardButton(text=text, callback_data=data) return InlineKeyboardButton(text=text, callback_data=data)
@@ -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-кнопки")
@@ -61,36 +61,27 @@ async def del_message(message: Message) -> None:
bot_logger.debug("Не удалось удалить сообщение", exc_info=True) bot_logger.debug("Не удалось удалить сообщение", exc_info=True)
# Отправка фото или обычного сообщения # Отправка обычного сообщения
async def smart_message( async def smart_message(
bot: Bot, bot: Bot,
user_id: int, user_id: int,
text: str, text: str,
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None, keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
photo: Optional[str] = None,
) -> None: ) -> None:
if photo is not None and photo.title() != "None": await bot.send_message(
await bot.send_photo( chat_id=user_id,
chat_id=user_id, text=text,
photo=photo, reply_markup=keyboard,
caption=text, )
reply_markup=keyboard,
)
else:
await bot.send_message(
chat_id=user_id,
text=text,
reply_markup=keyboard,
)
# Отправка сообщения всем админам # Отправка сообщения всем админам
async def send_admins( async def send_admins(
bot: Bot, bot: Bot,
text: str, text: str,
keyboard: Optional[InlineKeyboardMarkup] = None, keyboard: Optional[InlineKeyboardMarkup] = None,
markup: Optional[InlineKeyboardMarkup] = None, markup: Optional[InlineKeyboardMarkup] = None,
not_me: int = 0, not_me: int = 0,
) -> None: ) -> None:
reply_markup = markup or keyboard reply_markup = markup or keyboard
@@ -104,7 +95,9 @@ async def send_admins(
disable_web_page_preview=True, disable_web_page_preview=True,
) )
except Exception: except Exception:
bot_logger.warning("Не удалось отправить сообщение админу %s", admin, exc_info=True) bot_logger.warning(
"Не удалось отправить сообщение админу %s", admin, exc_info=True
)
# Логирование ошибки и отправка админам # Логирование ошибки и отправка админам
@@ -120,8 +113,10 @@ def ded(get_text: str) -> str:
if get_text is not None: if get_text is not None:
split_text = get_text.split("\n") split_text = get_text.split("\n")
if split_text[0] == "": split_text.pop(0) if split_text[0] == "":
if split_text[-1] == "": split_text.pop() split_text.pop(0)
if split_text[-1] == "":
split_text.pop()
save_text = [] save_text = []
for text in split_text: for text in split_text:
@@ -163,7 +158,7 @@ def convert_list(get_lists: List[list]) -> list:
# Разделение списка на части нужного размера # Разделение списка на части нужного размера
def split_list(get_list: list, count: int) -> List[list]: def split_list(get_list: list, count: int) -> List[list]:
return [get_list[i:i + count] for i in range(0, len(get_list), count)] return [get_list[i : i + count] for i in range(0, len(get_list), count)]
# Старое имя для разделения сообщений # Старое имя для разделения сообщений
@@ -198,9 +193,13 @@ def convert_date(from_time, full=True, second=True) -> Union[str, int]:
from_timestamp = int(from_time) from_timestamp = int(from_time)
if full: if full:
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M:%S") return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime(
"%d.%m.%Y %H:%M:%S"
)
if second: if second:
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M") return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime(
"%d.%m.%Y %H:%M"
)
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y") return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y")
+30 -15
View File
@@ -7,8 +7,13 @@ from typing import Union
from aiogram import Bot from aiogram import Bot
from aiogram.types import FSInputFile, CallbackQuery, Message from aiogram.types import FSInputFile, CallbackQuery, Message
from tgbot.data.config import BOT_DATABASE_EXPORT, BOT_STATUS_NOTIFICATION, BOT_VERSION, PATH_DATABASE, get_admins, \ from tgbot.data.config import (
get_text_desc BOT_DATABASE_EXPORT,
BOT_STATUS_NOTIFICATION,
BOT_VERSION,
PATH_DATABASE,
get_admins,
)
from tgbot.database import Userx, Settingsx from tgbot.database import Userx, Settingsx
from tgbot.utils.const_functions import get_unix, get_date, ded, send_admins from tgbot.utils.const_functions import get_unix, get_date, ded, send_admins
from tgbot.utils.misc.bot_logging import bot_logger from tgbot.utils.misc.bot_logging import bot_logger
@@ -40,9 +45,17 @@ async def autosettings_unix():
now_month = datetime.now().month now_month = datetime.now().month
now_year = datetime.now().year now_year = datetime.now().year
unix_day = int(datetime.strptime(f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp()) unix_day = int(
datetime.strptime(
f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S"
).timestamp()
)
unix_week = unix_day - (now_week * 86400) unix_week = unix_day - (now_week * 86400)
unix_month = int(datetime.strptime(f"1.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp()) unix_month = int(
datetime.strptime(
f"1.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S"
).timestamp()
)
await Settingsx().update( await Settingsx().update(
misc_profit_day=unix_day, misc_profit_day=unix_day,
@@ -68,8 +81,6 @@ async def startup_notify(bot: Bot, arSession: ARS):
ded(f""" ded(f"""
<b> Бот был успешно запущен</b> <b> Бот был успешно запущен</b>
{get_text_desc()}
<code> Данное сообщение видят только администраторы бота.</code> <code> Данное сообщение видят только администраторы бота.</code>
"""), """),
) )
@@ -91,7 +102,9 @@ async def autobackup_admin(bot: Bot):
disable_notification=True, disable_notification=True,
) )
except Exception: except Exception:
bot_logger.warning("Не удалось отправить автобэкап админу %s", admin, exc_info=True) bot_logger.warning(
"Не удалось отправить автобэкап админу %s", admin, exc_info=True
)
# Проверка наличия обновлений бота # Проверка наличия обновлений бота
@@ -106,7 +119,7 @@ async def check_update(bot: Bot, arSession: ARS):
response_data = json.loads((await response.read()).decode()) response_data = json.loads((await response.read()).decode())
if float(response_data['version']) > float(BOT_VERSION): if float(response_data["version"]) > float(BOT_VERSION):
await send_admins( await send_admins(
bot, bot,
ded(f""" ded(f"""
@@ -132,7 +145,7 @@ async def check_mail(bot: Bot, arSession: ARS):
) )
response_data = json.loads((await response.read()).decode()) response_data = json.loads((await response.read()).decode())
if response_data['status']: if response_data["status"]:
await send_admins( await send_admins(
bot, bot,
ded(f""" ded(f"""
@@ -176,21 +189,23 @@ async def functions_mail_make(bot: Bot, message: Message, call: CallbackQuery):
users_receive += 1 users_receive += 1
except Exception: except Exception:
users_block += 1 users_block += 1
bot_logger.debug("Пользователь %s не получил рассылку", user.user_id, exc_info=True) bot_logger.debug(
"Пользователь %s не получил рассылку", user.user_id, exc_info=True
)
users_count += 1 users_count += 1
if users_count % 10 == 0: if users_count % 10 == 0:
await call.message.edit_text(f"<b>📢 Рассылка началась... ({users_count}/{len(get_users)})</b>") await call.message.edit_text(
f"<b>📢 Рассылка началась... ({users_count}/{len(get_users)})</b>"
)
await asyncio.sleep(0.07) await asyncio.sleep(0.07)
await call.message.edit_text( await call.message.edit_text(ded(f"""
ded(f"""
<b>📢 Рассылка была завершена за <code>{get_unix() - get_time}сек</code></b> <b>📢 Рассылка была завершена за <code>{get_unix() - get_time}сек</code></b>
👤 Всего пользователей: <code>{len(get_users)}</code> 👤 Всего пользователей: <code>{len(get_users)}</code>
Пользователей получило сообщение: <code>{users_receive}</code> Пользователей получило сообщение: <code>{users_receive}</code>
Пользователей не получило сообщение: <code>{users_block}</code> Пользователей не получило сообщение: <code>{users_block}</code>
""") """))
)
+110 -40
View File
@@ -21,7 +21,11 @@ from tgbot.database import (
ModelUser, ModelUser,
) )
from tgbot.keyboards.inline_admin import profile_edit_finl from tgbot.keyboards.inline_admin import profile_edit_finl
from tgbot.keyboards.inline_admin_products import position_edit_open_finl, category_edit_open_finl, item_delete_finl from tgbot.keyboards.inline_admin_products import (
position_edit_open_finl,
category_edit_open_finl,
item_delete_finl,
)
from tgbot.keyboards.inline_user import user_profile_finl from tgbot.keyboards.inline_user import user_profile_finl
from tgbot.keyboards.inline_user_products import products_open_finl from tgbot.keyboards.inline_user_products import products_open_finl
from tgbot.services.api_hosting_text import HostingAPI from tgbot.services.api_hosting_text import HostingAPI
@@ -68,7 +72,7 @@ async def position_open_user(bot: Bot, user_id: int, position_id: int, remover:
await bot.send_message( await bot.send_message(
chat_id=user_id, chat_id=user_id,
text=ded(f""" text=ded(f"""
<b>🎁 Покупка товара</b>{hide_link(get_position.position_photo)} <b>🎁 Покупка товара</b>
Название: <code>{get_position.position_name}</code> Название: <code>{get_position.position_name}</code>
Категория: <code>{get_category.category_name}</code> Категория: <code>{get_category.category_name}</code>
@@ -78,7 +82,6 @@ async def position_open_user(bot: Bot, user_id: int, position_id: int, remover:
"""), """),
link_preview_options=LinkPreviewOptions(show_above_text=True), link_preview_options=LinkPreviewOptions(show_above_text=True),
reply_markup=products_open_finl(position_id, get_position.category_id, remover), reply_markup=products_open_finl(position_id, get_position.category_id, remover),
) )
@@ -114,10 +117,8 @@ async def open_profile_admin(bot: Bot, user_id: int, get_user: ModelUser):
async def refill_open_admin(bot: Bot, user_id: int, get_refill: ModelRefill): async def refill_open_admin(bot: Bot, user_id: int, get_refill: ModelRefill):
get_user = await Userx().get_required(user_id=get_refill.user_id) get_user = await Userx().get_required(user_id=get_refill.user_id)
if get_refill.refill_method in ['Form', 'Nickname', 'Number', 'QIWI']: if get_refill.refill_method in ["Form", "Nickname", "Number", "QIWI"]:
pay_method = "QIWI 🥝" pay_method = "QIWI 🥝"
elif get_refill.refill_method == "Yoomoney":
pay_method = "ЮMoney 🔮"
elif get_refill.refill_method == "Cryptobot": elif get_refill.refill_method == "Cryptobot":
pay_method = "CryptoBot 🔷" pay_method = "CryptoBot 🔷"
else: else:
@@ -138,7 +139,9 @@ async def refill_open_admin(bot: Bot, user_id: int, get_refill: ModelRefill):
# Открытие покупки админом # Открытие покупки админом
async def purchase_open_admin(bot: Bot, arSession: ARS, user_id: int, get_purchase: ModelPurchases): async def purchase_open_admin(
bot: Bot, arSession: ARS, user_id: int, get_purchase: ModelPurchases
):
get_user = await Userx().get_required(user_id=get_purchase.user_id) get_user = await Userx().get_required(user_id=get_purchase.user_id)
link_items = await ( link_items = await (
@@ -172,8 +175,18 @@ async def purchase_open_admin(bot: Bot, arSession: ARS, user_id: int, get_purcha
# Открытие категории админом # Открытие категории админом
async def category_open_admin(bot: Bot, user_id: int, category_id: int, remover: int): async def category_open_admin(bot: Bot, user_id: int, category_id: int, remover: int):
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0 profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = (
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0 0,
0,
0,
0,
)
profit_count_all, profit_count_day, profit_count_week, profit_count_month = (
0,
0,
0,
0,
)
get_items = await Itemx().gets(category_id=category_id) get_items = await Itemx().gets(category_id=category_id)
get_category = await Categoryx().get_required(category_id=category_id) get_category = await Categoryx().get_required(category_id=category_id)
@@ -217,8 +230,18 @@ async def category_open_admin(bot: Bot, user_id: int, category_id: int, remover:
# Открытие позиции админом # Открытие позиции админом
async def position_open_admin(bot: Bot, position_id: int, user_id: int): async def position_open_admin(bot: Bot, position_id: int, user_id: int):
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0 profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = (
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0 0,
0,
0,
0,
)
profit_count_all, profit_count_day, profit_count_week, profit_count_month = (
0,
0,
0,
0,
)
get_items = await Itemx().gets(position_id=position_id) get_items = await Itemx().gets(position_id=position_id)
get_position = await Positionx().get_required(position_id=position_id) get_position = await Positionx().get_required(position_id=position_id)
@@ -227,12 +250,6 @@ async def position_open_admin(bot: Bot, position_id: int, user_id: int):
get_settings = await Settingsx().get() get_settings = await Settingsx().get()
get_purchases = await Purchasesx().gets(purchase_position_id=position_id) get_purchases = await Purchasesx().gets(purchase_position_id=position_id)
# Наличие фото
if get_position.position_photo != "None":
position_photo_text = "<code>Присутствует ✅</code>"
else:
position_photo_text = "<code>Отсутствует ❌</code>"
# Наличие описания # Наличие описания
if get_position.position_desc != "None": if get_position.position_desc != "None":
position_desc = f"{get_position.position_desc}" position_desc = f"{get_position.position_desc}"
@@ -257,14 +274,13 @@ async def position_open_admin(bot: Bot, position_id: int, user_id: int):
await bot.send_message( await bot.send_message(
chat_id=user_id, chat_id=user_id,
text=ded(f""" text=ded(f"""
<b>📁 Редактирование позиции</b>{hide_link(get_position.position_photo)} <b>📁 Редактирование позиции</b>
Категория: <code>{get_category.category_name}</code> Категория: <code>{get_category.category_name}</code>
Позиция: <code>{get_position.position_name}</code> Позиция: <code>{get_position.position_name}</code>
Стоимость: <code>{get_position.position_price}</code> Стоимость: <code>{get_position.position_price}</code>
Количество: <code>{len(get_items)}шт</code> Количество: <code>{len(get_items)}шт</code>
Дата создания: <code>{convert_date(get_category.category_unix)}</code> Дата создания: <code>{convert_date(get_category.category_unix)}</code>
Изображение: {position_photo_text}
Описание: {position_desc} Описание: {position_desc}
💸 Продаж за День: <code>{profit_count_day}шт</code> - <code>{round(profit_amount_day, 2)}</code> 💸 Продаж за День: <code>{profit_count_day}шт</code> - <code>{round(profit_amount_day, 2)}</code>
@@ -274,7 +290,6 @@ async def position_open_admin(bot: Bot, position_id: int, user_id: int):
"""), """),
link_preview_options=LinkPreviewOptions(show_above_text=True), link_preview_options=LinkPreviewOptions(show_above_text=True),
reply_markup=await position_edit_open_finl(bot, position_id, 0), reply_markup=await position_edit_open_finl(bot, position_id, 0),
) )
@@ -303,14 +318,41 @@ async def item_open_admin(bot: Bot, item_id: int, user_id: int):
################################################################################ ################################################################################
# Статистика бота # Статистика бота
async def get_statistics() -> str: async def get_statistics() -> str:
refill_amount_all, refill_amount_day, refill_amount_week, refill_amount_month = 0, 0, 0, 0 refill_amount_all, refill_amount_day, refill_amount_week, refill_amount_month = (
refill_count_all, refill_count_day, refill_count_week, refill_count_month = 0, 0, 0, 0 0,
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0 0,
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0 0,
users_all, users_day, users_week, users_month, users_money_have, users_money_give = 0, 0, 0, 0, 0, 0 0,
refill_cryptobot_count, refill_cryptobot_amount = 0, 0 )
refill_yoomoney_count, refill_yoomoney_amount = 0, 0 refill_count_all, refill_count_day, refill_count_week, refill_count_month = (
refill_stars_count, refill_stars_amount = 0, 0 0,
0,
0,
0,
)
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = (
0,
0,
0,
0,
)
profit_count_all, profit_count_day, profit_count_week, profit_count_month = (
0,
0,
0,
0,
)
(
users_all,
users_day,
users_week,
users_month,
users_money_have,
users_money_give,
) = (0, 0, 0, 0, 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()
@@ -340,12 +382,14 @@ async def get_statistics() -> str:
refill_amount_all += refill.refill_amount refill_amount_all += refill.refill_amount
refill_count_all += 1 refill_count_all += 1
if refill.refill_method == "Yoomoney": if refill.refill_method == "Cryptobot":
refill_yoomoney_count += 1
refill_yoomoney_amount += refill.refill_amount
elif 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
@@ -375,12 +419,28 @@ async def get_statistics() -> str:
# Даты обновления статистики # Даты обновления статистики
all_days = [ all_days = [
'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье', "Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
"Воскресенье",
] ]
all_months = [ all_months = [
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', "Январь",
'Октябрь', 'Ноябрь', 'Декабрь' "Февраль",
"Март",
"Апрель",
"Май",
"Июнь",
"Июль",
"Август",
"Сентябрь",
"Октябрь",
"Ноябрь",
"Декабрь",
] ]
now_day = datetime.now().day now_day = datetime.now().day
@@ -388,12 +448,22 @@ async def get_statistics() -> str:
now_month = datetime.now().month now_month = datetime.now().month
now_year = datetime.now().year now_year = datetime.now().year
unix_day = int(datetime.strptime(f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp()) unix_day = int(
datetime.strptime(
f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S"
).timestamp()
)
unix_week = unix_day - (now_week * 86400) unix_week = unix_day - (now_week * 86400)
week_day = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%d")) week_day = int(
week_month = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%m")) datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%d")
week_week = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).weekday()) )
week_month = int(
datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%m")
)
week_week = int(
datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).weekday()
)
return ded(f""" return ded(f"""
<b>📊 СТАТИСТИКА БОТА</b> <b>📊 СТАТИСТИКА БОТА</b>
@@ -419,8 +489,8 @@ 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>
ЮMoney: <code>{refill_yoomoney_count}шт</code> - <code>{round(refill_yoomoney_amount, 2)}</code>
Остальные Остальные
Средств выдано: <code>{round(users_money_give, 2)}</code> Средств выдано: <code>{round(users_money_give, 2)}</code>