diff --git a/README.md b/README.md index 35404fa..ea807d1 100644 --- a/README.md +++ b/README.md @@ -27,31 +27,12 @@ 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 diff --git a/migrate.py b/migrate.py deleted file mode 100644 index fba8c8b..0000000 --- a/migrate.py +++ /dev/null @@ -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() diff --git a/migrations/env.py b/migrations/env.py deleted file mode 100644 index 385350e..0000000 --- a/migrations/env.py +++ /dev/null @@ -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()) diff --git a/migrations/script.py.mako b/migrations/script.py.mako deleted file mode 100644 index 590f5b3..0000000 --- a/migrations/script.py.mako +++ /dev/null @@ -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"} diff --git a/migrations/versions/20260529_0001_shop_schema.py b/migrations/versions/20260529_0001_shop_schema.py deleted file mode 100644 index 73bbb69..0000000 --- a/migrations/versions/20260529_0001_shop_schema.py +++ /dev/null @@ -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") diff --git a/migrations/versions/20260530_1915_492769059249_add_column_status_stars_into_storage_.py b/migrations/versions/20260530_1915_492769059249_add_column_status_stars_into_storage_.py deleted file mode 100644 index 5e543cc..0000000 --- a/migrations/versions/20260530_1915_492769059249_add_column_status_stars_into_storage_.py +++ /dev/null @@ -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 ### diff --git a/migrations/versions/20260530_1928_700275128e27_add_column_stars_course_into_storage_.py b/migrations/versions/20260530_1928_700275128e27_add_column_stars_course_into_storage_.py deleted file mode 100644 index ea3f605..0000000 --- a/migrations/versions/20260530_1928_700275128e27_add_column_stars_course_into_storage_.py +++ /dev/null @@ -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 ### diff --git a/migrations/versions/20260601_1934_36380377b0e6_add_column_misc_method_prod_into_.py b/migrations/versions/20260601_1934_36380377b0e6_add_column_misc_method_prod_into_.py deleted file mode 100644 index 5d36be1..0000000 --- a/migrations/versions/20260601_1934_36380377b0e6_add_column_misc_method_prod_into_.py +++ /dev/null @@ -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 ### diff --git a/pyproject.toml b/pyproject.toml index 69c96fa..f67cd4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,4 +28,3 @@ dependencies = [ [tool.setuptools.packages.find] include = ["tgbot*"] -exclude = ["migrations*"] diff --git a/tgbot/data/config.py b/tgbot/data/config.py index c8ec410..31bf2a0 100644 --- a/tgbot/data/config.py +++ b/tgbot/data/config.py @@ -28,16 +28,31 @@ 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") + 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") # Очистка строковых значений из окружения - @field_validator("bot_token", "timezone", "database_path", "logs_path", "yoomoney_client_id", mode="before") + @field_validator( + "bot_token", + "timezone", + "database_path", + "logs_path", + "yoomoney_client_id", + mode="before", + ) @classmethod def _strip_string(cls, value: object) -> str: return str(value or "").strip() @@ -52,11 +67,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 +83,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 +103,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 diff --git a/tgbot/database/db_position.py b/tgbot/database/db_position.py index ef71886..63adac1 100644 --- a/tgbot/database/db_position.py +++ b/tgbot/database/db_position.py @@ -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() + } diff --git a/tgbot/database/db_settings.py b/tgbot/database/db_settings.py index 826539b..3ac9810 100644 --- a/tgbot/database/db_settings.py +++ b/tgbot/database/db_settings.py @@ -14,20 +14,34 @@ 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) diff --git a/tgbot/database/entities.py b/tgbot/database/entities.py index 3033f7c..c9b97c4 100644 --- a/tgbot/database/entities.py +++ b/tgbot/database/entities.py @@ -18,7 +18,6 @@ class Position: position_name: str position_price: float position_desc: str - position_photo: str position_unix: int @@ -86,8 +85,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 diff --git a/tgbot/database/migration_runner.py b/tgbot/database/migration_runner.py index abb02fd..3d08120 100644 --- a/tgbot/database/migration_runner.py +++ b/tgbot/database/migration_runner.py @@ -12,13 +12,11 @@ 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 diff --git a/tgbot/database/repository.py b/tgbot/database/repository.py index 3256ef2..38f119d 100644 --- a/tgbot/database/repository.py +++ b/tgbot/database/repository.py @@ -6,7 +6,6 @@ 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.utils.misc.bot_logging import bot_logger ModelTranslator = TypeVar("ModelTranslator", bound=Base) @@ -151,7 +150,7 @@ class BaseRepository(Generic[ModelTranslator, EntityTranslator]): # Применение миграций и создание дефолтных строк async def prepare_database() -> None: database_path.parent.mkdir(parents=True, exist_ok=True) - await run_migrations() + # await run_migrations() from tgbot.database.db_payments import Paymentsx from tgbot.database.db_settings import Settingsx diff --git a/tgbot/keyboards/inline_admin.py b/tgbot/keyboards/inline_admin.py index 0b54e95..f509bb1 100644 --- a/tgbot/keyboards/inline_admin.py +++ b/tgbot/keyboards/inline_admin.py @@ -73,9 +73,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"), @@ -104,9 +110,15 @@ async def payment_yoomoney_finl() -> InlineKeyboardMarkup: assets_symbol = "➕" if get_payments.status_yoomoney == "True": - status_kb = ikb(f"{assets_symbol} | Статус: Включено ✅", data="payment_yoomoney_status:False") + status_kb = ikb( + f"{assets_symbol} | Статус: Включено ✅", + data="payment_yoomoney_status:False", + ) else: - status_kb = ikb(f"{assets_symbol} | Статус: Выключено ❌", data="payment_yoomoney_status:True") + status_kb = ikb( + f"{assets_symbol} | Статус: Выключено ❌", + data="payment_yoomoney_status:True", + ) keyboard.row( ikb("Информация ♻️", data="payment_yoomoney_check"), @@ -132,9 +144,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 +185,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": @@ -199,19 +208,20 @@ async def settings_finl() -> InlineKeyboardMarkup: method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip") 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, - ).row( - ikb("🎁 Позиции без товаров", data="..."), hide_position_kb, - ).row( - ikb("🎁 Метод добавления", data="..."), method_prod_kb, + ikb("🎁 Метод добавления", data="..."), + method_prod_kb, ) return keyboard.as_markup() @@ -227,7 +237,9 @@ async def settings_status_finl() -> InlineKeyboardMarkup: status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False") status_refill_kb = ikb("Включены ✅", data="settings_status_refill: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") @@ -238,18 +250,25 @@ async def settings_status_finl() -> InlineKeyboardMarkup: 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="..."), + notification_buy_kb, ).row( - ikb("📢 Увед. о пополнениях", data="..."), notification_refill_kb, + ikb("📢 Увед. о пополнениях", data="..."), + notification_refill_kb, ) return keyboard.as_markup() diff --git a/tgbot/keyboards/inline_admin_products.py b/tgbot/keyboards/inline_admin_products.py index 2901401..b62598e 100644 --- a/tgbot/keyboards/inline_admin_products.py +++ b/tgbot/keyboards/inline_admin_products.py @@ -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"t.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"t.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() diff --git a/tgbot/routers/admin/admin_products.py b/tgbot/routers/admin/admin_products.py index 3ad280f..d73921e 100644 --- a/tgbot/routers/admin/admin_products.py +++ b/tgbot/routers/admin/admin_products.py @@ -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( "❌ Название не может превышать 50 символов\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( "❌ Название не может превышать 50 символов\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( - "❌ Данные были введены неверно\n" - "📁 Введите цену для позиции", + "❌ Данные были введены неверно\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( - "📁 Отправьте новое изображение для позиции\n" - "❕ Отправьте 0 чтобы пропустить.", - 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( - "🧿 Отсутствует Дискорд вебхук, добавьте его в настройках" - ) - - position_photo = "None" - - if message.photo is not None: - cache_message = await message.answer( - "♻️ Подождите, фотография загружается..." - ) - - 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( "📁 Выберите позицию для изменения 🖍", - 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, 🎁 Отправляйте данные товаров ❗ Товары разделяются одной пустой строчкой. Пример: Данные товара... - + Данные товара... - + Данные товара... """), 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("⌛ Ждите, товары добавляются..") @@ -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""" ✅ Вы успешно удалили все категории 🗃 Категорий: {get_categories}шт 📁 Позиций: {get_positions}шт 🎁 Товаров: {get_items}шт - """) - ) + """)) # Удаление всех позиций @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""" ✅ Вы успешно удалили все позиции 📁 Позиций: {get_positions}шт 🎁 Товаров: {get_items}шт - """) - ) + """)) # Удаление всех товаров diff --git a/tgbot/routers/admin/admin_settings.py b/tgbot/routers/admin/admin_settings.py index b8ec83d..4f88298 100644 --- a/tgbot/routers/admin/admin_settings.py +++ b/tgbot/routers/admin/admin_settings.py @@ -5,7 +5,6 @@ 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.misc.bot_logging import bot_logger from tgbot.utils.misc.bot_models import FSM, ARS @@ -40,7 +39,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 +66,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 +93,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) @@ -113,7 +118,9 @@ async def settings_status_refill(call: CallbackQuery, bot: Bot, state: FSM, arSe # Включение/выключение уведомлений о покупках @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 +143,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 +174,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(""" ❔ Введите новый текст для FAQ ❕ Вы можете использовать заготовленный синтаксис и HTML разметку: ▪️ {username} - логин пользоваля ▪️ {user_id} - айди пользователя ▪️ {firstname} - имя пользователя - """) - ) + """)) # Изменение поддержки @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 +199,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 +214,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 +229,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,32 +242,11 @@ 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""" - 🧿 Отправьте новый вебхук дискорда - ❕ Для удаления вебхука введите 0 - ❕ Вы можете использовать публичный вебхук, но ответственность за его использование лежит только на вас - ▪️ Публичный вебхук: {get_discord_public_webhook} - """) - ) - - # Изменение текстового хостинга по умолчанию @router.callback_query(F.data == "settings_edit_hosting_text") -async def settings_edit_hosting_text(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): +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": @@ -282,8 +276,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( - "❌ Ошибка синтаксиса HTML\n" - "❔ Введите новый текст для FAQ", + "❌ Ошибка синтаксиса HTML\n" "❔ Введите новый текст для FAQ", ) await state.clear() @@ -310,67 +303,3 @@ async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession "🖍 Изменение данных бота", 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( - "⚙️ Настройки бота", - reply_markup=await settings_finl(), - ) - - # Добавление нового вебхука - cache_message = await message.answer("♻️ Проверка дискорд вебхука..") - - 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( - "⚙️ Настройки бота", - reply_markup=await settings_finl(), - ) - - # Обработка ошибки добавления вебхука - get_discord_public_webhook = await ( - DiscordDJ( - arSession=arSession, - bot=bot, - ) - ).export_webhook() - - await cache_message.edit_text( - ded(f""" - ❌ Указан некорректный вебхук - ➖➖➖➖➖➖➖➖➖➖ - 🧿 Отправьте новый вебхук дискорда - ❕ Для удаления вебхука введите 0 - ❕ Вы можете использовать публичный вебхук, но ответственность за его использование лежит только на вас - ▪️ Публичный вебхук: {get_discord_public_webhook} - """) - ) diff --git a/tgbot/routers/user/user_menu.py b/tgbot/routers/user/user_menu.py index 8060d9f..04016de 100644 --- a/tgbot/routers/user/user_menu.py +++ b/tgbot/routers/user/user_menu.py @@ -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) diff --git a/tgbot/services/api_discord.py b/tgbot/services/api_discord.py deleted file mode 100644 index e2a4408..0000000 --- a/tgbot/services/api_discord.py +++ /dev/null @@ -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"🧿 Дискорд вебхук недоступен. Как можно быстрее его замените\n" - f"❗️ Ошибка: {error_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'] diff --git a/tgbot/utils/const_functions.py b/tgbot/utils/const_functions.py index 34e468b..7795ea4 100644 --- a/tgbot/utils/const_functions.py +++ b/tgbot/utils/const_functions.py @@ -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) @@ -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") diff --git a/tgbot/utils/misc_functions.py b/tgbot/utils/misc_functions.py index 3fc7b2b..017dd4f 100644 --- a/tgbot/utils/misc_functions.py +++ b/tgbot/utils/misc_functions.py @@ -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""" ✅ Бот был успешно запущен ➖➖➖➖➖➖➖➖➖➖ - {get_text_desc()} - ➖➖➖➖➖➖➖➖➖➖ ❗ Данное сообщение видят только администраторы бота. """), ) @@ -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"📢 Рассылка началась... ({users_count}/{len(get_users)})") + await call.message.edit_text( + f"📢 Рассылка началась... ({users_count}/{len(get_users)})" + ) await asyncio.sleep(0.07) - await call.message.edit_text( - ded(f""" + await call.message.edit_text(ded(f""" 📢 Рассылка была завершена за {get_unix() - get_time}сек ➖➖➖➖➖➖➖➖➖➖ 👤 Всего пользователей: {len(get_users)} ✅ Пользователей получило сообщение: {users_receive} ❌ Пользователей не получило сообщение: {users_block} - """) - ) + """)) diff --git a/tgbot/utils/text_functions.py b/tgbot/utils/text_functions.py index 4634da4..7301eae 100644 --- a/tgbot/utils/text_functions.py +++ b/tgbot/utils/text_functions.py @@ -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""" - 🎁 Покупка товара{hide_link(get_position.position_photo)} + 🎁 Покупка товара ➖➖➖➖➖➖➖➖➖➖ ▪️ Название: {get_position.position_name} ▪️ Категория: {get_category.category_name} @@ -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,7 +117,7 @@ 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 🔮" @@ -138,7 +141,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 +177,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 +232,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 +252,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 = "Присутствует ✅" - else: - position_photo_text = "Отсутствует ❌" - # Наличие описания if get_position.position_desc != "None": position_desc = f"{get_position.position_desc}" @@ -257,14 +276,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""" - 📁 Редактирование позиции{hide_link(get_position.position_photo)} + 📁 Редактирование позиции ➖➖➖➖➖➖➖➖➖➖ ▪️ Категория: {get_category.category_name} ▪️ Позиция: {get_position.position_name} ▪️ Стоимость: {get_position.position_price}₽ ▪️ Количество: {len(get_items)}шт ▪️ Дата создания: {convert_date(get_category.category_unix)} - ▪️ Изображение: {position_photo_text} ▪️ Описание: {position_desc} 💸 Продаж за День: {profit_count_day}шт - {round(profit_amount_day, 2)}₽ @@ -274,7 +292,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,11 +320,38 @@ 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_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 @@ -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""" 📊 СТАТИСТИКА БОТА