Compare commits
23 Commits
main
...
30a98f30c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 30a98f30c4 | |||
| 79bb03e5af | |||
| 215366051c | |||
| 47f57ae2ba | |||
| f328fdc705 | |||
| a04734b8a7 | |||
| 5e5bab91f2 | |||
| 0d34ac8f1c | |||
| fb80c9fcd8 | |||
| 6f10f085ee | |||
| 55b31b28ca | |||
| 30686aeabd | |||
| 7190963724 | |||
| 3fef026b6e | |||
| 8181394b40 | |||
| f2f8356d99 | |||
| 6697917b95 | |||
| c40fa63142 | |||
| 573dae70fb | |||
| cb56f6757e | |||
| b7cd21bfc3 | |||
| 572dc8d1b4 | |||
| ec5ae395f3 |
@@ -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
@@ -1,10 +1,12 @@
|
||||
BOT_TOKEN=
|
||||
BOT_ADMIN_IDS=
|
||||
|
||||
BOT_DATABASE_EXPORT=True
|
||||
BOT_STATUS_NOTIFICATION=True
|
||||
|
||||
BOT_TIMEZONE=Europe/Moscow
|
||||
BOT_USER_CACHE_TTL=300
|
||||
BOT_THROTTLE_RATE=0.5
|
||||
|
||||
PATH_DATABASE=tgbot/data/database.db
|
||||
PATH_LOGS=tgbot/data/logs.log
|
||||
YOOMONEY_CLIENT_ID=
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN useradd --create-home --shell /usr/sbin/nologin bot
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY --chown=bot:bot . .
|
||||
RUN mkdir -p /app/tgbot/data && chown -R bot:bot /app
|
||||
|
||||
USER bot
|
||||
|
||||
CMD ["sh", "-c", "python migrate.py up && python main.py"]
|
||||
@@ -1,8 +1,17 @@
|
||||
# Telegram Autoshop Bot by Djimbo
|
||||
# Telegram Autoshop Bot
|
||||
|
||||
Telegram bot на `aiogram 3` для магазина с категориями, позициями, товарами, покупками, пополнениями и админкой.
|
||||
|
||||
## Быстрый запуск
|
||||
## Запуск через .bat (Windows)
|
||||
|
||||
Запусти `start.bat` двойным кликом. Скрипт автоматически:
|
||||
|
||||
1. Создаст виртуальное окружение `.venv`
|
||||
2. Установит зависимости из `requirements.txt`
|
||||
3. Создаст `.env` из примера (если ещё нет)
|
||||
4. Запустит бота
|
||||
|
||||
## Запуск через командную строку (вручную)
|
||||
|
||||
1. Установи Python `3.11+`.
|
||||
2. Поставь зависимости:
|
||||
@@ -22,40 +31,12 @@ cp .env.example .env
|
||||
```env
|
||||
BOT_TOKEN=
|
||||
BOT_ADMIN_IDS=
|
||||
YOOMONEY_CLIENT_ID=
|
||||
```
|
||||
|
||||
Конфигурация читается только из `.env` или переменных окружения.
|
||||
|
||||
5. Примени миграции:
|
||||
|
||||
```bash
|
||||
python3 migrate.py up
|
||||
```
|
||||
|
||||
6. Запусти бота:
|
||||
5. Запусти бота:
|
||||
|
||||
```bash
|
||||
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
@@ -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
@@ -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
|
||||
@@ -1,11 +0,0 @@
|
||||
services:
|
||||
bot:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./tgbot/data:/app/tgbot/data
|
||||
stop_grace_period: 30s
|
||||
@@ -9,7 +9,13 @@ from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.client.session.aiohttp import AiohttpSession
|
||||
from aiogram.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.repository import prepare_database
|
||||
from tgbot.middlewares import register_all_middlewares
|
||||
@@ -141,9 +147,7 @@ async def main():
|
||||
bot = Bot(
|
||||
token=BOT_TOKEN,
|
||||
session=session,
|
||||
default=DefaultBotProperties(
|
||||
parse_mode=ParseMode.HTML
|
||||
),
|
||||
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
||||
)
|
||||
|
||||
register_all_middlewares(dp)
|
||||
@@ -160,8 +164,10 @@ async def main():
|
||||
|
||||
bot_info = await bot.get_me()
|
||||
bot_logger.info("Бот запущен: @%s", bot_info.username)
|
||||
print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Бот запущен - @{bot_info.username} ~~~~~")
|
||||
print(colorama.Fore.LIGHTBLUE_EX + "~~~~~ TG developer - @djimbox ~~~~~")
|
||||
print(
|
||||
colorama.Fore.LIGHTYELLOW_EX
|
||||
+ f"~~~~~ Бот запущен - @{bot_info.username} ~~~~~"
|
||||
)
|
||||
print(colorama.Fore.RESET)
|
||||
|
||||
if len(get_admins()) == 0:
|
||||
|
||||
-188
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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")
|
||||
-32
@@ -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 ###
|
||||
-32
@@ -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 ###
|
||||
@@ -13,7 +13,6 @@ dependencies = [
|
||||
"aiofiles>=25.1,<26.0",
|
||||
"aiohttp>=3.13.5,<4.0",
|
||||
"aiosqlite>=0.20,<1.0",
|
||||
"alembic>=1.13,<2.0",
|
||||
"cachetools>=7.1,<8.0",
|
||||
"certifi>=2025.8,<2027.0",
|
||||
"colorama>=0.4,<1.0",
|
||||
@@ -28,4 +27,3 @@ dependencies = [
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["tgbot*"]
|
||||
exclude = ["migrations*"]
|
||||
|
||||
@@ -3,7 +3,6 @@ aiogram==3.28.2
|
||||
aiofiles==25.1.0
|
||||
aiohttp==3.13.5
|
||||
aiosqlite==0.22.1
|
||||
alembic==1.18.4
|
||||
cachetools==7.1.4
|
||||
certifi==2026.5.20
|
||||
colorama==0.4.6
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
powershell -ExecutionPolicy Bypass -File "%~dp0start.ps1"
|
||||
@@ -0,0 +1,52 @@
|
||||
$Host.UI.RawUI.WindowTitle = "AutoShop Bot"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $ScriptDir
|
||||
|
||||
# Проверка Python
|
||||
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "[ERROR] Python не найден. Установи Python 3.11+ с https://python.org" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Создание .venv если нет
|
||||
if (-not (Test-Path ".venv")) {
|
||||
Write-Host "[INFO] Создание виртуального окружения..."
|
||||
python -m venv .venv
|
||||
}
|
||||
|
||||
# Активация
|
||||
& ".venv\Scripts\Activate.ps1"
|
||||
|
||||
# Установка зависимостей
|
||||
Write-Host "[INFO] Установка зависимостей..."
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
cls
|
||||
|
||||
# Создание .env если нет
|
||||
if (-not (Test-Path ".env")) {
|
||||
Write-Host "[INFO] Создание .env из примера..."
|
||||
Copy-Item ".env.example" ".env"
|
||||
Write-Host "[!] Заполни BOT_TOKEN и BOT_ADMIN_IDS в файле .env" -ForegroundColor Yellow
|
||||
Write-Host "[!] Открываю .env для редактирования..." -ForegroundColor Yellow
|
||||
notepad .env
|
||||
Read-Host "Нажми Enter после сохранения .env"
|
||||
}
|
||||
|
||||
cls
|
||||
|
||||
# Проверка BOT_TOKEN
|
||||
$envContent = Get-Content ".env" -Raw
|
||||
if ($envContent -notmatch "(?m)^BOT_TOKEN=\S+") {
|
||||
Write-Host "[ERROR] BOT_TOKEN не заполнен в .env" -ForegroundColor Red
|
||||
Write-Host "[!] Открываю .env для редактирования..." -ForegroundColor Yellow
|
||||
notepad .env
|
||||
Read-Host "Нажми Enter после сохранения .env"
|
||||
}
|
||||
|
||||
cls
|
||||
|
||||
# Запуск бота
|
||||
Write-Host "[INFO] Запуск бота..." -ForegroundColor Green
|
||||
python main.py
|
||||
+35
-32
@@ -28,16 +28,29 @@ class Settings(BaseSettings):
|
||||
default=True,
|
||||
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")
|
||||
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")
|
||||
user_cache_ttl: int = Field(default=300, ge=0, validation_alias="BOT_USER_CACHE_TTL")
|
||||
throttle_rate: float = Field(default=0.5, ge=0, validation_alias="BOT_THROTTLE_RATE")
|
||||
yoomoney_client_id: str = Field(default="", validation_alias="YOOMONEY_CLIENT_ID")
|
||||
user_cache_ttl: int = Field(
|
||||
default=300, ge=0, validation_alias="BOT_USER_CACHE_TTL"
|
||||
)
|
||||
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
|
||||
def _strip_string(cls, value: object) -> str:
|
||||
return str(value or "").strip()
|
||||
@@ -52,11 +65,15 @@ class Settings(BaseSettings):
|
||||
if isinstance(value, int):
|
||||
values = [value]
|
||||
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)):
|
||||
values = list(value)
|
||||
else:
|
||||
raise ValueError("BOT_ADMIN_IDS должен быть числом или списком чисел через запятую")
|
||||
raise ValueError(
|
||||
"BOT_ADMIN_IDS должен быть числом или списком чисел через запятую"
|
||||
)
|
||||
|
||||
admin_ids = []
|
||||
|
||||
@@ -64,10 +81,14 @@ class Settings(BaseSettings):
|
||||
try:
|
||||
parsed_id = int(admin_id)
|
||||
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:
|
||||
raise ValueError("BOT_ADMIN_IDS должен содержать Телеграм ID больше нуля")
|
||||
raise ValueError(
|
||||
"BOT_ADMIN_IDS должен содержать Телеграм ID больше нуля"
|
||||
)
|
||||
|
||||
admin_ids.append(parsed_id)
|
||||
|
||||
@@ -80,7 +101,9 @@ class Settings(BaseSettings):
|
||||
try:
|
||||
timezone(value)
|
||||
except UnknownTimeZoneError as error:
|
||||
raise ValueError("BOT_TIMEZONE должен быть корректной временной зоной, например Europe/Moscow") from error
|
||||
raise ValueError(
|
||||
"BOT_TIMEZONE должен быть корректной временной зоной, например Europe/Moscow"
|
||||
) from error
|
||||
|
||||
return value
|
||||
|
||||
@@ -126,7 +149,6 @@ BOT_DATABASE_EXPORT = settings.database_export
|
||||
BOT_TIMEZONE = settings.timezone
|
||||
BOT_USER_CACHE_TTL = settings.user_cache_ttl
|
||||
BOT_THROTTLE_RATE = settings.throttle_rate
|
||||
YOOMONEY_CLIENT_ID = settings.yoomoney_client_id
|
||||
BOT_SCHEDULER = AsyncIOScheduler(timezone=BOT_TIMEZONE)
|
||||
BOT_VERSION = 4.2
|
||||
|
||||
@@ -146,23 +168,4 @@ def validate_bot_config() -> None:
|
||||
def get_text_desc() -> str:
|
||||
from tgbot.utils.const_functions import ded
|
||||
|
||||
return ded(f"""
|
||||
<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> бота, он ничем не сможет помочь. Бот является
|
||||
открытым и распространяется публично. Абсолютно любой желающий мог его запустить.
|
||||
""")
|
||||
return ded(f"""<b>♻️ Версия бота: <code>{BOT_VERSION}</code>""").strip()
|
||||
|
||||
@@ -6,7 +6,29 @@ from .db_purchases import PurchaseModel, Purchasesx
|
||||
from .db_refill import RefillModel, Refillx
|
||||
from .db_settings import SettingsModel, Settingsx
|
||||
from .db_users import UserModel, UsersRepository, Userx
|
||||
from .entities import Category, Item, Payments, Position, Purchase, Refill, Settings, User
|
||||
|
||||
from .db_referral import (
|
||||
ReferralModel,
|
||||
ReferralTransactionModel,
|
||||
ReferralWithdrawalModel,
|
||||
Referralx,
|
||||
ReferralTransactionx,
|
||||
ReferralWithdrawalx,
|
||||
)
|
||||
|
||||
from .entities import (
|
||||
Category,
|
||||
Item,
|
||||
Payments,
|
||||
Position,
|
||||
Purchase,
|
||||
Refill,
|
||||
Settings,
|
||||
User,
|
||||
Referral,
|
||||
ReferralTransaction,
|
||||
ReferralWithdrawal,
|
||||
)
|
||||
|
||||
ModelCategory = Category
|
||||
ModelItem = Item
|
||||
@@ -18,3 +40,7 @@ ModelSettings = Settings
|
||||
ModelUser = User
|
||||
ModelUsers = User
|
||||
SettingsRepository = Settingsx
|
||||
|
||||
ModelReferral = Referral
|
||||
ModelReferralTransaction = ReferralTransaction
|
||||
ModelReferralWithdrawal = ReferralWithdrawal
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Integer, String, Float
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
|
||||
from tgbot.database.core import Base
|
||||
from tgbot.database.entities import Payments
|
||||
@@ -14,17 +15,89 @@ class PaymentsModel(Base):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
|
||||
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)
|
||||
status_cryptobot: Mapped[str] = mapped_column(String(16), nullable=False, default="False")
|
||||
status_yoomoney: Mapped[str] = mapped_column(String(16), nullable=False, default="False")
|
||||
status_stars: Mapped[str] = mapped_column(String(16), nullable=False, default="False")
|
||||
status_cryptobot: 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
|
||||
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]):
|
||||
# Подключение модели платежных настроек
|
||||
def __init__(self):
|
||||
|
||||
@@ -13,14 +13,17 @@ from tgbot.utils.const_functions import get_unix
|
||||
class PositionModel(Base):
|
||||
__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)
|
||||
position_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||
position_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
position_price: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
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(Integer, nullable=False, default=get_unix)
|
||||
position_unix: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=get_unix
|
||||
)
|
||||
|
||||
|
||||
ModelBase = Position
|
||||
@@ -37,13 +40,12 @@ class Positionx(BaseRepository[PositionModel, Position]):
|
||||
|
||||
# Добавление позиции товара
|
||||
async def add(
|
||||
self,
|
||||
category_id: int,
|
||||
position_id: int,
|
||||
position_name: str,
|
||||
position_price: float,
|
||||
position_desc: str,
|
||||
position_photo: str,
|
||||
self,
|
||||
category_id: int,
|
||||
position_id: int,
|
||||
position_name: str,
|
||||
position_price: float,
|
||||
position_desc: str,
|
||||
) -> Position:
|
||||
return await self._insert(
|
||||
category_id=category_id,
|
||||
@@ -51,12 +53,13 @@ class Positionx(BaseRepository[PositionModel, Position]):
|
||||
position_name=position_name,
|
||||
position_price=position_price,
|
||||
position_desc=position_desc,
|
||||
position_photo=position_photo,
|
||||
position_unix=get_unix(),
|
||||
)
|
||||
|
||||
# Обновление позиции по ID или фильтру
|
||||
async def update(self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs) -> int:
|
||||
async def update(
|
||||
self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs
|
||||
) -> int:
|
||||
if isinstance(where, int):
|
||||
where = {"position_id": where}
|
||||
|
||||
@@ -68,12 +71,15 @@ class Positionx(BaseRepository[PositionModel, Position]):
|
||||
from tgbot.database.db_item import ItemModel
|
||||
|
||||
item_table = ItemModel.__table__
|
||||
statement = (
|
||||
select(item_table.c.position_id, func.count(item_table.c.increment).label("item_count"))
|
||||
.group_by(item_table.c.position_id)
|
||||
)
|
||||
statement = select(
|
||||
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:
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from math import isfinite
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import BigInteger, Float, Integer, String, UniqueConstraint, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
from tgbot.database.core import Base, session_factory
|
||||
from tgbot.database.entities import Referral, ReferralTransaction, ReferralWithdrawal
|
||||
from tgbot.database.repository import BaseRepository
|
||||
from tgbot.utils.const_functions import get_unix
|
||||
|
||||
|
||||
def is_valid_referral_withdrawal_recipient(
|
||||
withdrawal_method: str, recipient: str
|
||||
) -> bool:
|
||||
if withdrawal_method == "Cryptobot":
|
||||
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
|
||||
|
||||
if withdrawal_method == "Lolzteam":
|
||||
parsed_url = urlparse(recipient)
|
||||
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
|
||||
"zelenka.guru",
|
||||
"lolz.team",
|
||||
"lolz.live",
|
||||
"lolz.guru",
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def ensure_referrals_schema(conn: AsyncConnection) -> None:
|
||||
user_columns_result = await conn.exec_driver_sql("PRAGMA table_info(storage_users)")
|
||||
user_columns = {column[1] for column in user_columns_result.fetchall()}
|
||||
|
||||
# Обновление полей пользователя
|
||||
if user_columns:
|
||||
if "user_referrer_id" not in user_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_users ADD COLUMN user_referrer_id BIGINT"
|
||||
)
|
||||
if "user_referral_balance" not in user_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_users "
|
||||
"ADD COLUMN user_referral_balance FLOAT NOT NULL DEFAULT 0"
|
||||
)
|
||||
if "user_referral_hold" not in user_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_users "
|
||||
"ADD COLUMN user_referral_hold FLOAT NOT NULL DEFAULT 0"
|
||||
)
|
||||
|
||||
await conn.exec_driver_sql(
|
||||
"CREATE INDEX IF NOT EXISTS ix_storage_users_user_referrer_id "
|
||||
"ON storage_users (user_referrer_id)"
|
||||
)
|
||||
|
||||
settings_columns_result = await conn.exec_driver_sql(
|
||||
"PRAGMA table_info(storage_settings)"
|
||||
)
|
||||
settings_columns = {column[1] for column in settings_columns_result.fetchall()}
|
||||
|
||||
# Обновление полей настроек
|
||||
if settings_columns:
|
||||
if "status_referral" not in settings_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_settings "
|
||||
"ADD COLUMN status_referral VARCHAR(16) NOT NULL DEFAULT 'False'"
|
||||
)
|
||||
if "referral_bonus_rub" not in settings_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_settings "
|
||||
"ADD COLUMN referral_bonus_rub FLOAT NOT NULL DEFAULT 0"
|
||||
)
|
||||
if "referral_refill_percent" not in settings_columns:
|
||||
await conn.exec_driver_sql(
|
||||
"ALTER TABLE storage_settings "
|
||||
"ADD COLUMN referral_refill_percent FLOAT NOT NULL DEFAULT 0"
|
||||
)
|
||||
|
||||
|
||||
class ReferralModel(Base):
|
||||
__tablename__ = "storage_referrals"
|
||||
|
||||
increment: Mapped[int] = mapped_column(
|
||||
Integer, primary_key=True, autoincrement=True
|
||||
)
|
||||
referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||
referral_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=False, unique=True, index=True
|
||||
)
|
||||
referral_unix: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=get_unix
|
||||
)
|
||||
|
||||
|
||||
class ReferralTransactionModel(Base):
|
||||
__tablename__ = "storage_referral_transactions"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"user_id",
|
||||
"transaction_type",
|
||||
"source_id",
|
||||
name="uq_referral_transaction_source",
|
||||
),
|
||||
)
|
||||
|
||||
increment: Mapped[int] = mapped_column(
|
||||
Integer, primary_key=True, autoincrement=True
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||
related_user_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
||||
|
||||
transaction_type: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
source_id: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
transaction_unix: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=get_unix
|
||||
)
|
||||
|
||||
|
||||
class ReferralWithdrawalModel(Base):
|
||||
__tablename__ = "storage_referral_withdrawals"
|
||||
|
||||
increment: Mapped[int] = mapped_column(
|
||||
Integer, primary_key=True, autoincrement=True
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
|
||||
withdrawal_amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
withdrawal_method: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
withdrawal_recipient: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
withdrawal_status: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False, default="pending"
|
||||
)
|
||||
withdrawal_unix: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=get_unix
|
||||
)
|
||||
|
||||
processed_unix: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
processed_admin_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
|
||||
|
||||
|
||||
class Referralx(BaseRepository[ReferralModel, Referral]):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.table_model = ReferralModel
|
||||
self.entity_model = Referral
|
||||
self.storage_name = ReferralModel.__tablename__
|
||||
|
||||
async def add(self, referrer_id: int, referral_id: int) -> Referral:
|
||||
return await self._insert(
|
||||
referrer_id=referrer_id,
|
||||
referral_id=referral_id,
|
||||
referral_unix=get_unix(),
|
||||
)
|
||||
|
||||
async def register_referral(self, referral_id: int, referrer_id: int) -> str:
|
||||
if referral_id == referrer_id:
|
||||
return "SELF_REFERRAL"
|
||||
|
||||
async with session_factory() as session:
|
||||
try:
|
||||
await session.execute(text("BEGIN IMMEDIATE"))
|
||||
|
||||
settings_result = await session.execute(
|
||||
text(
|
||||
"SELECT status_referral, referral_bonus_rub "
|
||||
"FROM storage_settings WHERE id = 1"
|
||||
)
|
||||
)
|
||||
settings = settings_result.mappings().first()
|
||||
|
||||
if settings is None or settings["status_referral"] != "True":
|
||||
await session.rollback()
|
||||
return "DISABLED"
|
||||
|
||||
referrer_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_referral_balance FROM storage_users "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": referrer_id},
|
||||
)
|
||||
referrer = referrer_result.mappings().first()
|
||||
|
||||
if referrer is None:
|
||||
await session.rollback()
|
||||
return "REFERRER_NOT_FOUND"
|
||||
|
||||
referral_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_balance, user_referrer_id "
|
||||
"FROM storage_users WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": referral_id},
|
||||
)
|
||||
referral = referral_result.mappings().first()
|
||||
|
||||
if referral is None:
|
||||
await session.rollback()
|
||||
return "REFERRAL_NOT_FOUND"
|
||||
|
||||
existing_referral_result = await session.execute(
|
||||
text(
|
||||
"SELECT increment FROM storage_referrals "
|
||||
"WHERE referral_id = :user_id LIMIT 1"
|
||||
),
|
||||
{"user_id": referral_id},
|
||||
)
|
||||
|
||||
if (
|
||||
referral["user_referrer_id"] is not None
|
||||
or existing_referral_result.mappings().first() is not None
|
||||
):
|
||||
await session.rollback()
|
||||
return "ALREADY"
|
||||
|
||||
bonus = round(float(settings["referral_bonus_rub"]), 2)
|
||||
source_id = f"registration:{referral_id}"
|
||||
now_unix = get_unix()
|
||||
|
||||
# Приглашённому — бонус на основной баланс.
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referrals "
|
||||
"(referrer_id, referral_id, referral_unix) "
|
||||
"VALUES (:referrer_id, :referral_id, :referral_unix)"
|
||||
),
|
||||
{
|
||||
"referrer_id": referrer_id,
|
||||
"referral_id": referral_id,
|
||||
"referral_unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referrer_id = :referrer_id, "
|
||||
"user_balance = :balance "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"referrer_id": referrer_id,
|
||||
"balance": round(float(referral["user_balance"]) + bonus, 2),
|
||||
"user_id": referral_id,
|
||||
},
|
||||
)
|
||||
|
||||
# Рефереру — бонус на реферальный баланс.
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referral_balance = :balance "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"balance": round(
|
||||
float(referrer["user_referral_balance"]) + bonus, 2
|
||||
),
|
||||
"user_id": referrer_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||
),
|
||||
{
|
||||
"user_id": referral_id,
|
||||
"related_user_id": referrer_id,
|
||||
"transaction_type": "registration_bonus_main",
|
||||
"source_id": source_id,
|
||||
"amount": bonus,
|
||||
"transaction_unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||
),
|
||||
{
|
||||
"user_id": referrer_id,
|
||||
"related_user_id": referral_id,
|
||||
"transaction_type": "registration_bonus_referral",
|
||||
"source_id": source_id,
|
||||
"amount": bonus,
|
||||
"transaction_unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
return "ok"
|
||||
|
||||
async def transfer_to_main_balance(self, user_id: int, amount: float) -> str:
|
||||
try:
|
||||
transfer_amount = round(float(amount), 2)
|
||||
except (TypeError, ValueError):
|
||||
return "INVALID_AMOUNT"
|
||||
|
||||
if not isfinite(transfer_amount) or transfer_amount <= 0:
|
||||
return "INVALID_AMOUNT"
|
||||
|
||||
async with session_factory() as session:
|
||||
try:
|
||||
await session.execute(text("BEGIN IMMEDIATE"))
|
||||
|
||||
user_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_balance, user_referral_balance, user_referral_hold "
|
||||
"FROM storage_users WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": user_id},
|
||||
)
|
||||
get_user = user_result.mappings().first()
|
||||
|
||||
if get_user is None:
|
||||
await session.rollback()
|
||||
return "USER_NOT_FOUND"
|
||||
|
||||
available_balance = round(
|
||||
float(get_user["user_referral_balance"])
|
||||
- float(get_user["user_referral_hold"]),
|
||||
2,
|
||||
)
|
||||
|
||||
if transfer_amount > available_balance:
|
||||
await session.rollback()
|
||||
return "INSUFFICIENT_FUNDS"
|
||||
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_balance = :user_balance, "
|
||||
"user_referral_balance = :referral_balance "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"user_balance": round(
|
||||
float(get_user["user_balance"]) + transfer_amount,
|
||||
2,
|
||||
),
|
||||
"referral_balance": round(
|
||||
float(get_user["user_referral_balance"]) - transfer_amount,
|
||||
2,
|
||||
),
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||
),
|
||||
{
|
||||
"user_id": user_id,
|
||||
"transaction_type": "transfer_to_main",
|
||||
"source_id": f"transfer:{user_id}:{uuid4().hex}",
|
||||
"amount": transfer_amount,
|
||||
"transaction_unix": get_unix(),
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
return "ok"
|
||||
|
||||
|
||||
class ReferralTransactionx(
|
||||
BaseRepository[ReferralTransactionModel, ReferralTransaction]
|
||||
):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.table_model = ReferralTransactionModel
|
||||
self.entity_model = ReferralTransaction
|
||||
self.storage_name = ReferralTransactionModel.__tablename__
|
||||
|
||||
async def get_refill_reward(self, source_id: str) -> Optional[ReferralTransaction]:
|
||||
return await self.get(
|
||||
transaction_type="refill_percent",
|
||||
source_id=source_id,
|
||||
)
|
||||
|
||||
async def add(
|
||||
self,
|
||||
user_id: int,
|
||||
transaction_type: str,
|
||||
source_id: str,
|
||||
amount: float,
|
||||
related_user_id: Optional[int] = None,
|
||||
) -> ReferralTransaction:
|
||||
return await self._insert(
|
||||
user_id=user_id,
|
||||
related_user_id=related_user_id,
|
||||
transaction_type=transaction_type,
|
||||
source_id=source_id,
|
||||
amount=round(amount, 2),
|
||||
transaction_unix=get_unix(),
|
||||
)
|
||||
|
||||
|
||||
class ReferralWithdrawalx(BaseRepository[ReferralWithdrawalModel, ReferralWithdrawal]):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.table_model = ReferralWithdrawalModel
|
||||
self.entity_model = ReferralWithdrawal
|
||||
self.storage_name = ReferralWithdrawalModel.__tablename__
|
||||
|
||||
async def create_pending(
|
||||
self,
|
||||
user_id: int,
|
||||
withdrawal_amount: float,
|
||||
withdrawal_method: str,
|
||||
withdrawal_recipient: str,
|
||||
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||
try:
|
||||
amount = round(float(withdrawal_amount), 2)
|
||||
except (TypeError, ValueError):
|
||||
return "INVALID_AMOUNT", None
|
||||
|
||||
recipient = str(withdrawal_recipient).strip()
|
||||
|
||||
if not isfinite(amount) or amount <= 0:
|
||||
return "INVALID_AMOUNT", None
|
||||
|
||||
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||
return "INVALID_METHOD", None
|
||||
|
||||
if not is_valid_referral_withdrawal_recipient(withdrawal_method, recipient):
|
||||
return "INVALID_RECIPIENT", None
|
||||
|
||||
withdrawal_id: Optional[int] = None
|
||||
|
||||
async with session_factory() as session:
|
||||
try:
|
||||
await session.execute(text("BEGIN IMMEDIATE"))
|
||||
|
||||
user_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_referral_balance, user_referral_hold "
|
||||
"FROM storage_users WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": user_id},
|
||||
)
|
||||
get_user = user_result.mappings().first()
|
||||
|
||||
if get_user is None:
|
||||
await session.rollback()
|
||||
return "USER_NOT_FOUND", None
|
||||
|
||||
available_balance = round(
|
||||
float(get_user["user_referral_balance"])
|
||||
- float(get_user["user_referral_hold"]),
|
||||
2,
|
||||
)
|
||||
|
||||
if amount > available_balance:
|
||||
await session.rollback()
|
||||
return "INSUFFICIENT_FUNDS", None
|
||||
|
||||
now_unix = get_unix()
|
||||
withdrawal_result = await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_withdrawals "
|
||||
"(user_id, withdrawal_amount, withdrawal_method, withdrawal_recipient, withdrawal_status, withdrawal_unix) "
|
||||
"VALUES (:user_id, :amount, :method, :recipient, 'pending', :unix)"
|
||||
),
|
||||
{
|
||||
"user_id": user_id,
|
||||
"amount": amount,
|
||||
"method": withdrawal_method,
|
||||
"recipient": recipient,
|
||||
"unix": now_unix,
|
||||
},
|
||||
)
|
||||
withdrawal_id = int(withdrawal_result.lastrowid)
|
||||
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referral_hold = :hold "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"hold": round(
|
||||
float(get_user["user_referral_hold"]) + amount,
|
||||
2,
|
||||
),
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||
),
|
||||
{
|
||||
"user_id": user_id,
|
||||
"transaction_type": "withdrawal_hold",
|
||||
"source_id": f"withdrawal:{withdrawal_id}",
|
||||
"amount": amount,
|
||||
"unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
return "ok", await self.get(increment=withdrawal_id)
|
||||
|
||||
async def complete(
|
||||
self, withdrawal_id: int, admin_id: int
|
||||
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||
async with session_factory() as session:
|
||||
try:
|
||||
await session.execute(text("BEGIN IMMEDIATE"))
|
||||
|
||||
withdrawal_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_id, withdrawal_amount, withdrawal_status "
|
||||
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
|
||||
),
|
||||
{"withdrawal_id": withdrawal_id},
|
||||
)
|
||||
withdrawal = withdrawal_result.mappings().first()
|
||||
|
||||
if withdrawal is None:
|
||||
await session.rollback()
|
||||
return "NOT_FOUND", None
|
||||
if withdrawal["withdrawal_status"] != "pending":
|
||||
await session.rollback()
|
||||
return "ALREADY_PROCESSED", None
|
||||
|
||||
user_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_referral_balance, user_referral_hold "
|
||||
"FROM storage_users WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": withdrawal["user_id"]},
|
||||
)
|
||||
get_user = user_result.mappings().first()
|
||||
amount = float(withdrawal["withdrawal_amount"])
|
||||
|
||||
if (
|
||||
get_user is None
|
||||
or float(get_user["user_referral_balance"]) < amount
|
||||
or float(get_user["user_referral_hold"]) < amount
|
||||
):
|
||||
await session.rollback()
|
||||
return "BALANCE_ERROR", None
|
||||
|
||||
now_unix = get_unix()
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referral_balance = :balance, user_referral_hold = :hold "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"balance": round(
|
||||
float(get_user["user_referral_balance"]) - amount,
|
||||
2,
|
||||
),
|
||||
"hold": round(
|
||||
float(get_user["user_referral_hold"]) - amount,
|
||||
2,
|
||||
),
|
||||
"user_id": withdrawal["user_id"],
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_referral_withdrawals "
|
||||
"SET withdrawal_status = 'completed', processed_unix = :unix, processed_admin_id = :admin_id "
|
||||
"WHERE increment = :withdrawal_id"
|
||||
),
|
||||
{
|
||||
"unix": now_unix,
|
||||
"admin_id": admin_id,
|
||||
"withdrawal_id": withdrawal_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||
),
|
||||
{
|
||||
"user_id": withdrawal["user_id"],
|
||||
"transaction_type": "withdrawal_completed",
|
||||
"source_id": f"withdrawal:{withdrawal_id}",
|
||||
"amount": amount,
|
||||
"unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
return "ok", await self.get(increment=withdrawal_id)
|
||||
|
||||
async def reject(
|
||||
self, withdrawal_id: int, admin_id: int
|
||||
) -> Tuple[str, Optional[ReferralWithdrawal]]:
|
||||
async with session_factory() as session:
|
||||
try:
|
||||
await session.execute(text("BEGIN IMMEDIATE"))
|
||||
|
||||
withdrawal_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_id, withdrawal_amount, withdrawal_status "
|
||||
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
|
||||
),
|
||||
{"withdrawal_id": withdrawal_id},
|
||||
)
|
||||
withdrawal = withdrawal_result.mappings().first()
|
||||
|
||||
if withdrawal is None:
|
||||
await session.rollback()
|
||||
return "NOT_FOUND", None
|
||||
if withdrawal["withdrawal_status"] != "pending":
|
||||
await session.rollback()
|
||||
return "ALREADY_PROCESSED", None
|
||||
|
||||
user_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_referral_hold FROM storage_users "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": withdrawal["user_id"]},
|
||||
)
|
||||
get_user = user_result.mappings().first()
|
||||
amount = float(withdrawal["withdrawal_amount"])
|
||||
|
||||
if get_user is None or float(get_user["user_referral_hold"]) < amount:
|
||||
await session.rollback()
|
||||
return "BALANCE_ERROR", None
|
||||
|
||||
now_unix = get_unix()
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referral_hold = :hold WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"hold": round(
|
||||
float(get_user["user_referral_hold"]) - amount,
|
||||
2,
|
||||
),
|
||||
"user_id": withdrawal["user_id"],
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_referral_withdrawals "
|
||||
"SET withdrawal_status = 'rejected', processed_unix = :unix, processed_admin_id = :admin_id "
|
||||
"WHERE increment = :withdrawal_id"
|
||||
),
|
||||
{
|
||||
"unix": now_unix,
|
||||
"admin_id": admin_id,
|
||||
"withdrawal_id": withdrawal_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
|
||||
),
|
||||
{
|
||||
"user_id": withdrawal["user_id"],
|
||||
"transaction_type": "withdrawal_rejected",
|
||||
"source_id": f"withdrawal:{withdrawal_id}",
|
||||
"amount": amount,
|
||||
"unix": now_unix,
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
return "ok", await self.get(increment=withdrawal_id)
|
||||
|
||||
async def add(
|
||||
self,
|
||||
user_id: int,
|
||||
withdrawal_amount: float,
|
||||
withdrawal_method: str,
|
||||
withdrawal_recipient: str,
|
||||
) -> ReferralWithdrawal:
|
||||
return await self._insert(
|
||||
user_id=user_id,
|
||||
withdrawal_amount=round(withdrawal_amount, 2),
|
||||
withdrawal_method=withdrawal_method,
|
||||
withdrawal_recipient=withdrawal_recipient,
|
||||
withdrawal_status="pending",
|
||||
withdrawal_unix=get_unix(),
|
||||
)
|
||||
|
||||
async def update(
|
||||
self,
|
||||
where: Optional[Union[Dict[str, Any], int]] = None,
|
||||
**kwargs,
|
||||
) -> int:
|
||||
if isinstance(where, int):
|
||||
where = {"increment": where}
|
||||
|
||||
return await self._update(where=where, **kwargs)
|
||||
@@ -90,7 +90,7 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
||||
user_result = await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT user_balance, user_refill
|
||||
SELECT user_balance, user_refill, user_referrer_id
|
||||
FROM storage_users
|
||||
WHERE user_id = :user_id
|
||||
"""
|
||||
@@ -103,6 +103,14 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
||||
await session.rollback()
|
||||
return "USER_NOT_FOUND"
|
||||
|
||||
settings_result = await session.execute(
|
||||
text(
|
||||
"SELECT status_referral, referral_refill_percent "
|
||||
"FROM storage_settings WHERE id = 1"
|
||||
)
|
||||
)
|
||||
get_settings = settings_result.mappings().first()
|
||||
|
||||
new_balance = round(float(get_user["user_balance"]) + float(pay_amount), 2)
|
||||
new_refill = round(float(get_user["user_refill"]) + float(pay_amount), 2)
|
||||
|
||||
@@ -138,6 +146,66 @@ class Refillx(BaseRepository[RefillModel, Refill]):
|
||||
),
|
||||
{"balance": new_balance, "refill": new_refill, "user_id": user_id},
|
||||
)
|
||||
|
||||
referrer_id = get_user["user_referrer_id"]
|
||||
referral_reward = 0.0
|
||||
|
||||
if (
|
||||
get_settings is not None
|
||||
and get_settings["status_referral"] == "True"
|
||||
and referrer_id is not None
|
||||
and referrer_id != user_id
|
||||
):
|
||||
referral_reward = round(
|
||||
float(pay_amount)
|
||||
* float(get_settings["referral_refill_percent"])
|
||||
/ 100,
|
||||
2,
|
||||
)
|
||||
|
||||
if referral_reward > 0:
|
||||
referrer_result = await session.execute(
|
||||
text(
|
||||
"SELECT user_referral_balance FROM storage_users "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{"user_id": referrer_id},
|
||||
)
|
||||
get_referrer = referrer_result.mappings().first()
|
||||
|
||||
if get_referrer is not None:
|
||||
source_id = f"refill:{pay_comment or pay_receipt}"
|
||||
|
||||
await session.execute(
|
||||
text(
|
||||
"UPDATE storage_users "
|
||||
"SET user_referral_balance = :balance "
|
||||
"WHERE user_id = :user_id"
|
||||
),
|
||||
{
|
||||
"balance": round(
|
||||
float(get_referrer["user_referral_balance"])
|
||||
+ referral_reward,
|
||||
2,
|
||||
),
|
||||
"user_id": referrer_id,
|
||||
},
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"INSERT INTO storage_referral_transactions "
|
||||
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
|
||||
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
|
||||
),
|
||||
{
|
||||
"user_id": referrer_id,
|
||||
"related_user_id": user_id,
|
||||
"transaction_type": "refill_percent",
|
||||
"source_id": source_id,
|
||||
"amount": referral_reward,
|
||||
"transaction_unix": get_unix(),
|
||||
},
|
||||
)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy import Integer, String
|
||||
from sqlalchemy import Integer, String, Float
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from tgbot.database.core import Base
|
||||
@@ -14,24 +14,47 @@ class SettingsModel(Base):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
|
||||
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")
|
||||
notification_refill: Mapped[str] = mapped_column(String(16), nullable=False, default="True")
|
||||
notification_buy: Mapped[str] = mapped_column(String(16), nullable=False, default="False")
|
||||
notification_refill: Mapped[str] = mapped_column(
|
||||
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_support: Mapped[str] = mapped_column(String, 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_token_telegraph: Mapped[str] = mapped_column(String, nullable=False, default="None")
|
||||
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_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_hosting_text: Mapped[str] = mapped_column(
|
||||
String(64), nullable=False, default="telegraph"
|
||||
)
|
||||
misc_token_telegraph: Mapped[str] = mapped_column(
|
||||
String, nullable=False, default="None"
|
||||
)
|
||||
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_week: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
misc_profit_month: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
# Реферальная система
|
||||
referral_refill_percent: Mapped[float] = mapped_column(
|
||||
Float, nullable=False, default=0
|
||||
)
|
||||
status_referral: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, default="False"
|
||||
)
|
||||
referral_bonus_rub: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
|
||||
|
||||
ModelBase = Settings
|
||||
BaseModel = Settings
|
||||
|
||||
+33
-17
@@ -15,8 +15,12 @@ from tgbot.utils.const_functions import get_unix
|
||||
class UserModel(Base):
|
||||
__tablename__ = "storage_users"
|
||||
|
||||
increment: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, unique=True, index=True)
|
||||
increment: Mapped[int] = mapped_column(
|
||||
Integer, primary_key=True, autoincrement=True
|
||||
)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
BigInteger, nullable=False, unique=True, index=True
|
||||
)
|
||||
user_login: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||
user_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||
user_surname: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
||||
@@ -26,6 +30,13 @@ class UserModel(Base):
|
||||
user_give: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
user_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix)
|
||||
|
||||
# Поля для реферальной системы
|
||||
user_referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=True, index=True)
|
||||
user_referral_balance: Mapped[float] = mapped_column(
|
||||
Float, nullable=False, default=0
|
||||
)
|
||||
user_referral_hold: Mapped[float] = mapped_column(Float, nullable=False, default=0)
|
||||
|
||||
|
||||
ModelBase = User
|
||||
BaseModel = User
|
||||
@@ -41,31 +52,34 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
||||
|
||||
# Добавление или обновление пользователя
|
||||
async def add(
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
) -> User:
|
||||
return await self.upsert(
|
||||
user_id=user_id,
|
||||
user_login=user_login,
|
||||
user_name=user_name,
|
||||
user_surname=user_surname,
|
||||
user_fullname=user_fullname or " ".join(filter(None, [user_name, user_surname])),
|
||||
user_fullname=user_fullname
|
||||
or " ".join(filter(None, [user_name, user_surname])),
|
||||
)
|
||||
|
||||
# Выполнение upsert пользователя по телеграм ID
|
||||
async def upsert(
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
self,
|
||||
user_id: int,
|
||||
user_login: str,
|
||||
user_name: str,
|
||||
user_surname: str = "",
|
||||
user_fullname: str = "",
|
||||
) -> User:
|
||||
user_fullname = user_fullname or " ".join(filter(None, [user_name, user_surname]))
|
||||
user_fullname = user_fullname or " ".join(
|
||||
filter(None, [user_name, user_surname])
|
||||
)
|
||||
user_table = UserModel.__table__
|
||||
statement = insert(UserModel).values(
|
||||
user_id=user_id,
|
||||
@@ -105,7 +119,9 @@ class UsersRepository(BaseRepository[UserModel, User]):
|
||||
return user
|
||||
|
||||
# Обновление пользователя по ID или фильтру
|
||||
async def update(self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs) -> int:
|
||||
async def update(
|
||||
self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs
|
||||
) -> int:
|
||||
if isinstance(where, int):
|
||||
where = {"user_id": where}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -18,7 +19,6 @@ class Position:
|
||||
position_name: str
|
||||
position_price: float
|
||||
position_desc: str
|
||||
position_photo: str
|
||||
position_unix: int
|
||||
|
||||
|
||||
@@ -37,12 +37,16 @@ class Item:
|
||||
class Payments:
|
||||
id: int
|
||||
cryptobot_token: str
|
||||
yoomoney_token: str
|
||||
stars_course: float
|
||||
status_cryptobot: str
|
||||
status_yoomoney: str
|
||||
status_stars: str
|
||||
|
||||
# Оплата через Lolzteam
|
||||
lolzteam_token: str
|
||||
lolzteam_merchant_id: Optional[int]
|
||||
|
||||
status_lolzteam: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Purchase:
|
||||
@@ -86,8 +90,6 @@ class Settings:
|
||||
misc_bot: str
|
||||
misc_hosting_text: str
|
||||
misc_token_telegraph: str
|
||||
misc_discord_webhook_url: str
|
||||
misc_discord_webhook_name: str
|
||||
misc_hide_category: str
|
||||
misc_hide_position: str
|
||||
misc_method_prod: str
|
||||
@@ -95,6 +97,11 @@ class Settings:
|
||||
misc_profit_week: int
|
||||
misc_profit_month: int
|
||||
|
||||
# Реферальная система
|
||||
referral_refill_percent: float
|
||||
status_referral: str
|
||||
referral_bonus_rub: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
@@ -108,3 +115,40 @@ class User:
|
||||
user_refill: float
|
||||
user_give: float
|
||||
user_unix: int
|
||||
|
||||
# Поля для реферальной системы
|
||||
user_referrer_id: Optional[int]
|
||||
user_referral_balance: float
|
||||
user_referral_hold: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Referral:
|
||||
increment: int
|
||||
referrer_id: int
|
||||
referral_id: int
|
||||
referral_unix: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReferralTransaction:
|
||||
increment: int
|
||||
user_id: int
|
||||
related_user_id: Optional[int]
|
||||
transaction_type: str
|
||||
source_id: str
|
||||
amount: float
|
||||
transaction_unix: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReferralWithdrawal:
|
||||
increment: int
|
||||
user_id: int
|
||||
withdrawal_amount: float
|
||||
withdrawal_method: str
|
||||
withdrawal_recipient: str
|
||||
withdrawal_status: str
|
||||
withdrawal_unix: int
|
||||
processed_unix: Optional[int]
|
||||
processed_admin_id: Optional[int]
|
||||
|
||||
@@ -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")
|
||||
@@ -5,8 +5,7 @@ from typing import Any, Dict, Generic, List, Optional, Type, TypeVar
|
||||
from sqlalchemy import delete as sqlalchemy_delete
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import update as sqlalchemy_update
|
||||
from tgbot.database.core import Base, database_path, session_scope
|
||||
from tgbot.database.migration_runner import run_migrations
|
||||
from tgbot.database.core import Base, database_path, engine, session_scope
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
ModelTranslator = TypeVar("ModelTranslator", bound=Base)
|
||||
@@ -151,7 +150,26 @@ class BaseRepository(Generic[ModelTranslator, EntityTranslator]):
|
||||
# Применение миграций и создание дефолтных строк
|
||||
async def prepare_database() -> None:
|
||||
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_settings import Settingsx
|
||||
|
||||
@@ -20,6 +20,17 @@ def close_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
# Рассылка
|
||||
def referral_withdrawal_actions_finl(withdrawal_id: int) -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Выполнено", data=f"referral_withdrawal_complete:{withdrawal_id}"),
|
||||
ikb("❌ Отклонить", data=f"referral_withdrawal_reject:{withdrawal_id}"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
|
||||
|
||||
def mail_confirm_finl() -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
@@ -73,9 +84,15 @@ async def payment_cryptobot_finl() -> InlineKeyboardMarkup:
|
||||
assets_symbol = "➕"
|
||||
|
||||
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:
|
||||
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_cryptobot_status:True")
|
||||
status_kb = ikb(
|
||||
f"{assets_symbol} | Статус: Выключено ❌",
|
||||
data="payment_cryptobot_status:True",
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
ikb("Информация ♻️", data="payment_cryptobot_check"),
|
||||
@@ -92,28 +109,34 @@ async def payment_cryptobot_finl() -> InlineKeyboardMarkup:
|
||||
return keyboard.as_markup()
|
||||
|
||||
|
||||
# Управление - ЮMoney
|
||||
async def payment_yoomoney_finl() -> InlineKeyboardMarkup:
|
||||
# Управление - Lolzteam
|
||||
async def payment_lolzteam_finl() -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
get_payments = await Paymentsx().get()
|
||||
|
||||
if get_payments.yoomoney_token == "None":
|
||||
if get_payments.lolzteam_token == "None":
|
||||
assets_symbol = "➖"
|
||||
else:
|
||||
assets_symbol = "➕"
|
||||
|
||||
if get_payments.status_yoomoney == "True":
|
||||
status_kb = ikb(f"{assets_symbol} | Статус: Включено ✅", data="payment_yoomoney_status:False")
|
||||
if get_payments.status_lolzteam == "True":
|
||||
status_kb = ikb(
|
||||
f"{assets_symbol} | Статус: Включено ✅",
|
||||
data="payment_lolzteam_status:False",
|
||||
)
|
||||
else:
|
||||
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_yoomoney_status:True")
|
||||
status_kb = ikb(
|
||||
f"{assets_symbol} | Статус: Выключено ❌",
|
||||
data="payment_lolzteam_status:True",
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
ikb("Информация ♻️", data="payment_yoomoney_check"),
|
||||
ikb("Информация ♻️", data="payment_lolzteam_check"),
|
||||
).row(
|
||||
ikb("Баланс 💰", data="payment_yoomoney_balance"),
|
||||
ikb("Баланс 💰", data="payment_lolzteam_balance"),
|
||||
).row(
|
||||
ikb("Изменить 🖍", data="payment_yoomoney_edit"),
|
||||
ikb("Изменить 🖍", data="payment_lolzteam_edit"),
|
||||
).row(
|
||||
ikb("", data="..."),
|
||||
).row(
|
||||
@@ -132,9 +155,13 @@ async def payment_stars_finl() -> InlineKeyboardMarkup:
|
||||
assets_symbol = "➕"
|
||||
|
||||
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:
|
||||
status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_stars_status:True")
|
||||
status_kb = ikb(
|
||||
f"{assets_symbol} | Статус: Выключено ❌", data="payment_stars_status:True"
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
ikb("Информация ♻️", data="payment_stars_check"),
|
||||
@@ -169,16 +196,9 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
||||
if get_settings.misc_support == "None":
|
||||
support_kb = ikb("Не установлена ❌", data="settings_edit_support")
|
||||
else:
|
||||
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")
|
||||
support_kb = ikb(
|
||||
f"@{get_settings.misc_support} ✅", data="settings_edit_support"
|
||||
)
|
||||
|
||||
# Скрытие категорий без товаров
|
||||
if get_settings.misc_hide_category == "True":
|
||||
@@ -198,20 +218,38 @@ async def settings_finl() -> InlineKeyboardMarkup:
|
||||
else:
|
||||
method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip")
|
||||
|
||||
# Бонус за приглашение
|
||||
referral_bonus_kb = ikb(
|
||||
f"{get_settings.referral_bonus_rub}₽", data="settings_edit_referral_bonus"
|
||||
)
|
||||
|
||||
# Доход с пополнений
|
||||
deposit_percent_kb = ikb(
|
||||
f"{get_settings.referral_refill_percent}%",
|
||||
data="settings_edit_referral_percent",
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
ikb("❔ FAQ", data="..."), faq_kb,
|
||||
ikb("❔ FAQ", data="..."),
|
||||
faq_kb,
|
||||
).row(
|
||||
ikb("☎️ Поддержка", data="..."), support_kb,
|
||||
ikb("☎️ Поддержка", data="..."),
|
||||
support_kb,
|
||||
).row(
|
||||
ikb("🧿 Дискорд Webhook", url="https://teletype.in/@djimbox/djimboshop-discord"), discord_webhook_kb,
|
||||
ikb("🎁 Категории без товаров", data="..."),
|
||||
hide_category_kb,
|
||||
).row(
|
||||
ikb("🗯 Текстовый хостинг", data="..."), hosting_text_default_kb,
|
||||
ikb("🎁 Позиции без товаров", data="..."),
|
||||
hide_position_kb,
|
||||
).row(
|
||||
ikb("🎁 Категории без товаров", data="..."), hide_category_kb,
|
||||
ikb("🎁 Метод добавления", data="..."),
|
||||
method_prod_kb,
|
||||
).row(
|
||||
ikb("🎁 Позиции без товаров", data="..."), hide_position_kb,
|
||||
ikb("🤝 Бонус за приглашение", data="..."),
|
||||
referral_bonus_kb,
|
||||
).row(
|
||||
ikb("🎁 Метод добавления", data="..."), method_prod_kb,
|
||||
ikb("📈 Доход с пополнений", data="..."),
|
||||
deposit_percent_kb,
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -226,8 +264,11 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
|
||||
status_work_kb = ikb("Включены ✅", data="settings_status_work:False")
|
||||
status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False")
|
||||
status_refill_kb = ikb("Включены ✅", data="settings_status_refill:False")
|
||||
status_referral_kb = ikb("Включена ✅", data="settings_status_referral:False")
|
||||
notification_buy_kb = ikb("Включены 🔔", data="settings_notification_buy:False")
|
||||
notification_refill_kb = ikb("Включены 🔔", data="settings_notification_refill:False")
|
||||
notification_refill_kb = ikb(
|
||||
"Включены 🔔", data="settings_notification_refill:False"
|
||||
)
|
||||
|
||||
if get_settings.status_buy == "False":
|
||||
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")
|
||||
if get_settings.status_refill == "False":
|
||||
status_refill_kb = ikb("Выключены ❌", data="settings_status_refill:True")
|
||||
if get_settings.status_referral == "False":
|
||||
status_referral_kb = ikb("Выключена ❌", data="settings_status_referral:True")
|
||||
if get_settings.notification_buy == "False":
|
||||
notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True")
|
||||
if get_settings.notification_refill == "False":
|
||||
notification_refill_kb = ikb("Выключены 🔕", data="settings_notification_refill:True")
|
||||
notification_refill_kb = ikb(
|
||||
"Выключены 🔕", data="settings_notification_refill:True"
|
||||
)
|
||||
|
||||
keyboard.row(
|
||||
ikb("⛔ Тех. работы", data="..."), status_work_kb,
|
||||
ikb("⛔ Тех. работы", data="..."),
|
||||
status_work_kb,
|
||||
).row(
|
||||
ikb("💰 Пополнения", data="..."), status_refill_kb,
|
||||
ikb("💰 Пополнения", data="..."),
|
||||
status_refill_kb,
|
||||
).row(
|
||||
ikb("🎁 Покупки", data="..."), status_buy_kb,
|
||||
ikb("🎁 Покупки", data="..."),
|
||||
status_buy_kb,
|
||||
).row(
|
||||
ikb("📢 Увед. о покупках", data="..."), notification_buy_kb,
|
||||
ikb("🤝 Реферальная система", data="..."),
|
||||
status_referral_kb,
|
||||
).row(
|
||||
ikb("📢 Увед. о пополнениях", data="..."), notification_refill_kb,
|
||||
ikb("📢 Увед. о покупках", data="..."),
|
||||
notification_buy_kb,
|
||||
).row(
|
||||
ikb("📢 Увед. о пополнениях", data="..."),
|
||||
notification_refill_kb,
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
|
||||
@@ -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()
|
||||
|
||||
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"position_add_open:{category_id}"),
|
||||
).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}"),
|
||||
).row(
|
||||
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.row(
|
||||
ikb("✅ Да, удалить", data=f"category_edit_delete_confirm:{category_id}:{remover}"),
|
||||
ikb("❌ Нет, отменить", data=f"category_edit_open:{category_id}:{remover}")
|
||||
ikb(
|
||||
"✅ Да, удалить",
|
||||
data=f"category_edit_delete_confirm:{category_id}:{remover}",
|
||||
),
|
||||
ikb("❌ Нет, отменить", data=f"category_edit_open:{category_id}:{remover}"),
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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}"),
|
||||
).row(
|
||||
ikb("▪️ Изм. Описание", data=f"position_edit_desc:{position_id}:{remover}"),
|
||||
ikb("▪️ Изм. Фото", data=f"position_edit_photo:{position_id}:{remover}"),
|
||||
).row(
|
||||
ikb("▪️ Добавить Товары", data=f"item_add_position_open:{position_id}"),
|
||||
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"item_delete_swipe:{position_id}:0"),
|
||||
).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}"),
|
||||
).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}"),
|
||||
)
|
||||
|
||||
@@ -89,8 +104,11 @@ def position_edit_delete_finl(position_id: int, remover: int) -> InlineKeyboardM
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Да, удалить", data=f"position_edit_delete_confirm:{position_id}:{remover}"),
|
||||
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}")
|
||||
ikb(
|
||||
"✅ Да, удалить",
|
||||
data=f"position_edit_delete_confirm:{position_id}:{remover}",
|
||||
),
|
||||
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -101,8 +119,11 @@ def position_edit_clear_finl(position_id: int, remover: int) -> InlineKeyboardMa
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Да, очистить", data=f"position_edit_clear_confirm:{position_id}:{remover}"),
|
||||
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}")
|
||||
ikb(
|
||||
"✅ Да, очистить",
|
||||
data=f"position_edit_clear_confirm:{position_id}:{remover}",
|
||||
),
|
||||
ikb("❌ Нет, отменить", data=f"position_edit_open:{position_id}:{remover}"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -168,7 +189,7 @@ def products_removes_categories_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Да, удалить все", data="prod_removes_categories_confirm"),
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return")
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -180,7 +201,7 @@ def products_removes_positions_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Да, удалить все", data="prod_removes_positions_confirm"),
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return")
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
@@ -192,7 +213,7 @@ def products_removes_items_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
keyboard.row(
|
||||
ikb("✅ Да, удалить все", data="prod_removes_items_confirm"),
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return")
|
||||
ikb("❌ Нет, отменить", data="prod_removes_return"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup()
|
||||
|
||||
@@ -27,7 +27,42 @@ def user_support_finl(support_login: str) -> InlineKeyboardMarkup:
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
|
||||
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()
|
||||
@@ -43,8 +78,8 @@ async def refill_method_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
if get_payments.status_cryptobot == "True":
|
||||
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
||||
if get_payments.status_yoomoney == "True":
|
||||
keyboard.row(ikb("🔮 ЮMoney", data="user_refill_method:Yoomoney"))
|
||||
if get_payments.status_lolzteam == "True":
|
||||
keyboard.row(ikb("🟢 Lolzteam", data="user_refill_method:Lolzteam"))
|
||||
if get_payments.status_stars == "True":
|
||||
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.row(
|
||||
@@ -74,8 +111,8 @@ async def refill_method_buy_finl() -> InlineKeyboardMarkup:
|
||||
|
||||
if get_payments.status_cryptobot == "True":
|
||||
keyboard.row(ikb("🔷 CryptoBot", data="user_refill_method:Cryptobot"))
|
||||
if get_payments.status_yoomoney == "True":
|
||||
keyboard.row(ikb("🔮 ЮMoney", data="user_refill_method:Yoomoney"))
|
||||
if get_payments.status_lolzteam == "True":
|
||||
keyboard.row(ikb("🟢 Lolzteam", data="user_refill_method:Lolzteam"))
|
||||
if get_payments.status_stars == "True":
|
||||
keyboard.row(ikb("⭐️ Звёзды", data="user_refill_method:Stars"))
|
||||
|
||||
|
||||
@@ -11,16 +11,24 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
rkb("🎁 Купить"), rkb("👤 Профиль"), rkb("🧮 Наличие товаров"),
|
||||
rkb("🎁 Купить"),
|
||||
rkb("👤 Профиль"),
|
||||
rkb("🧮 Наличие товаров"),
|
||||
).row(
|
||||
rkb("☎️ Поддержка"), rkb("❔ FAQ"),
|
||||
rkb("🤝 Реферальная система"),
|
||||
).row(
|
||||
rkb("☎️ Поддержка"),
|
||||
rkb("❔ FAQ"),
|
||||
)
|
||||
|
||||
if user_id in get_admins():
|
||||
keyboard.row(
|
||||
rkb("🎁 Управление товарами"), rkb("📊 Статистика"),
|
||||
rkb("🎁 Управление товарами"),
|
||||
rkb("📊 Статистика"),
|
||||
).row(
|
||||
rkb("⚙️ Настройки"), rkb("🔆 Общие функции"), rkb("🔑 Платежные системы"),
|
||||
rkb("⚙️ Настройки"),
|
||||
rkb("🔆 Общие функции"),
|
||||
rkb("🔑 Платежные системы"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup(resize_keyboard=True)
|
||||
@@ -31,7 +39,9 @@ def payments_frep() -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
rkb("🔷 CryptoBot"), rkb("🔮 ЮMoney"), rkb("⭐️ Звёзды"),
|
||||
rkb("🔷 CryptoBot"),
|
||||
rkb("🟢 Lolzteam"),
|
||||
rkb("⭐️ Звёзды"),
|
||||
).row(
|
||||
rkb("🔙 Главное меню"),
|
||||
)
|
||||
@@ -44,7 +54,8 @@ def functions_frep() -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
rkb("🔍 Поиск"), rkb("📢 Рассылка"),
|
||||
rkb("🔍 Поиск"),
|
||||
rkb("📢 Рассылка"),
|
||||
).row(
|
||||
rkb("🔙 Главное меню"),
|
||||
)
|
||||
@@ -57,7 +68,8 @@ def settings_frep() -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
rkb("🖍 Изменить данные"), rkb("🕹 Выключатели"),
|
||||
rkb("🖍 Изменить данные"),
|
||||
rkb("🕹 Выключатели"),
|
||||
).row(
|
||||
rkb("🔙 Главное меню"),
|
||||
)
|
||||
@@ -70,11 +82,15 @@ def items_frep() -> ReplyKeyboardMarkup:
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
|
||||
keyboard.row(
|
||||
rkb("📁 Создать позицию ➕"), rkb("🗃 Создать категорию ➕"),
|
||||
rkb("📁 Создать позицию ➕"),
|
||||
rkb("🗃 Создать категорию ➕"),
|
||||
).row(
|
||||
rkb("📁 Изменить позицию 🖍"), rkb("🗃 Изменить категорию 🖍"),
|
||||
rkb("📁 Изменить позицию 🖍"),
|
||||
rkb("🗃 Изменить категорию 🖍"),
|
||||
).row(
|
||||
rkb("🔙 Главное меню"), rkb("🎁 Добавить товары ➕"), rkb("❌ Удаление"),
|
||||
rkb("🔙 Главное меню"),
|
||||
rkb("🎁 Добавить товары ➕"),
|
||||
rkb("❌ Удаление"),
|
||||
)
|
||||
|
||||
return keyboard.as_markup(resize_keyboard=True)
|
||||
|
||||
@@ -3,16 +3,37 @@ from aiogram import BaseMiddleware
|
||||
from cachetools import TTLCache
|
||||
|
||||
from tgbot.data.config import BOT_USER_CACHE_TTL
|
||||
from tgbot.database import Userx
|
||||
from tgbot.database import Referralx, Settingsx, Userx
|
||||
from tgbot.utils.const_functions import clear_html
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
|
||||
class ExistsUserMiddleware(BaseMiddleware):
|
||||
# Создание кеша пользователей
|
||||
def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None:
|
||||
self.users = Userx()
|
||||
self.referrals = Referralx()
|
||||
self.cache = TTLCache(maxsize=10_000, ttl=cache_ttl)
|
||||
|
||||
@staticmethod
|
||||
def _get_referrer_id(event) -> int | None:
|
||||
event_text = getattr(event, "text", None)
|
||||
|
||||
if not isinstance(event_text, str):
|
||||
return None
|
||||
|
||||
start_data = event_text.split(maxsplit=1)
|
||||
|
||||
if len(start_data) != 2 or start_data[0] != "/start":
|
||||
return None
|
||||
|
||||
referral_data = start_data[1]
|
||||
|
||||
if not referral_data.startswith("r_") or not referral_data[2:].isdigit():
|
||||
return None
|
||||
|
||||
return int(referral_data[2:])
|
||||
|
||||
# Добавление или обновление пользователя перед handler-ом
|
||||
async def __call__(self, handler, event, data):
|
||||
this_user = data.get("event_from_user")
|
||||
@@ -32,6 +53,12 @@ class ExistsUserMiddleware(BaseMiddleware):
|
||||
)
|
||||
|
||||
cached_user = self.cache.get(user_id)
|
||||
existing_user = None
|
||||
|
||||
if cached_user is None:
|
||||
existing_user = await self.users.get(user_id=user_id)
|
||||
|
||||
is_new_user = cached_user is None and existing_user is None
|
||||
|
||||
if cached_user is None or cached_user["data"] != user_data:
|
||||
user = await self.users.upsert(
|
||||
@@ -41,10 +68,48 @@ class ExistsUserMiddleware(BaseMiddleware):
|
||||
user_surname=user_data[2],
|
||||
user_fullname=user_data[3],
|
||||
)
|
||||
self.cache[user_id] = {"data": user_data, "user": user}
|
||||
else:
|
||||
user = cached_user["user"]
|
||||
|
||||
referrer_id = self._get_referrer_id(event)
|
||||
|
||||
if is_new_user and referrer_id is not None:
|
||||
registration_status = await self.referrals.register_referral(
|
||||
referral_id=user_id,
|
||||
referrer_id=referrer_id,
|
||||
)
|
||||
|
||||
if registration_status == "ok":
|
||||
user = await self.users.get_required(user_id=user_id)
|
||||
get_settings = await Settingsx().get()
|
||||
bot = data.get("bot") or getattr(event, "bot", None)
|
||||
|
||||
if bot is not None:
|
||||
notifications = (
|
||||
(
|
||||
referrer_id,
|
||||
"<b>🤝 Новый реферал</b>\n\n"
|
||||
f"▪️ Пользователь: <a href='tg://user?id={user_id}'>{user.user_name}</a>\n"
|
||||
f"▪️ Начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
|
||||
),
|
||||
(
|
||||
user_id,
|
||||
"<b>🤝 Реферальный бонус</b>\n\n"
|
||||
f"▪️ Вам начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
|
||||
),
|
||||
)
|
||||
|
||||
for recipient_id, notification_text in notifications:
|
||||
try:
|
||||
await bot.send_message(recipient_id, notification_text)
|
||||
except Exception:
|
||||
bot_logger.warning(
|
||||
"Не удалось отправить уведомление о реферальной регистрации",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
self.cache[user_id] = {"data": user_data, "user": user}
|
||||
|
||||
data["User"] = user
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
+46
-12
@@ -2,8 +2,19 @@
|
||||
from aiogram import Dispatcher
|
||||
|
||||
from tgbot.routers import main_errors, main_start, main_missed
|
||||
from tgbot.routers.admin import admin_menu, admin_functions, admin_payments, admin_products, admin_settings
|
||||
from tgbot.routers.user import user_menu, user_transactions, user_products
|
||||
from tgbot.routers.admin import (
|
||||
admin_menu,
|
||||
admin_functions,
|
||||
admin_payments,
|
||||
admin_products,
|
||||
admin_settings,
|
||||
)
|
||||
from tgbot.routers.user import (
|
||||
user_menu,
|
||||
user_referral,
|
||||
user_transactions,
|
||||
user_products,
|
||||
)
|
||||
from tgbot.utils.misc.bot_filters import IsAdmin, IsPrivate
|
||||
|
||||
|
||||
@@ -23,17 +34,39 @@ def register_all_routers(dp: Dispatcher):
|
||||
user_products.router.callback_query.filter(IsPrivate())
|
||||
user_transactions.router.message.filter(IsPrivate())
|
||||
user_transactions.router.callback_query.filter(IsPrivate())
|
||||
user_referral.router.message.filter(IsPrivate())
|
||||
user_referral.router.callback_query.filter(IsPrivate())
|
||||
|
||||
admin_menu.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
admin_menu.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_functions.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
admin_functions.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_payments.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
admin_payments.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_settings.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
admin_settings.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_products.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов
|
||||
admin_products.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов
|
||||
admin_menu.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_menu.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
admin_functions.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_functions.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
admin_payments.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_payments.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
admin_settings.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_settings.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
admin_products.router.message.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа message роутера только для админов
|
||||
admin_products.router.callback_query.filter(
|
||||
IsPrivate(), IsAdmin()
|
||||
) # Работа callback роутера только для админов
|
||||
|
||||
# Подключение обязательных роутеров
|
||||
dp.include_router(main_errors.router) # Роутер ошибки
|
||||
@@ -44,6 +77,7 @@ def register_all_routers(dp: Dispatcher):
|
||||
dp.include_router(admin_menu.router) # Админ роутер
|
||||
dp.include_router(user_products.router) # Юзер роутер
|
||||
dp.include_router(user_transactions.router) # Юзер роутер
|
||||
dp.include_router(user_referral.router) # Юзер роутер
|
||||
dp.include_router(admin_functions.router) # Админ роутер
|
||||
dp.include_router(admin_payments.router) # Админ роутер
|
||||
dp.include_router(admin_settings.router) # Админ роутер
|
||||
|
||||
@@ -5,18 +5,137 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from tgbot.database import Purchasesx, Refillx, Userx
|
||||
from tgbot.database import Purchasesx, Refillx, ReferralWithdrawalx, Userx
|
||||
from tgbot.keyboards.inline_admin import profile_edit_return_finl, mail_confirm_finl
|
||||
from tgbot.services.api_hosting_text import HostingAPI
|
||||
from tgbot.utils.const_functions import is_number, to_number, del_message, ded, clear_html, convert_date
|
||||
from tgbot.utils.const_functions import (
|
||||
is_number,
|
||||
to_number,
|
||||
del_message,
|
||||
ded,
|
||||
clear_html,
|
||||
convert_date,
|
||||
)
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
from tgbot.utils.misc_functions import functions_mail_make
|
||||
from tgbot.utils.text_functions import open_profile_admin, refill_open_admin, purchase_open_admin
|
||||
from tgbot.utils.text_functions import (
|
||||
open_profile_admin,
|
||||
refill_open_admin,
|
||||
purchase_open_admin,
|
||||
)
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
def withdrawal_method_title(withdrawal_method: str) -> str:
|
||||
if withdrawal_method == "Cryptobot":
|
||||
return "CryptoBot"
|
||||
|
||||
return withdrawal_method
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("referral_withdrawal_complete:"))
|
||||
async def referral_withdrawal_complete(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
withdrawal_id_raw = call.data.split(":", 1)[1]
|
||||
if not withdrawal_id_raw.isdigit():
|
||||
return await call.answer("❌ Заявка не найдена", True)
|
||||
|
||||
status, withdrawal = await ReferralWithdrawalx().complete(
|
||||
withdrawal_id=int(withdrawal_id_raw),
|
||||
admin_id=call.from_user.id,
|
||||
)
|
||||
if status == "ALREADY_PROCESSED":
|
||||
return await call.answer("❌ Заявка уже обработана", True)
|
||||
if status != "ok" or withdrawal is None:
|
||||
return await call.answer("❌ Не удалось обработать заявку", True)
|
||||
|
||||
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||
get_user = await Userx().get_required(user_id=withdrawal.user_id)
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
<b>✅ Заявка на вывод выполнена</b>
|
||||
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
▪️ Способ: <code>{method_title}</code>
|
||||
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||
"""),
|
||||
reply_markup=None,
|
||||
)
|
||||
try:
|
||||
await bot.send_message(
|
||||
withdrawal.user_id,
|
||||
ded(f"""
|
||||
<b>✅ Заявка на вывод выполнена</b>
|
||||
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
▪️ Способ: <code>{method_title}</code>
|
||||
"""),
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning(
|
||||
"Не удалось отправить пользователю уведомление о выполненном выводе",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
await call.answer("✅ Заявка выполнена", True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("referral_withdrawal_reject:"))
|
||||
async def referral_withdrawal_reject(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
withdrawal_id_raw = call.data.split(":", 1)[1]
|
||||
if not withdrawal_id_raw.isdigit():
|
||||
return await call.answer("❌ Заявка не найдена", True)
|
||||
|
||||
status, withdrawal = await ReferralWithdrawalx().reject(
|
||||
withdrawal_id=int(withdrawal_id_raw),
|
||||
admin_id=call.from_user.id,
|
||||
)
|
||||
if status == "ALREADY_PROCESSED":
|
||||
return await call.answer("❌ Заявка уже обработана", True)
|
||||
if status != "ok" or withdrawal is None:
|
||||
return await call.answer("❌ Не удалось обработать заявку", True)
|
||||
|
||||
get_user = await Userx().get_required(user_id=withdrawal.user_id)
|
||||
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
<b>❌ Заявка на вывод отклонена</b>
|
||||
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
▪️ Способ: <code>{method_title}</code>
|
||||
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||
"""),
|
||||
reply_markup=None,
|
||||
)
|
||||
try:
|
||||
await bot.send_message(
|
||||
withdrawal.user_id,
|
||||
ded(f"""
|
||||
<b>❌ Заявка на вывод отклонена</b>
|
||||
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
"""),
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning(
|
||||
"Не удалось отправить пользователю уведомление об отклонённом выводе",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
await call.answer("❌ Заявка отклонена", True)
|
||||
|
||||
|
||||
# Поиск чеков и профилей
|
||||
@router.message(F.text == "🔍 Поиск")
|
||||
async def functions_find(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
@@ -55,17 +174,23 @@ async def functions_mail_get(message: Message, bot: Bot, state: FSM, arSession:
|
||||
|
||||
|
||||
# Подтверждение отправки рассылки
|
||||
@router.callback_query(F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm"))
|
||||
async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.callback_query(
|
||||
F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm")
|
||||
)
|
||||
async def functions_mail_confirm(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_status = call.data.split(":")[1]
|
||||
|
||||
send_message = (await state.get_data())['here_mail_message']
|
||||
send_message = (await state.get_data())["here_mail_message"]
|
||||
await state.clear()
|
||||
|
||||
if get_status == "Yes":
|
||||
get_users = await Userx().get_all()
|
||||
|
||||
await call.message.edit_text(f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>")
|
||||
await call.message.edit_text(
|
||||
f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>"
|
||||
)
|
||||
|
||||
await asyncio.create_task(functions_mail_make(bot, send_message, call))
|
||||
else:
|
||||
@@ -76,7 +201,7 @@ async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSe
|
||||
##################################### ПОИСК ####################################
|
||||
# Принятие айди/логина пользователя или чека для поиска
|
||||
@router.message(F.text, StateFilter("here_find"))
|
||||
@router.message(F.text.lower().startswith(('.find', 'find')))
|
||||
@router.message(F.text.lower().startswith((".find", "find")))
|
||||
async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
find_data = message.text.lower()
|
||||
|
||||
@@ -116,14 +241,18 @@ async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession:
|
||||
return await refill_open_admin(bot, message.from_user.id, get_refill)
|
||||
|
||||
if get_purchase is not None:
|
||||
return await purchase_open_admin(bot, arSession, message.from_user.id, get_purchase)
|
||||
return await purchase_open_admin(
|
||||
bot, arSession, message.from_user.id, get_purchase
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
############################## УПРАВЛЕНИЕ ПРОФИЛЕМ #############################
|
||||
# Обновление профиля пользователя
|
||||
@router.callback_query(F.data.startswith("admin_user_refresh:"))
|
||||
async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
async def functions_user_refresh(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = int(call.data.split(":")[1])
|
||||
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
@@ -136,7 +265,9 @@ async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSe
|
||||
|
||||
# Покупки пользователя
|
||||
@router.callback_query(F.data.startswith("admin_user_purchases:"))
|
||||
async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
async def functions_user_purchases(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = int(call.data.split(":")[1])
|
||||
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
@@ -174,7 +305,9 @@ async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, ar
|
||||
|
||||
# Выдача баланса пользователю
|
||||
@router.callback_query(F.data.startswith("admin_user_balance_add:"))
|
||||
async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
async def functions_user_balance_add(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = int(call.data.split(":")[1])
|
||||
|
||||
await state.update_data(here_user=user_id)
|
||||
@@ -188,8 +321,10 @@ async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM,
|
||||
|
||||
# Принятие суммы для выдачи баланса пользователю
|
||||
@router.message(F.text, StateFilter("here_user_add"))
|
||||
async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user']
|
||||
async def functions_user_balance_add_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user"]
|
||||
|
||||
if not is_number(message.text):
|
||||
return await message.answer(
|
||||
@@ -222,7 +357,11 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
|
||||
f"<b>💰 Вам было выдано <code>{message.text}₽</code></b>",
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.debug("Не удалось уведомить пользователя %s о выдаче баланса", user_id, exc_info=True)
|
||||
bot_logger.debug(
|
||||
"Не удалось уведомить пользователя %s о выдаче баланса",
|
||||
user_id,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
f"👤 Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
|
||||
@@ -235,7 +374,9 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
|
||||
|
||||
# Изменение баланса пользователю
|
||||
@router.callback_query(F.data.startswith("admin_user_balance_set:"))
|
||||
async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
async def functions_user_balance_set(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = int(call.data.split(":")[1])
|
||||
|
||||
await state.update_data(here_user=user_id)
|
||||
@@ -249,8 +390,10 @@ async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM,
|
||||
|
||||
# Принятие суммы для изменения баланса пользователя
|
||||
@router.message(F.text, StateFilter("here_user_set"))
|
||||
async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user']
|
||||
async def functions_user_balance_set_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user"]
|
||||
|
||||
if not is_number(message.text):
|
||||
return await message.answer(
|
||||
@@ -294,7 +437,9 @@ async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM,
|
||||
|
||||
# Отправка сообщения пользователю
|
||||
@router.callback_query(F.data.startswith("admin_user_message:"))
|
||||
async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
async def functions_user_user_message(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = int(call.data.split(":")[1])
|
||||
|
||||
await state.update_data(here_user_id=user_id)
|
||||
@@ -309,17 +454,24 @@ async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM,
|
||||
|
||||
# Принятие сообщения для отправки пользователю
|
||||
@router.message(F.text, StateFilter("here_user_message"))
|
||||
async def functions_user_user_message_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
user_id = (await state.get_data())['here_user_id']
|
||||
async def functions_user_user_message_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
user_id = (await state.get_data())["here_user_id"]
|
||||
await state.clear()
|
||||
|
||||
get_message = "<b>💌 Сообщение от администратора:</b>\n" + f"<code>{clear_html(message.text)}</code>"
|
||||
get_message = (
|
||||
"<b>💌 Сообщение от администратора:</b>\n"
|
||||
+ f"<code>{clear_html(message.text)}</code>"
|
||||
)
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
|
||||
try:
|
||||
await bot.send_message(user_id, get_message)
|
||||
except Exception:
|
||||
bot_logger.debug("Не удалось отправить сообщение пользователю %s", user_id, exc_info=True)
|
||||
bot_logger.debug(
|
||||
"Не удалось отправить сообщение пользователю %s", user_id, exc_info=True
|
||||
)
|
||||
await message.reply("<b>❌ Не удалось отправить сообщение</b>")
|
||||
else:
|
||||
await message.reply("<b>✅ Сообщение было успешно доставлено</b>")
|
||||
|
||||
@@ -4,12 +4,16 @@ from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
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_lolzteam import LolzteamAPI
|
||||
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.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
|
||||
router = Router(name=__name__)
|
||||
@@ -17,7 +21,9 @@ router = Router(name=__name__)
|
||||
|
||||
# Управление - 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 message.answer(
|
||||
@@ -26,14 +32,16 @@ async def payments_cryptobot_open(message: Message, bot: Bot, state: FSM, arSess
|
||||
)
|
||||
|
||||
|
||||
# Управление - ЮMoney
|
||||
@router.message(F.text == "🔮 ЮMoney")
|
||||
async def payments_yoomoney_open(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
# Управление - Lolzteam
|
||||
@router.message(F.text == "🟢 Lolzteam")
|
||||
async def payments_cryptobot_open(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
await state.clear()
|
||||
|
||||
await message.answer(
|
||||
"<b>🔮 Управление - ЮMoney</b>",
|
||||
reply_markup=await payment_yoomoney_finl(),
|
||||
"<b>🟢 Управление - Lolzteam</b>",
|
||||
reply_markup=await payment_lolzteam_finl(),
|
||||
)
|
||||
|
||||
|
||||
@@ -52,7 +60,9 @@ async def payments_stars_open(message: Message, bot: Bot, state: FSM, arSession:
|
||||
################################### CRYPTOBOT ##################################
|
||||
# Баланс - CryptoBot
|
||||
@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 (
|
||||
await CryptobotAPI.connect(
|
||||
bot=bot,
|
||||
@@ -70,7 +80,9 @@ async def payments_cryptobot_balance(call: CallbackQuery, bot: Bot, state: FSM,
|
||||
|
||||
# Информация - CryptoBot
|
||||
@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 (
|
||||
await CryptobotAPI.connect(
|
||||
bot=bot,
|
||||
@@ -88,7 +100,9 @@ async def payments_cryptobot_check(call: CallbackQuery, bot: Bot, state: FSM, ar
|
||||
|
||||
# Изменение - CryptoBot
|
||||
@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 call.message.edit_text(
|
||||
ded(f"""
|
||||
@@ -102,13 +116,17 @@ async def payments_cryptobot_edit(call: CallbackQuery, bot: Bot, state: FSM, arS
|
||||
|
||||
# Выключатель - CryptoBot
|
||||
@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_payments = await Paymentsx().get()
|
||||
|
||||
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)
|
||||
|
||||
@@ -121,12 +139,16 @@ async def payments_cryptobot_status(call: CallbackQuery, bot: Bot, state: FSM, a
|
||||
############################## ПРИНЯТИЕ CRYPTOBOT ##############################
|
||||
# Принятие токена Cryptobot
|
||||
@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
|
||||
|
||||
await state.clear()
|
||||
|
||||
cache_message = await message.answer("<b>🔷 Проверка введённых CryptoBot данных... 🔄</b>")
|
||||
cache_message = await message.answer(
|
||||
"<b>🔷 Проверка введённых CryptoBot данных... 🔄</b>"
|
||||
)
|
||||
|
||||
status, response = await (
|
||||
await CryptobotAPI.connect(
|
||||
@@ -140,9 +162,13 @@ async def payments_cryptobot_get(message: Message, bot: Bot, state: FSM, arSessi
|
||||
|
||||
if status:
|
||||
await Paymentsx().update(cryptobot_token=get_token)
|
||||
await cache_message.edit_text("<b>🔷 CryptoBot кошелёк был успешно изменён ✅</b>")
|
||||
await cache_message.edit_text(
|
||||
"<b>🔷 CryptoBot кошелёк был успешно изменён ✅</b>"
|
||||
)
|
||||
else:
|
||||
await cache_message.edit_text("<b>🔷 Не удалось изменить CryptoBot кошелёк ❌</b>")
|
||||
await cache_message.edit_text(
|
||||
"<b>🔷 Не удалось изменить CryptoBot кошелёк ❌</b>"
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
"<b>🔷 Управление - CryptoBot</b>",
|
||||
@@ -151,12 +177,14 @@ async def payments_cryptobot_get(message: Message, bot: Bot, state: FSM, arSessi
|
||||
|
||||
|
||||
################################################################################
|
||||
#################################### ЮMoney ####################################
|
||||
# Баланс - ЮMoney
|
||||
@router.callback_query(F.data == "payment_yoomoney_balance")
|
||||
async def payments_yoomoney_balance(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
################################### LOLZTEAM ##################################
|
||||
# Баланс - Lolzteam
|
||||
@router.callback_query(F.data == "payment_lolzteam_balance")
|
||||
async def payments_lolzteam_balance(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
response = await (
|
||||
await YoomoneyAPI.connect(
|
||||
await LolzteamAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=call,
|
||||
@@ -170,11 +198,13 @@ async def payments_yoomoney_balance(call: CallbackQuery, bot: Bot, state: FSM, a
|
||||
)
|
||||
|
||||
|
||||
# Информация - ЮMoney
|
||||
@router.callback_query(F.data == "payment_yoomoney_check")
|
||||
async def payments_yoomoney_check(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
response = await (
|
||||
await YoomoneyAPI.connect(
|
||||
# Информация - Lolzteam
|
||||
@router.callback_query(F.data == "payment_lolzteam_check")
|
||||
async def payment_lolzteam_check(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
status, response = await (
|
||||
await LolzteamAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=call,
|
||||
@@ -188,75 +218,117 @@ async def payments_yoomoney_check(call: CallbackQuery, bot: Bot, state: FSM, arS
|
||||
)
|
||||
|
||||
|
||||
# Изменение - ЮMoney
|
||||
@router.callback_query(F.data == "payment_yoomoney_edit")
|
||||
async def payments_yoomoney_edit(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
response = await (
|
||||
await YoomoneyAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
)
|
||||
).authorization_get()
|
||||
|
||||
await state.set_state("here_yoomoney_token")
|
||||
# Изменение - Lolzteam
|
||||
@router.callback_query(F.data == "payment_lolzteam_edit")
|
||||
async def payment_lolzteam_edit(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
await state.set_state("here_lolzteam_token")
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
<b>🔮 Изменение ЮMoney кошелька - <a href='https://teletype.in/@djimbox/djimboshop-yoomoney'>Инструкция</a></b>
|
||||
<b>🟢 Изменение Lolzteam - <a href='https://lolz.live/account/api'>API</a></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Отправьте ссылку/код из адресной строки
|
||||
▪️ {response}
|
||||
▪️ Создайте Приложение в "Lolzteam", сгенерируйте токен (basic, invoice, market) и отправьте
|
||||
"""),
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
|
||||
# Выключатель - ЮMoney
|
||||
@router.callback_query(F.data.startswith("payment_yoomoney_status:"))
|
||||
async def payments_yoomoney_status(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
# Выключатель - Lolzteam
|
||||
@router.callback_query(F.data.startswith("payment_lolzteam_status:"))
|
||||
async def payment_lolzteam_status(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_status = call.data.split(":")[1]
|
||||
|
||||
get_payments = await Paymentsx().get()
|
||||
|
||||
if get_status == "True" and get_payments.yoomoney_token == "None":
|
||||
return await call.answer("❌ Токен данной платежной системы не был добавлен", True)
|
||||
if get_status == "True" and get_payments.lolzteam_token == "None":
|
||||
return await call.answer(
|
||||
"❌ Токен данной платежной системы не был добавлен", True
|
||||
)
|
||||
|
||||
await Paymentsx().update(status_yoomoney=get_status)
|
||||
await Paymentsx().update(status_lolzteam=get_status)
|
||||
|
||||
await call.message.edit_text(
|
||||
"<b>🔮 Управление - ЮMoney</b>",
|
||||
reply_markup=await payment_yoomoney_finl(),
|
||||
"<b>🟢 Управление - Lolzteam</b>",
|
||||
reply_markup=await payment_lolzteam_finl(),
|
||||
)
|
||||
|
||||
|
||||
################################ ПРИНЯТИЕ ЮMONEY ###############################
|
||||
# Принятие токена ЮMoney
|
||||
@router.message(StateFilter("here_yoomoney_token"))
|
||||
async def payments_yoomoney_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
get_code = message.text
|
||||
############################## ПРИНЯТИЕ LOLZTEAM ##############################
|
||||
# Принятие токена Lolzteam
|
||||
@router.message(StateFilter("here_lolzteam_token"))
|
||||
async def payments_lolzteam_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
get_token = message.text
|
||||
|
||||
try:
|
||||
get_code = get_code[get_code.index("code=") + 5:].replace(" ", "")
|
||||
except Exception:
|
||||
bot_logger.debug("ЮMoney code= не найден в сообщении, пробую использовать текст как код", exc_info=True)
|
||||
cache_message = await message.answer(
|
||||
"<b>🟢 Проверка введённых Lolzteam данных... 🔄</b>"
|
||||
)
|
||||
|
||||
cache_message = await message.answer("<b>🔮 Проверка введённых ЮMoney данных... 🔄</b>")
|
||||
|
||||
status, token, response = await (
|
||||
await YoomoneyAPI.connect(
|
||||
status, response = await (
|
||||
await LolzteamAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=message,
|
||||
skipping_error=True,
|
||||
token=get_token,
|
||||
merchant_id="None",
|
||||
)
|
||||
).authorization_enter(str(get_code))
|
||||
).check()
|
||||
|
||||
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()
|
||||
|
||||
cache_message = await message.answer(
|
||||
"<b>🟢 Проверка введённых Lolzteam данных... 🔄</b>"
|
||||
)
|
||||
|
||||
status, response = await (
|
||||
await LolzteamAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=message,
|
||||
skipping_error=True,
|
||||
token=get_token,
|
||||
merchant_id=get_merchant_id,
|
||||
)
|
||||
).check()
|
||||
|
||||
if status:
|
||||
await Paymentsx().update(lolzteam_merchant_id=int(get_merchant_id))
|
||||
await cache_message.edit_text("<b>🟢 Lolzteam был успешно изменён ✅</b>")
|
||||
else:
|
||||
await cache_message.edit_text("<b>🟢 Не удалось изменить Lolzteam ❌</b>")
|
||||
|
||||
await message.answer(
|
||||
"<b>🔮 Управление - ЮMoney</b>",
|
||||
reply_markup=await payment_yoomoney_finl(),
|
||||
"<b>🟢 Управление - Lolzteam</b>",
|
||||
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")
|
||||
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 (
|
||||
await StarsAPI.connect(
|
||||
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")
|
||||
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 (
|
||||
await StarsAPI.connect(
|
||||
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")
|
||||
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()
|
||||
|
||||
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:"))
|
||||
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]
|
||||
|
||||
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
|
||||
@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):
|
||||
return await message.answer(
|
||||
"<b>❌ Введите число. Например: <code>1.5</code></b>"
|
||||
|
||||
@@ -26,12 +26,23 @@ from tgbot.keyboards.inline_admin_products import (
|
||||
products_removes_items_finl,
|
||||
item_add_finish_finl,
|
||||
)
|
||||
from tgbot.services.api_discord import DiscordAPI
|
||||
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_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__)
|
||||
|
||||
@@ -123,8 +134,10 @@ async def prod_removes(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
################################################################################
|
||||
############################### СОЗДАНИЕ КАТЕГОРИИ #############################
|
||||
# Принятие названия категории для её создания
|
||||
@router.message(F.text, StateFilter('here_category_name'))
|
||||
async def prod_category_add_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.message(F.text, StateFilter("here_category_name"))
|
||||
async def prod_category_add_name_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
if len(message.text) > 50:
|
||||
return await message.answer(
|
||||
"<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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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'))
|
||||
async def prod_category_edit_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
category_id = (await state.get_data())['here_category_id']
|
||||
remover = (await state.get_data())['here_remover']
|
||||
@router.message(F.text, StateFilter("here_category_edit_name"))
|
||||
async def prod_category_edit_name_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
category_id = (await state.get_data())["here_category_id"]
|
||||
remover = (await state.get_data())["here_remover"]
|
||||
|
||||
if len(message.text) > 50:
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
remover = int(call.data.split(":")[2])
|
||||
|
||||
@@ -241,7 +266,9 @@ async def prod_category_edit_delete_confirm(call: CallbackQuery, bot: Bot, state
|
||||
############################### ДОБАВЛЕНИЕ ПОЗИЦИИ #############################
|
||||
# Cтраницы выбора категорий для расположения позиции
|
||||
@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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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'))
|
||||
async def prod_position_add_name_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.message(F.text, StateFilter("here_position_name"))
|
||||
async def prod_position_add_name_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
if len(message.text) > 50:
|
||||
return await message.answer(
|
||||
"<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'))
|
||||
async def prod_position_add_price_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.message(F.text, StateFilter("here_position_price"))
|
||||
async def prod_position_add_price_get(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
if not is_number(message.text):
|
||||
return await message.answer(
|
||||
"<b>❌ Данные были введены неверно</b>\n"
|
||||
"📁 Введите цену для позиции",
|
||||
"<b>❌ Данные были введены неверно</b>\n" "📁 Введите цену для позиции",
|
||||
)
|
||||
|
||||
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']
|
||||
position_name = (await state.get_data())['here_position_name']
|
||||
category_id = (await state.get_data())["here_category_id"]
|
||||
position_name = (await state.get_data())["here_position_name"]
|
||||
position_price = to_number(message.text)
|
||||
position_desc = "None"
|
||||
position_photo = "None"
|
||||
position_id = gen_id(12)
|
||||
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_price=position_price,
|
||||
position_desc=position_desc,
|
||||
position_photo=position_photo,
|
||||
)
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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),
|
||||
)
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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'))
|
||||
async def prod_position_edit_name_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']
|
||||
@router.message(F.text, StateFilter("here_position_edit_name"))
|
||||
async def prod_position_edit_name_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"]
|
||||
|
||||
if len(message.text) > 50:
|
||||
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:"))
|
||||
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])
|
||||
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'))
|
||||
async def prod_position_edit_price_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']
|
||||
@router.message(F.text, StateFilter("here_position_edit_price"))
|
||||
async def prod_position_edit_price_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"]
|
||||
|
||||
if not is_number(message.text):
|
||||
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:"))
|
||||
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])
|
||||
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'))
|
||||
async def prod_position_edit_desc_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']
|
||||
@router.message(F.text, StateFilter("here_position_edit_desc"))
|
||||
async def prod_position_edit_desc_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"]
|
||||
|
||||
if len(message.text) > 1200:
|
||||
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)
|
||||
|
||||
|
||||
# Изменение изображения позиции
|
||||
@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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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:
|
||||
await call.message.edit_text(
|
||||
"<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:
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
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),
|
||||
)
|
||||
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:"))
|
||||
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])
|
||||
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})
|
||||
async def prod_item_add_position_open(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
@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
|
||||
):
|
||||
position_id = int(call.data.split(":")[1])
|
||||
|
||||
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>
|
||||
❗ Товары разделяются одной пустой строчкой. Пример:
|
||||
<code>Данные товара...
|
||||
|
||||
|
||||
Данные товара...
|
||||
|
||||
|
||||
Данные товара...</code>
|
||||
"""),
|
||||
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})
|
||||
async def prod_item_add_finish(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.callback_query(
|
||||
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])
|
||||
|
||||
try:
|
||||
count_items = (await state.get_data())['here_add_item_count']
|
||||
count_items = (await state.get_data())["here_add_item_count"]
|
||||
except Exception:
|
||||
bot_logger.debug("В state нет счетчика добавленных товаров", exc_info=True)
|
||||
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):
|
||||
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:
|
||||
get_items = clear_list(message.text.split("\n"))
|
||||
|
||||
item_count = (await state.get_data())['here_add_item_count']
|
||||
category_id = (await state.get_data())['here_add_item_category_id']
|
||||
position_id = (await state.get_data())['here_add_item_position_id']
|
||||
item_count = (await state.get_data())["here_add_item_count"]
|
||||
category_id = (await state.get_data())["here_add_item_category_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))
|
||||
|
||||
@@ -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:"))
|
||||
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])
|
||||
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),
|
||||
)
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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")
|
||||
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 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")
|
||||
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_positions = len(await Positionx().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")
|
||||
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_positions = len(await Positionx().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 Itemx().clear()
|
||||
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
await call.message.edit_text(ded(f"""
|
||||
<b>✅ Вы успешно удалили все категории</b>
|
||||
🗃 Категорий: <code>{get_categories}шт</code>
|
||||
📁 Позиций: <code>{get_positions}шт</code>
|
||||
🎁 Товаров: <code>{get_items}шт</code>
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
|
||||
|
||||
# Удаление всех позиций
|
||||
@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_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")
|
||||
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_items = len(await Itemx().get_all())
|
||||
|
||||
await Positionx().clear()
|
||||
await Itemx().clear()
|
||||
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
await call.message.edit_text(ded(f"""
|
||||
<b>✅ Вы успешно удалили все позиции</b>
|
||||
📁 Позиций: <code>{get_positions}шт</code>
|
||||
🎁 Товаров: <code>{get_items}шт</code>
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
|
||||
|
||||
# Удаление всех товаров
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from math import isfinite
|
||||
|
||||
from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from tgbot.database import Settingsx, Userx
|
||||
from tgbot.keyboards.inline_admin import settings_status_finl, settings_finl
|
||||
from tgbot.services.api_discord import DiscordDJ, DiscordAPI
|
||||
from tgbot.utils.const_functions import ded
|
||||
from tgbot.utils.const_functions import ded, is_number, to_number
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
from tgbot.utils.misc_functions import send_admins, insert_tags
|
||||
@@ -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:"))
|
||||
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_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:"))
|
||||
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_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:"))
|
||||
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_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())
|
||||
|
||||
|
||||
# Включение/выключение реферальной системы
|
||||
@router.callback_query(F.data.startswith("settings_status_referral:"))
|
||||
async def settings_status_referral(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_status = call.data.split(":")[1]
|
||||
|
||||
get_user = await Userx().get_required(user_id=call.from_user.id)
|
||||
await Settingsx().update(status_referral=get_status)
|
||||
|
||||
if get_status == "True":
|
||||
send_text = "🟢 Включил реферальную систему в боте"
|
||||
else:
|
||||
send_text = "🔴 Выключил реферальную систему в боте"
|
||||
|
||||
await send_admins(
|
||||
bot,
|
||||
f"👤 Администратор <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
|
||||
f"{send_text}",
|
||||
not_me=get_user.user_id,
|
||||
)
|
||||
|
||||
await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
|
||||
|
||||
|
||||
# Включение/выключение уведомлений о покупках
|
||||
@router.callback_query(F.data.startswith("settings_notification_buy:"))
|
||||
async def settings_notification_buy(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_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:"))
|
||||
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_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.set_state("here_settings_faq")
|
||||
await call.message.edit_text(
|
||||
ded("""
|
||||
await call.message.edit_text(ded("""
|
||||
<b>❔ Введите новый текст для FAQ</b>
|
||||
❕ Вы можете использовать заготовленный синтаксис и HTML разметку:
|
||||
▪️ <code>{username}</code> - логин пользоваля
|
||||
▪️ <code>{user_id}</code> - айди пользователя
|
||||
▪️ <code>{firstname}</code> - имя пользователя
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
|
||||
|
||||
# Изменение поддержки
|
||||
@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.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:"))
|
||||
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]
|
||||
|
||||
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:"))
|
||||
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]
|
||||
|
||||
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:"))
|
||||
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]
|
||||
|
||||
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
|
||||
@@ -282,8 +281,7 @@ async def settings_faq_get(message: Message, bot: Bot, state: FSM, arSession: AR
|
||||
except Exception:
|
||||
bot_logger.debug("Некорректная HTML-разметка FAQ", exc_info=True)
|
||||
return await message.answer(
|
||||
"<b>❌ Ошибка синтаксиса HTML</b>\n"
|
||||
"❔ Введите новый текст для FAQ",
|
||||
"<b>❌ Ошибка синтаксиса HTML</b>\n" "❔ Введите новый текст для FAQ",
|
||||
)
|
||||
|
||||
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"))
|
||||
async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
get_support = message.text
|
||||
@@ -310,67 +374,3 @@ async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession
|
||||
"<b>🖍 Изменение данных бота</b>",
|
||||
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
@@ -14,21 +14,20 @@ from tgbot.utils.text_functions import position_open_user
|
||||
|
||||
# Игнор-колбэки покупок
|
||||
prohibit_buy = (
|
||||
'buy_category_swipe',
|
||||
'buy_category_open',
|
||||
'buy_position_swipe',
|
||||
'buy_position_open',
|
||||
'buy_item_open',
|
||||
'buy_item_confirm',
|
||||
"buy_category_swipe",
|
||||
"buy_category_open",
|
||||
"buy_position_swipe",
|
||||
"buy_position_open",
|
||||
"buy_item_open",
|
||||
"buy_item_confirm",
|
||||
)
|
||||
|
||||
# Игнор-колбэки пополнений
|
||||
prohibit_refill = (
|
||||
'user_refill',
|
||||
'user_refill_method',
|
||||
'Pay:Cryptobot',
|
||||
'Pay:Yoomoney',
|
||||
'Pay:',
|
||||
"user_refill",
|
||||
"user_refill_method",
|
||||
"Pay:Cryptobot",
|
||||
"Pay:",
|
||||
)
|
||||
|
||||
router = Router(name=__name__)
|
||||
@@ -54,7 +53,9 @@ async def filter_work_message(message: Message, bot: Bot, state: FSM, arSession:
|
||||
|
||||
# Фильтр на технические работы - колбэк
|
||||
@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 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(), StateFilter('here_item_count'))
|
||||
@router.message(IsBuy(), StateFilter("here_item_count"))
|
||||
async def filter_buy_message(message: Message, bot: Bot, state: FSM, arSession: ARS):
|
||||
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))
|
||||
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 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):
|
||||
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))
|
||||
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 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):
|
||||
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):
|
||||
deepling_args = message.text[7:]
|
||||
|
||||
if deepling_args.startswith("p_"):
|
||||
if deepling_args.startswith("r_"):
|
||||
await main_start(message, bot, state, arSession)
|
||||
elif deepling_args.startswith("p_"):
|
||||
position_id_raw = deepling_args[2:]
|
||||
|
||||
if not position_id_raw.isdigit():
|
||||
|
||||
@@ -5,7 +5,7 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import Command
|
||||
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.keyboards.inline_user import user_support_finl
|
||||
from tgbot.keyboards.inline_user_page import *
|
||||
@@ -60,7 +60,7 @@ async def user_available(message: Message, bot: Bot, state: FSM, arSession: ARS)
|
||||
|
||||
|
||||
# Открытие 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):
|
||||
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":
|
||||
return await message.answer(
|
||||
ded(f"""
|
||||
❔ Текст FAQ не указан. Измените его в настройках бота.
|
||||
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_desc()}
|
||||
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_warning()}
|
||||
"""),
|
||||
ded(f"""❔ Текст FAQ не указан."""),
|
||||
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):
|
||||
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":
|
||||
return await message.answer(
|
||||
ded(f"""
|
||||
☎️ Контакты поддержки не указаны. Измените их в настройках бота.
|
||||
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_desc()}
|
||||
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_warning()}
|
||||
"""),
|
||||
ded(f"""☎️ Контакты поддержки не указаны."""),
|
||||
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):
|
||||
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):
|
||||
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")
|
||||
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 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:"))
|
||||
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])
|
||||
|
||||
items_available, remover_max, remover_now = await get_items_available(remover)
|
||||
|
||||
@@ -5,10 +5,21 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
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_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.utils.const_functions import ded, del_message, convert_date, send_admins
|
||||
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:"))
|
||||
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])
|
||||
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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:"))
|
||||
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])
|
||||
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_payments.status_cryptobot == "True" or get_payments.status_yoomoney == "True":
|
||||
if get_payments.status_cryptobot == "True":
|
||||
await call.message.answer(
|
||||
"<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)
|
||||
else:
|
||||
return await call.answer("❗ У вас недостаточно средств. Пополните баланс", True)
|
||||
return await call.answer(
|
||||
"❗ У вас недостаточно средств. Пополните баланс", True
|
||||
)
|
||||
|
||||
if len(get_items) < 1:
|
||||
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>{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:
|
||||
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"))
|
||||
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_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:
|
||||
await state.clear()
|
||||
return await message.answer("<b>🎁 Товар который вы хотели купить, закончился</b>")
|
||||
return await message.answer(
|
||||
"<b>🎁 Товар который вы хотели купить, закончился</b>"
|
||||
)
|
||||
|
||||
# Если введено кол-во товаров меньше 1 или меньше кол-ва имеющегося в наличии
|
||||
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>{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":
|
||||
return await call.message.edit_text("<b>❌ Позиция не была найдена</b>")
|
||||
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":
|
||||
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_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>
|
||||
▪️ Товар: <code>{purchase_result.position_name} | {purchase_result.purchase_count}шт | {purchase_result.purchase_price}₽</code>
|
||||
▪️ Чек: <code>#{purchase_result.receipt}</code>
|
||||
""")
|
||||
"""),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from math import isfinite
|
||||
from urllib.parse import urlparse
|
||||
import re
|
||||
|
||||
from aiogram import Bot, F, Router
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from tgbot.database import ReferralWithdrawalx, Referralx, Settingsx, Userx
|
||||
from tgbot.keyboards.inline_admin import referral_withdrawal_actions_finl
|
||||
from tgbot.keyboards.inline_user import (
|
||||
referral_menu_finl,
|
||||
referral_transfer_method_finl,
|
||||
referral_withdrawal_method_finl,
|
||||
)
|
||||
from tgbot.utils.const_functions import clear_html, ded, is_number, to_number
|
||||
from tgbot.utils.misc.bot_models import ARS, FSM
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
router = Router(name=__name__)
|
||||
|
||||
|
||||
def is_valid_withdrawal_recipient(withdrawal_method: str, recipient: str) -> bool:
|
||||
if withdrawal_method == "Cryptobot":
|
||||
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
|
||||
|
||||
if withdrawal_method == "Lolzteam":
|
||||
parsed_url = urlparse(recipient)
|
||||
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
|
||||
"zelenka.guru",
|
||||
"lolz.team",
|
||||
"lolz.live",
|
||||
"lolz.guru",
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def withdrawal_recipient_hint(withdrawal_method: str) -> str:
|
||||
if withdrawal_method == "Cryptobot":
|
||||
return (
|
||||
"<b>📥 Введите @username получателя в CryptoBot</b>\n"
|
||||
"➖➖➖➖➖➖➖➖➖➖\n"
|
||||
"<i>Пример: @username</i>"
|
||||
)
|
||||
|
||||
return (
|
||||
"<b>📥 Введите ссылку на профиль получателя Lolzteam</b>\n"
|
||||
"➖➖➖➖➖➖➖➖➖➖\n"
|
||||
"<i>Подойдут ссылки с zelenka.guru, lolz.team, lolz.live или lolz.guru</i>"
|
||||
)
|
||||
|
||||
|
||||
def withdrawal_method_title(withdrawal_method: str) -> str:
|
||||
if withdrawal_method == "Cryptobot":
|
||||
return "CryptoBot"
|
||||
|
||||
return withdrawal_method
|
||||
|
||||
|
||||
async def get_referral_menu_data(user_id: int, bot: Bot):
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
if get_settings.status_referral != "True":
|
||||
return None
|
||||
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
get_referrals = await Referralx().gets(referrer_id=user_id)
|
||||
|
||||
bot_username = get_settings.misc_bot
|
||||
|
||||
if bot_username == "None":
|
||||
bot_username = (await bot.get_me()).username or ""
|
||||
|
||||
referral_link = f"https://t.me/{bot_username}?start=r_{user_id}"
|
||||
available_balance = round(
|
||||
get_user.user_referral_balance - get_user.user_referral_hold,
|
||||
2,
|
||||
)
|
||||
|
||||
referral_text = ded(f"""
|
||||
<b>🤝 Реферальная система</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Приглашено пользователей: <code>{len(get_referrals)}</code>
|
||||
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
|
||||
▪️ Доступно: <code>{available_balance}₽</code>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Бонус за приглашение: <code>{get_settings.referral_bonus_rub}₽</code>
|
||||
▪️ Доход с пополнений: <code>{get_settings.referral_refill_percent}%</code>
|
||||
""")
|
||||
|
||||
return referral_text, referral_menu_finl(referral_link)
|
||||
|
||||
|
||||
@router.message(F.text == "🤝 Реферальная система")
|
||||
async def show_referral_menu(
|
||||
message: Message,
|
||||
bot: Bot,
|
||||
state: FSM,
|
||||
arSession: ARS,
|
||||
user_id: int | None = None,
|
||||
):
|
||||
await state.clear()
|
||||
|
||||
user_id = user_id or message.from_user.id
|
||||
referral_menu_data = await get_referral_menu_data(user_id, bot)
|
||||
if referral_menu_data is None:
|
||||
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||
|
||||
referral_text, referral_keyboard = referral_menu_data
|
||||
|
||||
await message.answer(
|
||||
referral_text,
|
||||
reply_markup=referral_keyboard,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "referral_menu")
|
||||
async def user_referral_menu_return(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
await state.clear()
|
||||
referral_menu_data = await get_referral_menu_data(call.from_user.id, bot)
|
||||
if referral_menu_data is None:
|
||||
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||
|
||||
referral_text, referral_keyboard = referral_menu_data
|
||||
await call.message.edit_text(
|
||||
referral_text,
|
||||
reply_markup=referral_keyboard,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "referral_transfer")
|
||||
async def user_referral_transfer_start(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||
|
||||
await state.clear()
|
||||
await state.set_state("here_referral_transfer_amount")
|
||||
await call.message.edit_text(
|
||||
"<b>💸 Введите сумму для перевода на основной баланс</b>",
|
||||
reply_markup=referral_transfer_method_finl(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "referral_withdrawal")
|
||||
async def user_referral_withdrawal_start(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_settings = await Settingsx().get()
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||
|
||||
await state.clear()
|
||||
await call.message.edit_text(
|
||||
"<b>📥 Выберите способ вывода средств</b>",
|
||||
reply_markup=referral_withdrawal_method_finl(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("referral_withdrawal_method:"))
|
||||
async def user_referral_withdrawal_method(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
withdrawal_method = call.data.split(":", 1)[1]
|
||||
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||
return await call.answer("❌ Способ вывода не найден", True)
|
||||
|
||||
get_settings = await Settingsx().get()
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await call.answer("⛔ Реферальная система временно отключена", True)
|
||||
|
||||
await state.clear()
|
||||
await state.update_data(here_referral_withdrawal_method=withdrawal_method)
|
||||
await state.set_state("here_referral_withdrawal_amount")
|
||||
await call.message.edit_text("<b>📥 Введите сумму для вывода</b>")
|
||||
|
||||
|
||||
@router.message(F.text, StateFilter("here_referral_withdrawal_amount"))
|
||||
async def user_referral_withdrawal_amount(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_settings = await Settingsx().get()
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||
|
||||
if not is_number(message.text):
|
||||
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||
|
||||
withdrawal_amount = float(to_number(message.text))
|
||||
if not isfinite(withdrawal_amount) or withdrawal_amount <= 0:
|
||||
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||
|
||||
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||
available_balance = round(
|
||||
get_user.user_referral_balance - get_user.user_referral_hold, 2
|
||||
)
|
||||
if withdrawal_amount > available_balance:
|
||||
return await message.answer(
|
||||
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||
)
|
||||
|
||||
withdrawal_data = await state.get_data()
|
||||
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
|
||||
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
|
||||
await state.clear()
|
||||
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
|
||||
|
||||
await state.update_data(here_referral_withdrawal_amount=withdrawal_amount)
|
||||
await state.set_state("here_referral_withdrawal_recipient")
|
||||
await message.answer(withdrawal_recipient_hint(withdrawal_method))
|
||||
|
||||
|
||||
@router.message(F.text, StateFilter("here_referral_withdrawal_recipient"))
|
||||
async def user_referral_withdrawal_recipient(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_settings = await Settingsx().get()
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||
|
||||
withdrawal_data = await state.get_data()
|
||||
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
|
||||
withdrawal_amount = withdrawal_data.get("here_referral_withdrawal_amount")
|
||||
|
||||
if withdrawal_method is None or withdrawal_amount is None:
|
||||
await state.clear()
|
||||
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
|
||||
|
||||
withdrawal_recipient = clear_html(message.text).strip()
|
||||
if not withdrawal_recipient:
|
||||
return await message.answer(
|
||||
"<b>❌ Введите реквизиты</b>\n"
|
||||
f"{withdrawal_recipient_hint(withdrawal_method)}"
|
||||
)
|
||||
|
||||
if not is_valid_withdrawal_recipient(withdrawal_method, withdrawal_recipient):
|
||||
return await message.answer(
|
||||
"<b>❌ Некорректные реквизиты</b>\n"
|
||||
f"{withdrawal_recipient_hint(withdrawal_method)}"
|
||||
)
|
||||
|
||||
withdrawal_status, withdrawal = await ReferralWithdrawalx().create_pending(
|
||||
user_id=message.from_user.id,
|
||||
withdrawal_amount=withdrawal_amount,
|
||||
withdrawal_method=withdrawal_method,
|
||||
withdrawal_recipient=withdrawal_recipient,
|
||||
)
|
||||
|
||||
if withdrawal_status == "INSUFFICIENT_FUNDS":
|
||||
return await message.answer(
|
||||
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||
)
|
||||
|
||||
if withdrawal_status in ("INVALID_AMOUNT", "INVALID_RECIPIENT", "INVALID_METHOD"):
|
||||
return await message.answer("<b>❌ Некорректные данные для вывода</b>")
|
||||
|
||||
if withdrawal_status == "USER_NOT_FOUND":
|
||||
await state.clear()
|
||||
return await message.answer(
|
||||
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
|
||||
)
|
||||
|
||||
if withdrawal_status != "ok" or withdrawal is None:
|
||||
return await message.answer(
|
||||
"<b>❌ Не удалось создать заявку. Попробуйте позже</b>"
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
|
||||
await message.answer(
|
||||
ded(f"""
|
||||
<b>📥 Заявка на вывод создана</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
▪️ Способ: <code>{method_title}</code>
|
||||
"""),
|
||||
)
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
<b>📥 Новая заявка на вывод</b>
|
||||
|
||||
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
|
||||
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
|
||||
▪️ Способ: <code>{method_title}</code>
|
||||
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
|
||||
"""),
|
||||
keyboard=referral_withdrawal_actions_finl(withdrawal.increment),
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text, StateFilter("here_referral_transfer_amount"))
|
||||
async def user_referral_transfer_amount(
|
||||
message: Message, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
if get_settings.status_referral != "True":
|
||||
await state.clear()
|
||||
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
|
||||
|
||||
if not is_number(message.text):
|
||||
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||
|
||||
transfer_amount = float(to_number(message.text))
|
||||
|
||||
if not isfinite(transfer_amount) or transfer_amount <= 0:
|
||||
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||
|
||||
transfer_status = await Referralx().transfer_to_main_balance(
|
||||
user_id=message.from_user.id,
|
||||
amount=transfer_amount,
|
||||
)
|
||||
|
||||
if transfer_status == "INSUFFICIENT_FUNDS":
|
||||
return await message.answer(
|
||||
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
|
||||
)
|
||||
if transfer_status == "INVALID_AMOUNT":
|
||||
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
|
||||
if transfer_status == "USER_NOT_FOUND":
|
||||
await state.clear()
|
||||
return await message.answer(
|
||||
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
|
||||
)
|
||||
if transfer_status != "ok":
|
||||
return await message.answer(
|
||||
"<b>❌ Не удалось выполнить перевод. Попробуйте позже</b>"
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
get_user = await Userx().get_required(user_id=message.from_user.id)
|
||||
|
||||
await message.answer(
|
||||
ded(f"""
|
||||
<b>💸 Средства переведены на основной баланс</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Переведено: <code>{round(transfer_amount, 2)}₽</code>
|
||||
▪️ Основной баланс: <code>{get_user.user_balance}₽</code>
|
||||
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
|
||||
"""),
|
||||
)
|
||||
@@ -5,17 +5,18 @@ from aiogram import Router, Bot, F
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import CallbackQuery, Message, PreCheckoutQuery
|
||||
|
||||
from tgbot.database import Paymentsx, Refillx, Userx, Settingsx
|
||||
from tgbot.database import Paymentsx, ReferralTransactionx, Refillx, Userx, Settingsx
|
||||
from tgbot.keyboards.inline_user import refill_bill_finl, refill_method_finl
|
||||
from tgbot.services.api_cryptobot import CryptobotAPI
|
||||
from tgbot.services.api_lolzteam import LolzteamAPI
|
||||
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.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import FSM, ARS
|
||||
from tgbot.utils.misc_functions import send_admins
|
||||
|
||||
min_refill_rub = 5 # Минимальная сумма пополнения в рублях
|
||||
max_refill_rub = 150000 # Максимальная сумма пополнения в рублях
|
||||
|
||||
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()
|
||||
|
||||
if (
|
||||
get_payments.status_cryptobot == "False" and
|
||||
get_payments.status_yoomoney == "False" and
|
||||
get_payments.status_stars == "False"
|
||||
get_payments.status_cryptobot == "False"
|
||||
and get_payments.status_lolzteam == "False"
|
||||
and get_payments.status_stars == "False"
|
||||
):
|
||||
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:"))
|
||||
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]
|
||||
|
||||
get_payments = await Paymentsx().get()
|
||||
|
||||
if refill_method == "Cryptobot" and get_payments.status_cryptobot == "False":
|
||||
return await call.answer("❌ Пополнение данным способом временно недоступно", True)
|
||||
elif refill_method == "Yoomoney" and get_payments.status_yoomoney == "False":
|
||||
return await call.answer("❌ Пополнение данным способом временно недоступно", 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":
|
||||
return await call.answer("❌ Пополнение данным способом временно недоступно", True)
|
||||
return await call.answer(
|
||||
"❌ Пополнение данным способом временно недоступно", True
|
||||
)
|
||||
|
||||
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(
|
||||
ded(f"""
|
||||
<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>")
|
||||
|
||||
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()
|
||||
|
||||
# Генерация платежа
|
||||
@@ -95,14 +104,16 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
|
||||
update=cache_message,
|
||||
)
|
||||
).bill(refill_amount)
|
||||
elif refill_method == "Yoomoney":
|
||||
|
||||
elif refill_method == "Lolzteam":
|
||||
bill_message, bill_link, bill_receipt = await (
|
||||
await YoomoneyAPI.connect(
|
||||
await LolzteamAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
update=cache_message,
|
||||
)
|
||||
).bill(refill_amount)
|
||||
|
||||
elif refill_method == "Stars":
|
||||
bill_message, bill_link, bill_receipt = await (
|
||||
await StarsAPI.connect(
|
||||
@@ -111,6 +122,7 @@ async def refill_amount_get(message: Message, bot: Bot, state: FSM, arSession: A
|
||||
update=cache_message,
|
||||
)
|
||||
).bill(refill_amount)
|
||||
|
||||
else:
|
||||
return await cache_message.edit_text(
|
||||
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
|
||||
@router.callback_query(F.data.startswith('Pay:Cryptobot'))
|
||||
async def refill_check_cryptobot(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS):
|
||||
@router.callback_query(F.data.startswith("Pay:Cryptobot"))
|
||||
async def refill_check_cryptobot(
|
||||
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
|
||||
):
|
||||
pay_method = call.data.split(":")[1]
|
||||
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":
|
||||
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":
|
||||
await call.answer("❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30)
|
||||
await call.answer(
|
||||
"❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30
|
||||
)
|
||||
elif pay_status == 1:
|
||||
await call.answer("❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30)
|
||||
await call.answer(
|
||||
"❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30
|
||||
)
|
||||
elif pay_status == 2:
|
||||
await call.answer("❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5)
|
||||
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)
|
||||
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):
|
||||
pay_method = call.data.split(":")[1]
|
||||
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":
|
||||
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":
|
||||
await call.answer("❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30)
|
||||
await call.answer(
|
||||
"❗ Пользователь не найден. Напишите в поддержку.", True, cache_time=30
|
||||
)
|
||||
elif pay_status == 1:
|
||||
await call.answer("❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30)
|
||||
await call.answer(
|
||||
"❗️ Не удалось проверить платёж. Попробуйте позже", True, cache_time=30
|
||||
)
|
||||
elif pay_status == 2:
|
||||
await call.answer("❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5)
|
||||
await call.answer(
|
||||
"❗️ Оплата не была найдена. Попробуйте позже", True, cache_time=5
|
||||
)
|
||||
else:
|
||||
await call.answer(f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.", True, cache_time=5)
|
||||
await call.answer(
|
||||
f"❗ Неизвестная ошибка {pay_status}. Обратитесь в поддержку.",
|
||||
True,
|
||||
cache_time=5,
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
#################################### ЗВЁЗДЫ ####################################
|
||||
# Подтверждение платежа Telegram Stars
|
||||
@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 StarsAPI.connect(
|
||||
bot=bot,
|
||||
@@ -277,7 +333,9 @@ async def refill_stars_success(message: Message, bot: Bot, state: FSM, arSession
|
||||
stars_api.parse_successful_payment(payment)
|
||||
)
|
||||
except ValueError:
|
||||
bot_logger.warning("Некорректная successful_payment для Telegram Stars", exc_info=True)
|
||||
bot_logger.warning(
|
||||
"Некорректная successful_payment для Telegram Stars", exc_info=True
|
||||
)
|
||||
return
|
||||
|
||||
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:
|
||||
await delete_refill_message(bot, bill_chat_id, bill_message_id)
|
||||
|
||||
await message.answer(
|
||||
ded(f"""
|
||||
await message.answer(ded(f"""
|
||||
<b>💰 Вы пополнили баланс на сумму <code>{pay_amount}₽</code>. Удачи ❤️
|
||||
🧾 Чек: <code>#{receipt}</code></b>
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
elif refill_status == "ALREADY":
|
||||
if bill_chat_id == message.chat.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(
|
||||
bot: Bot,
|
||||
call: CallbackQuery,
|
||||
pay_method: str,
|
||||
pay_amount: float,
|
||||
pay_receipt: Optional[int] = None,
|
||||
pay_comment: Optional[str] = None,
|
||||
bot: Bot,
|
||||
call: CallbackQuery,
|
||||
pay_method: str,
|
||||
pay_amount: float,
|
||||
pay_receipt: Optional[int] = None,
|
||||
pay_comment: Optional[str] = None,
|
||||
):
|
||||
user_id = call.from_user.id
|
||||
|
||||
@@ -338,19 +394,19 @@ async def refill_success(
|
||||
if response_success != "ok":
|
||||
return response_success
|
||||
|
||||
await call.message.answer(
|
||||
ded(f"""
|
||||
await call.message.answer(ded(f"""
|
||||
<b>💰 Вы пополнили баланс на сумму <code>{pay_amount}₽</code>. Удачи ❤️
|
||||
🧾 Чек: <code>#{receipt}</code></b>
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
await delete_refill_message(bot, call.message.chat.id, call.message.message_id)
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
@@ -362,17 +418,17 @@ async def delete_refill_message(bot: Bot, chat_id: Optional[int], message_id: Op
|
||||
|
||||
# Сохранение пополнения и уведомление админов
|
||||
async def save_refill_success(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
pay_method: str,
|
||||
pay_amount: float,
|
||||
pay_receipt: int,
|
||||
pay_comment: str,
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
pay_method: str,
|
||||
pay_amount: float,
|
||||
pay_receipt: int,
|
||||
pay_comment: str,
|
||||
) -> Tuple[str, int]:
|
||||
if pay_method == "Yoomoney":
|
||||
text_method = "ЮMoney"
|
||||
elif pay_method == "Cryptobot":
|
||||
if pay_method == "Cryptobot":
|
||||
text_method = "CryptoBot"
|
||||
elif pay_method == "Lolzteam":
|
||||
text_method = "Lolzteam"
|
||||
elif pay_method == "Stars":
|
||||
text_method = "Telegram Stars"
|
||||
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>
|
||||
▪️ Сумма пополнения: <code>{pay_amount}₽</code> <code>({text_method})</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
|
||||
|
||||
@@ -13,20 +13,31 @@ from tgbot.utils.misc.bot_models import ARS
|
||||
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
|
||||
class CryptobotAPI:
|
||||
# Настройка клиента CryptoBot
|
||||
def __init__(
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: str = "None",
|
||||
adding: bool = False,
|
||||
skipping_error: bool = False,
|
||||
self,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: str = "None",
|
||||
adding: bool = False,
|
||||
skipping_error: bool = False,
|
||||
):
|
||||
self.bot = bot
|
||||
self.arSession = arSession
|
||||
@@ -38,12 +49,12 @@ class CryptobotAPI:
|
||||
# Инициализация данных
|
||||
@classmethod
|
||||
async def connect(
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: Optional[str] = None,
|
||||
skipping_error: bool = False,
|
||||
cls,
|
||||
bot: Bot,
|
||||
arSession: ARS,
|
||||
update: Optional[Union[Message, CallbackQuery]] = None,
|
||||
token: Optional[str] = None,
|
||||
skipping_error: bool = False,
|
||||
) -> "CryptobotAPI":
|
||||
adding = token is not None
|
||||
|
||||
@@ -75,21 +86,23 @@ class CryptobotAPI:
|
||||
await send_admins(
|
||||
self.bot,
|
||||
f"<b>🔷 CryptoBot недоступен. Как можно быстрее его замените</b>\n"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>"
|
||||
f"❗️ Ошибка: <code>{error_code}</code>",
|
||||
)
|
||||
|
||||
# Проверка кассы/кошелька
|
||||
async def check(self) -> Tuple[bool, str]:
|
||||
status, response = await self._request("getMe")
|
||||
|
||||
if status and response['ok']:
|
||||
return True, ded(f"""
|
||||
if status and response["ok"]:
|
||||
return True, ded(
|
||||
f"""
|
||||
<b>🔷 CryptoBot кошелёк полностью функционирует ✅</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Токен: <code>{self.token}</code>
|
||||
▪️ Айди: <code>{response['result']['app_id']}</code>
|
||||
▪️ Имя: <code>{response['result']['name']}</code>
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
return False, "<b>🔷 Не удалось проверить CryptoBot кошелёк ❌</b>"
|
||||
|
||||
@@ -97,17 +110,17 @@ class CryptobotAPI:
|
||||
async def balance(self) -> str:
|
||||
status, response = await self._request("getBalance")
|
||||
|
||||
if status and response['ok']:
|
||||
if status and response["ok"]:
|
||||
save_currencies = []
|
||||
|
||||
response_balances = sorted(
|
||||
response['result'],
|
||||
response["result"],
|
||||
reverse=True,
|
||||
key=lambda balance: to_number(balance['available']),
|
||||
key=lambda balance: to_number(balance["available"]),
|
||||
)
|
||||
|
||||
for currency in response_balances:
|
||||
if currency['currency_code'] in ALLOW_CURRENCIES:
|
||||
if currency["currency_code"] in ALLOW_CURRENCIES:
|
||||
save_currencies.append(
|
||||
f"▪️ {currency['currency_code']}: <code>{currency['available']}</code>"
|
||||
)
|
||||
@@ -123,24 +136,26 @@ class CryptobotAPI:
|
||||
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)
|
||||
|
||||
payload = {
|
||||
'currency_type': 'fiat',
|
||||
'fiat': 'RUB',
|
||||
'amount': str(pay_amount),
|
||||
'expires_in': 10800,
|
||||
'accepted_assets': assets_currencies
|
||||
"currency_type": "fiat",
|
||||
"fiat": "RUB",
|
||||
"amount": str(pay_amount),
|
||||
"expires_in": 10800,
|
||||
"accepted_assets": assets_currencies,
|
||||
}
|
||||
|
||||
status, response = await self._request("createInvoice", payload)
|
||||
|
||||
if status and response['ok']:
|
||||
if status and response["ok"]:
|
||||
bill_message = ded(f"""
|
||||
<b>💰 Пополнение баланса</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||
▪️ Для пополнения баланса, нажмите на кнопку ниже
|
||||
<code>Перейти к оплате</code> и оплатите выставленный вам счёт
|
||||
▪️ У вас имеется 3 часа на оплату счета
|
||||
▪️ Сумма пополнения: <code>{pay_amount}₽</code>
|
||||
@@ -148,46 +163,52 @@ class CryptobotAPI:
|
||||
❗️ После оплаты, нажмите на <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, "", ""
|
||||
|
||||
# Проверка счета на оплату
|
||||
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 = {
|
||||
'invoice_ids': f'{bill_receipt}',
|
||||
'fiat': 'RUB',
|
||||
"invoice_ids": f"{bill_receipt}",
|
||||
"fiat": "RUB",
|
||||
}
|
||||
|
||||
status, response = await self._request("getInvoices", payload)
|
||||
|
||||
pay_status, pay_amount = 1, 0
|
||||
|
||||
if status and response['ok']:
|
||||
get_invoice = response['result']['items'][0]
|
||||
if status and response["ok"]:
|
||||
get_invoice = response["result"]["items"][0]
|
||||
|
||||
if get_invoice['status'] == "active":
|
||||
if get_invoice["status"] == "active":
|
||||
pay_status = 2
|
||||
elif get_invoice['status'] == "expired":
|
||||
elif get_invoice["status"] == "expired":
|
||||
pay_status = 3
|
||||
else:
|
||||
pay_status = 0
|
||||
pay_amount = to_number(get_invoice['amount'])
|
||||
pay_amount = to_number(get_invoice["amount"])
|
||||
|
||||
return pay_status, pay_amount
|
||||
|
||||
# Генерация запроса
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
self,
|
||||
method: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[bool, Any]:
|
||||
session = await self.arSession.get_session()
|
||||
|
||||
base_url = 'https://pay.crypt.bot/api/'
|
||||
base_url = "https://pay.crypt.bot/api/"
|
||||
headers = {
|
||||
'Crypto-Pay-API-Token': self.token,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
"Crypto-Pay-API-Token": self.token,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
|
||||
url = base_url + method
|
||||
@@ -204,7 +225,9 @@ class CryptobotAPI:
|
||||
if response.status == 200:
|
||||
return True, response_data
|
||||
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
|
||||
except ClientConnectorCertificateError:
|
||||
|
||||
@@ -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']
|
||||
@@ -16,19 +16,16 @@ from tgbot.utils.misc.bot_models import ARS
|
||||
@dataclass
|
||||
class ModelHostingText:
|
||||
telegraph: str = "telegraph"
|
||||
pastie: str = "pastie"
|
||||
friendpaste: str = "friendpaste"
|
||||
snippet: str = "snippet"
|
||||
|
||||
|
||||
# API для работы с текстовыми хостингами
|
||||
class HostingAPI:
|
||||
# Настройка клиента текстового хостинга
|
||||
def __init__(
|
||||
self,
|
||||
arSession: ARS,
|
||||
bot: Bot,
|
||||
text_hosting: str,
|
||||
self,
|
||||
arSession: ARS,
|
||||
bot: Bot,
|
||||
text_hosting: str,
|
||||
):
|
||||
self.arSession = arSession
|
||||
self.bot = bot
|
||||
@@ -42,7 +39,7 @@ class HostingAPI:
|
||||
return cls(
|
||||
arSession=arSession,
|
||||
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))
|
||||
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:
|
||||
return str(return_link)
|
||||
|
||||
# pastie -> friendpaste
|
||||
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
|
||||
self.text_hosting = ModelHostingText.telegraph
|
||||
|
||||
if attempt < max_attempts - 1:
|
||||
await asyncio.sleep(3)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -29,13 +29,13 @@ def rkb(text: str) -> KeyboardButton:
|
||||
|
||||
# Быстрая сборка inline-кнопки
|
||||
def ikb(
|
||||
text: str,
|
||||
data: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
switch: Optional[str] = None,
|
||||
web: Optional[str] = None,
|
||||
copy: Optional[str] = None,
|
||||
login: Optional[str] = None,
|
||||
text: str,
|
||||
data: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
switch: Optional[str] = None,
|
||||
web: Optional[str] = None,
|
||||
copy: Optional[str] = None,
|
||||
login: Optional[str] = None,
|
||||
) -> InlineKeyboardButton:
|
||||
if data is not None:
|
||||
return InlineKeyboardButton(text=text, callback_data=data)
|
||||
@@ -48,7 +48,7 @@ def ikb(
|
||||
if copy is not None:
|
||||
return InlineKeyboardButton(text=text, copy_text=CopyTextButton(text=copy))
|
||||
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-кнопки")
|
||||
|
||||
@@ -61,36 +61,27 @@ async def del_message(message: Message) -> None:
|
||||
bot_logger.debug("Не удалось удалить сообщение", exc_info=True)
|
||||
|
||||
|
||||
# Отправка фото или обычного сообщения
|
||||
# Отправка обычного сообщения
|
||||
async def smart_message(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
||||
photo: Optional[str] = None,
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
||||
) -> None:
|
||||
if photo is not None and photo.title() != "None":
|
||||
await bot.send_photo(
|
||||
chat_id=user_id,
|
||||
photo=photo,
|
||||
caption=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
|
||||
# Отправка сообщения всем админам
|
||||
async def send_admins(
|
||||
bot: Bot,
|
||||
text: str,
|
||||
keyboard: Optional[InlineKeyboardMarkup] = None,
|
||||
markup: Optional[InlineKeyboardMarkup] = None,
|
||||
not_me: int = 0,
|
||||
bot: Bot,
|
||||
text: str,
|
||||
keyboard: Optional[InlineKeyboardMarkup] = None,
|
||||
markup: Optional[InlineKeyboardMarkup] = None,
|
||||
not_me: int = 0,
|
||||
) -> None:
|
||||
reply_markup = markup or keyboard
|
||||
|
||||
@@ -104,7 +95,9 @@ async def send_admins(
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
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:
|
||||
split_text = get_text.split("\n")
|
||||
|
||||
if split_text[0] == "": split_text.pop(0)
|
||||
if split_text[-1] == "": split_text.pop()
|
||||
if split_text[0] == "":
|
||||
split_text.pop(0)
|
||||
if split_text[-1] == "":
|
||||
split_text.pop()
|
||||
save_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]:
|
||||
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)
|
||||
|
||||
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:
|
||||
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")
|
||||
|
||||
|
||||
@@ -7,8 +7,13 @@ from typing import Union
|
||||
from aiogram import Bot
|
||||
from aiogram.types import FSInputFile, CallbackQuery, Message
|
||||
|
||||
from tgbot.data.config import BOT_DATABASE_EXPORT, BOT_STATUS_NOTIFICATION, BOT_VERSION, PATH_DATABASE, get_admins, \
|
||||
get_text_desc
|
||||
from tgbot.data.config import (
|
||||
BOT_DATABASE_EXPORT,
|
||||
BOT_STATUS_NOTIFICATION,
|
||||
BOT_VERSION,
|
||||
PATH_DATABASE,
|
||||
get_admins,
|
||||
)
|
||||
from tgbot.database import Userx, Settingsx
|
||||
from tgbot.utils.const_functions import get_unix, get_date, ded, send_admins
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
@@ -40,9 +45,17 @@ async def autosettings_unix():
|
||||
now_month = datetime.now().month
|
||||
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_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(
|
||||
misc_profit_day=unix_day,
|
||||
@@ -68,8 +81,6 @@ async def startup_notify(bot: Bot, arSession: ARS):
|
||||
ded(f"""
|
||||
<b>✅ Бот был успешно запущен</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_desc()}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
<code>❗ Данное сообщение видят только администраторы бота.</code>
|
||||
"""),
|
||||
)
|
||||
@@ -91,7 +102,9 @@ async def autobackup_admin(bot: Bot):
|
||||
disable_notification=True,
|
||||
)
|
||||
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())
|
||||
|
||||
if float(response_data['version']) > float(BOT_VERSION):
|
||||
if float(response_data["version"]) > float(BOT_VERSION):
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
@@ -132,7 +145,7 @@ async def check_mail(bot: Bot, arSession: ARS):
|
||||
)
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if response_data['status']:
|
||||
if response_data["status"]:
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
@@ -176,21 +189,23 @@ async def functions_mail_make(bot: Bot, message: Message, call: CallbackQuery):
|
||||
users_receive += 1
|
||||
except Exception:
|
||||
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
|
||||
|
||||
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 call.message.edit_text(
|
||||
ded(f"""
|
||||
await call.message.edit_text(ded(f"""
|
||||
<b>📢 Рассылка была завершена за <code>{get_unix() - get_time}сек</code></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
👤 Всего пользователей: <code>{len(get_users)}</code>
|
||||
✅ Пользователей получило сообщение: <code>{users_receive}</code>
|
||||
❌ Пользователей не получило сообщение: <code>{users_block}</code>
|
||||
""")
|
||||
)
|
||||
"""))
|
||||
|
||||
+110
-40
@@ -21,7 +21,11 @@ from tgbot.database import (
|
||||
ModelUser,
|
||||
)
|
||||
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_products import products_open_finl
|
||||
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(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🎁 Покупка товара</b>{hide_link(get_position.position_photo)}
|
||||
<b>🎁 Покупка товара</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Название: <code>{get_position.position_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),
|
||||
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):
|
||||
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 🥝"
|
||||
elif get_refill.refill_method == "Yoomoney":
|
||||
pay_method = "ЮMoney 🔮"
|
||||
elif get_refill.refill_method == "Cryptobot":
|
||||
pay_method = "CryptoBot 🔷"
|
||||
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)
|
||||
|
||||
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):
|
||||
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
|
||||
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,
|
||||
)
|
||||
|
||||
get_items = await Itemx().gets(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):
|
||||
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
|
||||
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,
|
||||
)
|
||||
|
||||
get_items = await Itemx().gets(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_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":
|
||||
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(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>📁 Редактирование позиции</b>{hide_link(get_position.position_photo)}
|
||||
<b>📁 Редактирование позиции</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Категория: <code>{get_category.category_name}</code>
|
||||
▪️ Позиция: <code>{get_position.position_name}</code>
|
||||
▪️ Стоимость: <code>{get_position.position_price}₽</code>
|
||||
▪️ Количество: <code>{len(get_items)}шт</code>
|
||||
▪️ Дата создания: <code>{convert_date(get_category.category_unix)}</code>
|
||||
▪️ Изображение: {position_photo_text}
|
||||
▪️ Описание: {position_desc}
|
||||
|
||||
💸 Продаж за День: <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),
|
||||
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:
|
||||
refill_amount_all, refill_amount_day, refill_amount_week, refill_amount_month = 0, 0, 0, 0
|
||||
refill_count_all, refill_count_day, refill_count_week, refill_count_month = 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_cryptobot_amount = 0, 0
|
||||
refill_yoomoney_count, refill_yoomoney_amount = 0, 0
|
||||
refill_stars_count, refill_stars_amount = 0, 0
|
||||
refill_amount_all, refill_amount_day, refill_amount_week, refill_amount_month = (
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
refill_count_all, refill_count_day, refill_count_week, refill_count_month = (
|
||||
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_positions = await Positionx().get_all()
|
||||
@@ -340,12 +382,14 @@ async def get_statistics() -> str:
|
||||
refill_amount_all += refill.refill_amount
|
||||
refill_count_all += 1
|
||||
|
||||
if refill.refill_method == "Yoomoney":
|
||||
refill_yoomoney_count += 1
|
||||
refill_yoomoney_amount += refill.refill_amount
|
||||
elif refill.refill_method == "Cryptobot":
|
||||
if refill.refill_method == "Cryptobot":
|
||||
refill_cryptobot_count += 1
|
||||
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":
|
||||
refill_stars_count += 1
|
||||
refill_stars_amount += refill.refill_amount
|
||||
@@ -375,12 +419,28 @@ async def get_statistics() -> str:
|
||||
|
||||
# Даты обновления статистики
|
||||
all_days = [
|
||||
'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье',
|
||||
"Понедельник",
|
||||
"Вторник",
|
||||
"Среда",
|
||||
"Четверг",
|
||||
"Пятница",
|
||||
"Суббота",
|
||||
"Воскресенье",
|
||||
]
|
||||
|
||||
all_months = [
|
||||
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь',
|
||||
'Октябрь', 'Ноябрь', 'Декабрь'
|
||||
"Январь",
|
||||
"Февраль",
|
||||
"Март",
|
||||
"Апрель",
|
||||
"Май",
|
||||
"Июнь",
|
||||
"Июль",
|
||||
"Август",
|
||||
"Сентябрь",
|
||||
"Октябрь",
|
||||
"Ноябрь",
|
||||
"Декабрь",
|
||||
]
|
||||
|
||||
now_day = datetime.now().day
|
||||
@@ -388,12 +448,22 @@ async def get_statistics() -> str:
|
||||
now_month = datetime.now().month
|
||||
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)
|
||||
|
||||
week_day = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%d"))
|
||||
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())
|
||||
week_day = int(
|
||||
datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%d")
|
||||
)
|
||||
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"""
|
||||
<b>📊 СТАТИСТИКА БОТА</b>
|
||||
@@ -419,8 +489,8 @@ async def get_statistics() -> str:
|
||||
┃
|
||||
┣‒ Платежные системы (всего)
|
||||
┣ 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>
|
||||
┣ ЮMoney: <code>{refill_yoomoney_count}шт</code> - <code>{round(refill_yoomoney_amount, 2)}₽</code>
|
||||
┃
|
||||
┣‒ Остальные
|
||||
┣ Средств выдано: <code>{round(users_money_give, 2)}₽</code>
|
||||
|
||||
Reference in New Issue
Block a user