diff --git a/main.py b/main.py
index b239ec1..4005909 100644
--- a/main.py
+++ b/main.py
@@ -6,9 +6,10 @@ import sys
import colorama
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
+from aiogram.enums import ParseMode
from tgbot.data.config import BOT_TOKEN, BOT_SCHEDULER, get_admins
-from tgbot.database.db_helper import create_dbx
+from tgbot.database.adb_helper import database_initialization
from tgbot.middlewares import register_all_middlwares
from tgbot.routers import register_all_routers
from tgbot.services.api_session import AsyncRequestSession
@@ -19,30 +20,31 @@ from tgbot.utils.misc_functions import autobackup_admin, startup_notify
colorama.init()
-# Запуск шедулеров
+# Start schedulers
async def scheduler_start(bot):
BOT_SCHEDULER.add_job(autobackup_admin, trigger="cron", hour=00, args=(bot,)) # Ежедневный Автобэкап в 00:00
-# Запуск бота и базовых функций
+# Start bot and basic functions
async def main():
- BOT_SCHEDULER.start() # Запуск Шедулера
- dp = Dispatcher() # Образ Диспетчера
- arSession = AsyncRequestSession() # Пул асинхронной сессии запросов
+ BOT_SCHEDULER.start() # Start scheduler
+ dp = Dispatcher() # Dispatcher image
+ arSession = AsyncRequestSession() # Async session pool (aiohttp)
+
bot = Bot( # Образ Бота
token=BOT_TOKEN,
default=DefaultBotProperties(
- parse_mode="HTML",
+ parse_mode=ParseMode.HTML
),
)
- register_all_middlwares(dp) # Регистрация всех мидлварей
- register_all_routers(dp) # Регистрация всех роутеров
+ register_all_middlwares(dp) # Register all middlewares
+ register_all_routers(dp) # Register all routers
try:
- await set_commands(bot) # Установка пользовательских команд в боте
- await startup_notify(bot) # Уведомление админов о запуске бота
- await scheduler_start(bot) # Подключение шедулеров
+ await set_commands(bot) # Set commands for users
+ await startup_notify(bot) # Notification that bot was started
+ await scheduler_start(bot) # Connect schedulers
bot_logger.warning("BOT WAS STARTED")
print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Bot was started - @{(await bot.get_me()).username} ~~~~~")
@@ -51,31 +53,25 @@ async def main():
if len(get_admins()) == 0: print("***** ENTER ADMIN ID IN settings.ini *****")
- await bot.delete_webhook() # Удаление вебхуков, если они имеются
- await bot.get_updates(offset=-1) # Сброс пендинг апдейтов
+ await bot.delete_webhook() # Deletes webhooks, if they was have
+ await bot.get_updates(offset=-1) # Reset update pengings
- # Запуск бота (поллинга)
+ # Run bot (polling method)
await dp.start_polling(
bot,
arSession=arSession,
allowed_updates=dp.resolve_used_update_types(),
)
finally:
- await arSession.close() # Закрытие Асинхронной Сессии для прочих запросов
- await bot.session.close() # Закрытие сессии бота
+ await arSession.close() # Close async session (aiohttp)
+ await bot.session.close() # Close bot session
if __name__ == "__main__":
- create_dbx() # Генерация Базы Данных и Таблиц
+ database_initialization() # Initializate Database, tables and columns
try:
- if sys.platform == 'win32': # Запуск на 32-х битных системах
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
- loop = asyncio.ProactorEventLoop()
- asyncio.set_event_loop(loop)
- loop.run_until_complete(main())
- else:
- asyncio.run(main()) # Запуск на всех других системах
+ asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
bot_logger.warning("Bot was stopped")
finally:
diff --git a/requirements.txt b/requirements.txt
index 5169ac2..ffc88b3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,8 @@
-APScheduler==3.10.4
-aiogram==3.5.0
-colorlog==6.8.2
-pytz==2024.1
-typing~=3.7.4.3
-aiohttp~=3.9.1
-cachetools==5.3.3
-colorama~=0.4.5
-aiofiles~=23.2.1
-pydantic~=2.5.2
\ No newline at end of file
+APScheduler==3.11.0
+aiogram==3.22.0
+colorlog==6.9.0
+pytz==2025.2
+DateTime==5.5
+typing==3.7.4.3
+aiofiles==25.1.0
+colorama==0.4.6
\ No newline at end of file
diff --git a/tgbot/database/__init__.py b/tgbot/database/__init__.py
index e69de29..026e5ee 100644
--- a/tgbot/database/__init__.py
+++ b/tgbot/database/__init__.py
@@ -0,0 +1,2 @@
+from .db_settings import Settingsx, ModelBase as ModelSettings
+from .db_users import Usersx, ModelBase as ModelUsers
diff --git a/tgbot/database/adb_helper.py b/tgbot/database/adb_helper.py
new file mode 100644
index 0000000..f0ec111
--- /dev/null
+++ b/tgbot/database/adb_helper.py
@@ -0,0 +1,494 @@
+# - *- coding: utf- 8 - *-
+import sqlite3
+from typing import Any, Generic, TypeVar, Optional
+
+from pydantic.fields import FieldInfo
+from pydantic_core import PydanticUndefinedType
+
+from tgbot.data.config import PATH_DATABASE
+from tgbot.database.adb_migration import CONST_MIGRATE_TABLES, CONST_MIGRATE_COLUMNS
+from tgbot.utils.const_functions import ded
+
+
+# noinspection PyProtectedMember
+
+################################################################################
+############################### SUPPORT FUNCTIONS ##############################
+# Converting the resulting list into a dictionary
+def dict_factory(cursor, row) -> dict:
+ save_dict = {}
+
+ for idx, col in enumerate(cursor.description):
+ save_dict[col[0]] = row[idx]
+
+ return save_dict
+
+
+# Formatting a query without arguments
+def update_format(sql, parameters: dict) -> tuple[str, list]:
+ values = ", ".join([
+ f"{item} = ?" for item in parameters
+ ])
+ sql += f" {values}"
+
+ return sql, list(parameters.values())
+
+
+# Formatting a query with arguments
+def update_format_where(sql, parameters: dict) -> tuple[str, list]:
+ sql += " WHERE "
+
+ sql += " AND ".join([
+ f"{item} = ?" for item in parameters
+ ])
+
+ return sql, list(parameters.values())
+
+
+################################################################################
+################# BASIC CLASS FOR WORKING WITH DATABASE TABLES #################
+ModelTranslator = TypeVar("ModelTranslator", bound="ModelBase")
+
+
+class Databasex(Generic[ModelTranslator]):
+ def __init__(self):
+ self.storage_name = "storage"
+ self.column_one = False
+ self.table_model = None
+
+ # Delete entry
+ def delete(self, **kwargs):
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+ sql = f"DELETE FROM {self.storage_name}"
+ sql, parameters = update_format_where(sql, kwargs)
+
+ con.execute(sql, parameters)
+
+ # Clear all entries
+ def clear(self):
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+ sql = f"DELETE FROM {self.storage_name}"
+
+ con.execute(sql)
+
+ # Get entry
+ def get(self, **kwargs) -> Optional[ModelTranslator]:
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+ sql = f"SELECT * FROM {self.storage_name}"
+ sql, parameters = update_format_where(sql, kwargs)
+
+ response = con.execute(sql, parameters).fetchone()
+
+ if response is not None:
+ response = self.table_model(**response)
+
+ return response
+
+ # Get entries
+ def gets(self, **kwargs) -> list[Optional[ModelTranslator]]:
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+ sql = f"SELECT * FROM {self.storage_name}"
+ sql, parameters = update_format_where(sql, kwargs)
+
+ response = con.execute(sql, parameters).fetchall()
+
+ return [self.table_model(**cache_object) for cache_object in response]
+
+ # Get all entries
+ def get_all(self) -> list[Optional[ModelTranslator]]:
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+ sql = f"SELECT * FROM {self.storage_name}"
+
+ response = con.execute(sql).fetchall()
+
+ return [self.table_model(**cache_object) for cache_object in response]
+
+ # Edit entry
+ def update(self, **kwargs):
+ ...
+
+ # Export column names, default data, and type hints
+ def fields(self) -> dict[str, list[dict[str, Any]]]:
+ for field_name, field_data in self.table_model.model_fields.items():
+ field_name: str
+ field_data: FieldInfo
+
+ column_data = [
+ {
+ 'column': field_name,
+ 'type': field_data.annotation,
+ 'value': field_data.default,
+ } for field_name, field_data in self.table_model.model_fields.items()
+ ]
+
+ return {'column_data': column_data, 'column_one': self.column_one}
+
+
+################################################################################
+################################################################################
+# Export all tables from the Database to a list
+def database_export_fields_db() -> list:
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+
+ database_tables = con.execute("SELECT * FROM sqlite_master where type='table'").fetchall()
+ database_tables_db = [table['name'] for table in database_tables]
+
+ # If there are service tables, remove them from the list of available tables.
+ if "sqlite_sequence" in database_tables_db:
+ database_tables_db.remove("sqlite_sequence")
+ if "sqlite_master" in database_tables_db:
+ database_tables_db.remove("sqlite_master")
+
+ return database_tables_db
+
+
+# Export all tables, columns, and typehints from structures
+def database_export_fields_structure() -> dict:
+ # Import all table models
+ import tgbot.database as import_modeles
+
+ # Storing all structure files (db_X.py) in a single list
+ all_tables = [
+ getattr(import_modeles, name)
+ for name in dir(import_modeles)
+ if (
+ isinstance(getattr(import_modeles, name), type) and
+ (name.istitle() and name.endswith("x"))
+ )
+ ]
+
+ save_tables = {} # Storage of all sorted and converted tables/columns
+
+ # Iterating through tables and columns
+ for table in all_tables:
+ table_fields = table().fields()
+
+ column_data = table_fields['column_data']
+ column_one = table_fields['column_one']
+
+ cache_columns = []
+
+ # If the table name is not in the dictionary, add it.
+ if table().storage_name not in save_tables:
+ save_tables[table().storage_name] = {
+ 'column_one': column_one,
+ 'column_datas': [],
+ }
+
+ # Column sorting
+ for table_field in column_data:
+ column_name = table_field['column']
+ column_type = table_field['type'].__name__
+ column_value = table_field['value']
+
+ # Replacing python date types with SQL
+ column_type = column_type.replace(
+ "str", "TEXT",
+ ).replace(
+ "int", "INTEGER",
+ ).replace(
+ "float", "REAL",
+ )
+
+ # If there is no default value, replace it with - None
+ if isinstance(column_value, PydanticUndefinedType):
+ column_value = None
+
+ # Default values based on type hints
+ if column_type == "TEXT":
+ if "status" in column_name.lower():
+ column_value = "False"
+ else:
+ column_value = "None"
+ elif column_type == "INTEGER":
+ column_value = 0
+ elif column_type == "REAL":
+ column_value = 0.0
+ else:
+ column_value = table_field['value']
+
+ save_tables[table().storage_name]['column_datas'].append(
+ {
+ 'column': column_name,
+ 'type': column_type,
+ 'value': column_value,
+ }
+ )
+
+ return save_tables
+
+
+################################################################################
+################################################################################
+# Database migration
+def database_migration():
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+
+ # If there are values for tables or columns, perform migration.
+ if len(CONST_MIGRATE_TABLES) > 0 or len(CONST_MIGRATE_COLUMNS) > 0:
+ print(f"|| Migrating | Data was found")
+
+ # Export existing tables in the Database
+ database_tables_db = database_export_fields_db()
+
+ # Renaming tables - iterating through tables
+ for table_name_later, table_name_new in CONST_MIGRATE_TABLES.items():
+ if table_name_later in database_tables_db:
+ print(f"|| Migrating | Table '{table_name_later}' renamed to '{table_name_new}' (✓)")
+ con.execute(f"ALTER TABLE {table_name_later} RENAME TO {table_name_new}")
+ else:
+ print(f"|| Migrating | Table '{table_name_later}' for renamed not found (X)")
+
+ # Renaming columns - iterating through tables with columns inside
+ for table_name in CONST_MIGRATE_COLUMNS:
+ if table_name in database_tables_db:
+ # Unloading existing columns from the current table
+ columns_export = [
+ column['name'] for column in con.execute(f"PRAGMA table_info({table_name})").fetchall()
+ ]
+
+ # Browsing columns to rename them
+ for column_name_later, column_name_new in CONST_MIGRATE_COLUMNS[table_name].items():
+ if column_name_later in columns_export:
+ print(
+ f"|| Migrating | Column '{column_name_later}' renamed to '{column_name_new}' from table '{table_name}' (✓)"
+ )
+ con.execute(
+ f"ALTER TABLE {table_name} RENAME COLUMN {column_name_later} TO {column_name_new}"
+ )
+ else:
+ print(
+ f"|| Migrating | Column '{column_name_later}' from table '{table_name}' not found (X)"
+ )
+ else:
+ print(f"|| Migrating | Table '{table_name}' for renamed column(s) not found (X)")
+
+ else:
+ print(f"|| Migrating | Data not found")
+
+ print(f"||")
+
+
+# Checking the relevance of columns (adding new ones, deleting old ones)
+def database_check_columns():
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+
+ symbol_log = True
+
+ # Exporting tables and columns from structures
+ fields_structure = database_export_fields_structure()
+
+ for number, field in enumerate(fields_structure.items()):
+ table_name: str = field[0] # Table name
+ table_data: dict = field[1] # Table data
+
+ column_datas = table_data['column_datas'] # Column name, type, and value
+
+ columns_cache_add = [] # Columns that will be added to the database
+ columns_cache_edit_columns = [] # Columns whose date types will be changed in the database
+ columns_cache_edit_data = None # Which table columns will change date types in the database?
+
+ # Unloading existing columns from the current table
+ column_datas_types_db = [column for column in con.execute(f"PRAGMA table_info({table_name})").fetchall()]
+
+ # Unloading existing column names from the current table
+ columns_export_db = [column['name'] for column in column_datas_types_db]
+
+ # Iterating through columns from a structure
+ for column_structure in column_datas:
+ # If a column from the structure is present in the database table
+ if column_structure['column'] in columns_export_db:
+ columns_export_db.remove(column_structure['column'])
+
+ # Date type of the current column in the Structure
+ column_type_structure = column_structure['type']
+
+ # Date type of the current column in the database
+ column_type_database = [
+ column for column in con.execute(f"PRAGMA table_info({table_name})").fetchall() if
+ column_structure['column'] == column['name']
+ ]
+
+ # If the date column type (DB) is in the list, extract it.
+ if len(column_type_database) > 0:
+ column_type_database = column_type_database[0]['type']
+
+ # If the date types differ and have not been saved before, add them to the dictionary.
+ if column_type_structure != column_type_database:
+ if columns_cache_edit_data is None:
+ columns_cache_edit_data = column_datas
+
+ if column_structure['column'] not in columns_cache_edit_columns:
+ columns_cache_edit_columns.append(
+ f"{column_structure['column']}: {column_type_database} -> {column_type_structure} "
+ )
+ else:
+ # If a column from the structure is not present in the database, save it for addition.
+ columns_cache_add.append(column_structure)
+
+ # The remaining columns that are present in the database table but are missing from the structure
+ columns_cache_delete = columns_export_db
+
+ # Adding new columns
+ for column in columns_cache_add:
+ print(f"|| Modification | Column '{column['column']}' added to table '{table_name}'")
+ con.execute(
+ f"ALTER TABLE {table_name} ADD COLUMN {column['column']} {column['type']} DEFAULT '{column['value']}'"
+ )
+
+ # Deleting existing obsolete columns
+ for column in columns_cache_delete:
+ print(f"|| Modification | Column '{column}' deleted from table '{table_name}'")
+ con.execute(f"ALTER TABLE {table_name} DROP COLUMN {column}")
+
+ # Changing the date types of columns in a table
+ sql_edit_columns_create_table = []
+ sql_edit_columns_insert_values = []
+ columns_cache_edit_columns = ", ".join(columns_cache_edit_columns)
+
+ if columns_cache_edit_data is not None:
+ for column in columns_cache_edit_data:
+ if column['type'] == "TEXT":
+ sql_edit_columns_create_table.append(
+ f"{column['column']} {column['type']} DEFAULT '{column['value']}'"
+ )
+ else:
+ sql_edit_columns_create_table.append(
+ f"{column['column']} {column['type']} DEFAULT {column['value']}"
+ )
+
+ sql_edit_columns_insert_values.append(column['column'])
+
+ sql_edit_columns_create_table = ", ".join(sql_edit_columns_create_table)
+ sql_edit_columns_create_table = f"CREATE TABLE {table_name}_new ({sql_edit_columns_create_table})"
+
+ sql_edit_columns_insert_values = ", ".join(sql_edit_columns_insert_values)
+ sql_edit_columns_insert_values = f"INSERT INTO {table_name}_new ({sql_edit_columns_insert_values}) SELECT {sql_edit_columns_insert_values} FROM {table_name}"
+
+ con.execute(sql_edit_columns_create_table) # Creating a new table
+ con.execute(sql_edit_columns_insert_values) # Transferring data from old columns to new ones
+ con.execute(f"DROP TABLE {table_name};")
+ con.execute(f"ALTER TABLE {table_name}_new RENAME TO {table_name};")
+
+ print(
+ f"|| Modification | Columns '{columns_cache_edit_columns}' was edit data type for table '{table_name}'"
+ )
+
+
+# Creating/deleting tables, columns, and records
+def database_initialization():
+ print("======== DATABASE PROCESSING ========")
+ database_migration() # Migration check
+
+ with sqlite3.connect(PATH_DATABASE) as con:
+ con.row_factory = dict_factory
+
+ # List of exported tables and columns from structures
+ database_fields = database_export_fields_structure()
+
+ # List of exported tables from the structure
+ database_tables_import = [table for table in database_fields.keys()]
+
+ # List of tables available in the Database
+ database_tables_db = database_export_fields_db()
+
+ # List of tables to be deleted that are not present in imports but are present in the database
+ database_tables_delete = [
+ table for table in database_tables_db if table not in database_tables_import
+ ]
+
+ # Browse the list of imported tables, columns, and default records to create them.
+ for number, db_field in enumerate(database_fields.items()):
+ table_name: str = db_field[0] # Table name
+ table_data: dict = db_field[1] # Table data
+
+ column_one = table_data['column_one'] # Will the table have one record or more
+ column_datas = table_data['column_datas'] # Column name, data type, and value
+ column_len = len(column_datas) # Length (number) of all columns in the table
+
+ # Checking for the required number of records in a table
+ if len(con.execute(f"PRAGMA table_info({table_name})").fetchall()) == column_len:
+ print(f"|| Creating | Table '{table_name}' was found ({number + 1}/{len(database_fields)})")
+ else:
+ print(f"|| Creating | Table '{table_name}' not found ({number + 1}/{len(database_fields)})")
+
+ # Column names with type hints (user_id INTEGER, user_name TEXT)
+ columns_cache_create = []
+
+ # Column names without type hints (user_id, user_name)
+ columns_cache_insert = []
+
+ # Default data for automatic entry into columns (settings, payment systems)
+ columns_values = []
+
+ ############################################################
+ ##################### CREATING TABLES ######################
+ for column in column_datas:
+ column_name = column['column'] # Table name
+ column_value = column['value'] # Default value in column
+ column_type = column['type'] # Entry data type (INTEGER, TEXT, REAL, ...)
+
+ # Incrementable entry check
+ if column_name == "increment":
+ column_execute_create = "increment INTEGER PRIMARY KEY AUTOINCREMENT"
+ else:
+ column_execute_create = f"{column_name} {column_type}"
+
+ columns_cache_insert.append(column_name)
+ columns_cache_create.append(column_execute_create)
+
+ # If default data is available, add it.
+ if column_one and column_value is not None:
+ columns_values.append(column_value)
+
+ # Collecting a request with all added columns
+ column_execute_create = ",\n".join(columns_cache_create)
+ column_execute_create = ded(f"""
+ CREATE TABLE IF NOT EXISTS {table_name}(
+ {column_execute_create}
+ )
+ """)
+
+ # Request to create a table and columns
+ con.execute(column_execute_create)
+
+ ############################################################
+ ####################### ADDING ENTRY #######################
+ # If there is a record to add and the table supports only one record
+ if column_one and len(columns_values) > 0:
+ column_check_have = con.execute(f"SELECT * FROM {table_name}").fetchall()
+
+ # If there is no entry in the table, add it.
+ if len(column_check_have) == 0:
+ # Request collection
+ column_execute_insert = ",\n".join(columns_cache_insert)
+ column_execute_insert = ded(f"""
+ INSERT INTO {table_name}(
+ {column_execute_insert}
+ )
+ VALUES ({"?," * (len(columns_cache_insert) - 1) + "?"})
+ """)
+
+ # Request to add entries to the table
+ con.execute(column_execute_insert, columns_values)
+
+ print(f"|| Creating | Table '{table_name}' was created ({number + 1}/{len(database_fields)})")
+
+ # If there are tables to be deleted, delete them.
+ if len(database_tables_delete) > 0:
+ for table_name in database_tables_delete:
+ print(f"|| Database | Table '{table_name}' was deleted")
+ con.execute(f"DROP TABLE {table_name}")
+
+ print("||")
+ database_check_columns() # Checking the relevance of columns
+ print("=====================================")
+ print()
diff --git a/tgbot/database/adb_migration.py b/tgbot/database/adb_migration.py
new file mode 100644
index 0000000..7b4961c
--- /dev/null
+++ b/tgbot/database/adb_migration.py
@@ -0,0 +1,36 @@
+# Example with migration - Tables
+#
+# CONST_MIGRATE_TABLES = {
+# 'old_table_name1': 'new_table_name1',
+# 'old_table_name2': 'new_table_name2',
+# }
+
+
+# Example with migration - Columns
+# CONST_MIGRATE_COLUMNS = {
+# 'name_of_the_table_in_which_the_column_is_located1': {
+# 'old_column_name11': 'new_column_name11',
+# 'old_column_name12': 'new_column_name12',
+# },
+# 'name_of_the_table_in_which_the_column_is_located2': {
+# 'old_column_name21': 'new_column_name21',
+# 'old_column_name22': 'new_column_name22',
+# },
+# }
+
+
+################################################################################
+############################### MIGRATION TABLES ###############################
+CONST_MIGRATE_TABLES = {
+ # 'storage_refillme': 'storage_refill',
+ # 'storage_purchasesme': 'storage_purchases',
+}
+
+################################################################################
+############################### MIGRATION COLUMNS ##############################
+CONST_MIGRATE_COLUMNS = {
+ # 'storage_refill': {
+ # 'refill_methodme': 'refill_method',
+ # 'refill_amountme': 'refill_amount',
+ # },
+}
diff --git a/tgbot/database/db_helper.py b/tgbot/database/db_helper.py
deleted file mode 100644
index f8c0620..0000000
--- a/tgbot/database/db_helper.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# - *- coding: utf- 8 - *-
-import sqlite3
-
-from tgbot.data.config import PATH_DATABASE
-from tgbot.utils.const_functions import ded
-
-
-# Преобразование полученного списка в словарь
-def dict_factory(cursor, row) -> dict:
- save_dict = {}
-
- for idx, col in enumerate(cursor.description):
- save_dict[col[0]] = row[idx]
-
- return save_dict
-
-
-# Форматирование запроса без аргументов
-def update_format(sql, parameters: dict) -> tuple[str, list]:
- values = ", ".join([
- f"{item} = ?" for item in parameters
- ])
- sql += f" {values}"
-
- return sql, list(parameters.values())
-
-
-# Форматирование запроса с аргументами
-def update_format_where(sql, parameters: dict) -> tuple[str, list]:
- sql += " WHERE "
-
- sql += " AND ".join([
- f"{item} = ?" for item in parameters
- ])
-
- return sql, list(parameters.values())
-
-
-################################################################################
-# Создание всех таблиц для БД
-def create_dbx():
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
-
- ############################################################
- # Создание таблицы с хранением - пользователей
- if len(con.execute("PRAGMA table_info(storage_users)").fetchall()) == 7:
- print("DB was found(1/2)")
- else:
- con.execute(
- ded(f"""
- CREATE TABLE storage_users(
- increment INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER,
- user_login TEXT,
- user_name TEXT,
- user_surname TEXT,
- user_fullname TEXT,
- user_unix INTEGER
- )
- """)
- )
- print("DB was not found(1/2) | Creating...")
-
- # Создание таблицы с хранением - настроек
- if len(con.execute("PRAGMA table_info(storage_settings)").fetchall()) == 1:
- print("DB was found(2/2)")
- else:
- con.execute(
- ded(f"""
- CREATE TABLE storage_settings(
- status_work TEXT
- )
- """)
- )
-
- con.execute(
- ded(f"""
- INSERT INTO storage_settings(
- status_work
- )
- VALUES (?)
- """),
- [
- 'True',
- ]
- )
- print("DB was not found(2/2) | Creating...")
diff --git a/tgbot/database/db_settings.py b/tgbot/database/db_settings.py
index 8f14d52..5e0d103 100644
--- a/tgbot/database/db_settings.py
+++ b/tgbot/database/db_settings.py
@@ -4,33 +4,40 @@ import sqlite3
from pydantic import BaseModel
from tgbot.data.config import PATH_DATABASE
-from tgbot.database.db_helper import dict_factory, update_format
+from tgbot.database.adb_helper import Databasex, dict_factory, update_format
-# Модель таблицы
-class SettingsModel(BaseModel):
- status_work: str # Статус работы бота
+# Table model
+class ModelBase(BaseModel):
+ status_work: str # Status bot work
+
+ class Config:
+ json_schema_extra = {
+ 'storage_name': 'storage_settings'
+ }
-# Работа с настройками
-class Settingsx:
- storage_name = "storage_settings"
+# Settings
+class Settingsx(Databasex[ModelBase]):
+ def __init__(self):
+ super().__init__()
+ self.table_model = ModelBase
+ self.storage_name = self.table_model.model_json_schema()['storage_name']
+ self.column_one = True
- # Получение записи
- @staticmethod
- def get() -> SettingsModel:
+ # Get entry
+ def get(self) -> ModelBase:
with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory
- sql = f"SELECT * FROM {Settingsx.storage_name}"
+ sql = f"SELECT * FROM {self.storage_name}"
- return SettingsModel(**con.execute(sql).fetchone())
+ return ModelBase(**con.execute(sql).fetchone())
- # Редактирование записи
- @staticmethod
- def update(**kwargs):
+ # Edit entry
+ def update(self, **kwargs):
with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory
- sql = f"UPDATE {Settingsx.storage_name} SET"
+ sql = f"UPDATE {self.storage_name} SET"
sql, parameters = update_format(sql, kwargs)
con.execute(sql, parameters)
diff --git a/tgbot/database/db_users.py b/tgbot/database/db_users.py
index c2cd730..e8713a4 100644
--- a/tgbot/database/db_users.py
+++ b/tgbot/database/db_users.py
@@ -4,28 +4,37 @@ import sqlite3
from pydantic import BaseModel
from tgbot.data.config import PATH_DATABASE
-from tgbot.database.db_helper import dict_factory, update_format_where, update_format
+from tgbot.database.adb_helper import Databasex, dict_factory, update_format
from tgbot.utils.const_functions import get_unix, ded
-# Модель таблицы
-class UserModel(BaseModel):
- increment: int # Инкремент записи
- user_id: int # Айди пользователя
- user_login: str # Юзернейм пользователя
- user_name: str # Имя пользователя
- user_surname: str # Фамилия пользователя
- user_fullname: str # Полное имя + фамилия пользователя
- user_unix: int # Дата регистрации пользователя в боте (в UNIX времени)
+# Table model
+class ModelBase(BaseModel):
+ increment: int # increment
+ user_id: int # User id
+ user_login: str # User username
+ user_name: str # User name
+ user_surname: str # User surname
+ user_fullname: str # User fullname (name + surname)
+ user_unix: int # Date of user registration in the bot (in UNIX time)
+
+ class Config:
+ json_schema_extra = {
+ 'storage_name': 'storage_users'
+ }
-# Работа с юзером
-class Userx:
- storage_name = "storage_users"
+# Users
+class Usersx(Databasex[ModelBase]):
+ def __init__(self):
+ super().__init__()
+ self.table_model = ModelBase
+ self.storage_name = self.table_model.model_json_schema()['storage_name']
+ self.column_one = False
- # Добавление записи
- @staticmethod
+ # Add entry
def add(
+ self,
user_id: int,
user_login: str,
user_name: str,
@@ -39,7 +48,7 @@ class Userx:
con.execute(
ded(f"""
- INSERT INTO {Userx.storage_name} (
+ INSERT INTO {self.storage_name} (
user_id,
user_login,
user_name,
@@ -58,76 +67,12 @@ class Userx:
],
)
- # Получение записи
- @staticmethod
- def get(**kwargs) -> UserModel:
+ # Edit entry
+ def update(self, user_id: int, **kwargs):
with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory
- sql = f"SELECT * FROM {Userx.storage_name}"
- sql, parameters = update_format_where(sql, kwargs)
-
- response = con.execute(sql, parameters).fetchone()
-
- if response is not None:
- response = UserModel(**response)
-
- return response
-
- # Получение записей
- @staticmethod
- def gets(**kwargs) -> list[UserModel]:
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
- sql = f"SELECT * FROM {Userx.storage_name}"
- sql, parameters = update_format_where(sql, kwargs)
-
- response = con.execute(sql, parameters).fetchall()
-
- if len(response) >= 1:
- response = [UserModel(**cache_object) for cache_object in response]
-
- return response
-
- # Получение всех записей
- @staticmethod
- def get_all() -> list[UserModel]:
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
- sql = f"SELECT * FROM {Userx.storage_name}"
-
- response = con.execute(sql).fetchall()
-
- if len(response) >= 1:
- response = [UserModel(**cache_object) for cache_object in response]
-
- return response
-
- # Редактирование записи
- @staticmethod
- def update(user_id, **kwargs):
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
- sql = f"UPDATE {Userx.storage_name} SET"
+ sql = f"UPDATE {self.storage_name} SET"
sql, parameters = update_format(sql, kwargs)
parameters.append(user_id)
con.execute(sql + "WHERE user_id = ?", parameters)
-
- # Удаление записи
- @staticmethod
- def delete(**kwargs):
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
- sql = f"DELETE FROM {Userx.storage_name}"
- sql, parameters = update_format_where(sql, kwargs)
-
- con.execute(sql, parameters)
-
- # Очистка всех записей
- @staticmethod
- def clear():
- with sqlite3.connect(PATH_DATABASE) as con:
- con.row_factory = dict_factory
- sql = f"DELETE FROM {Userx.storage_name}"
-
- con.execute(sql)
diff --git a/tgbot/keyboards/inline_main.py b/tgbot/keyboards/inline_main.py
index 5f4dac6..b204771 100644
--- a/tgbot/keyboards/inline_main.py
+++ b/tgbot/keyboards/inline_main.py
@@ -2,25 +2,34 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
-from tgbot.data.config import get_admins
from tgbot.utils.const_functions import ikb
-# Кнопки инлайн меню
-def menu_finl(user_id: int) -> InlineKeyboardMarkup:
+# Inline keyboards for users
+def user_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder()
keyboard.row(
ikb("User X", data="user_inline_x"),
ikb("User 1", data="user_inline:user_btn"),
ikb("User 2", data="..."),
+ ).row(
+ ikb("User Unknown", data="unknown"),
)
- if user_id in get_admins():
- keyboard.row(
- ikb("Admin X", data="admin_inline_x"),
- ikb("Admin 1", data="admin_inline:admin_btn"),
- ikb("Admin 2", data="unknown"),
- )
+ return keyboard.as_markup()
+
+
+# Inline keyboards for admins
+def admin_finl() -> InlineKeyboardMarkup:
+ keyboard = InlineKeyboardBuilder()
+
+ keyboard.row(
+ ikb("Admin X", data="admin_inline_x"),
+ ikb("Admin 1", data="admin_inline:admin_btn"),
+ ikb("Admin 2", data="..."),
+ ).row(
+ ikb("Admin Unknown", data="unknown"),
+ )
return keyboard.as_markup()
diff --git a/tgbot/keyboards/inline_misc.py b/tgbot/keyboards/inline_misc.py
deleted file mode 100644
index 73aa71c..0000000
--- a/tgbot/keyboards/inline_misc.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# - *- coding: utf- 8 - *-
-from aiogram.utils.keyboard import InlineKeyboardBuilder
-
-from tgbot.utils.const_functions import ikb
-
-# Тестовые админ инлайн кнопки
-admin_inl = InlineKeyboardBuilder(
-).row(
- ikb("Admin Inline 1", data="..."),
- ikb("Admin Inline 2", data="..."),
-).row(
- ikb("Admin Inline 3", data="..."),
-).as_markup()
-
-# Тестовые юзер инлайн кнопки
-user_inl = InlineKeyboardBuilder(
-).row(
- ikb("User Inline 1", data="..."),
- ikb("User Inline 2", data="..."),
-).row(
- ikb("User Inline 3", data="..."),
-).as_markup()
diff --git a/tgbot/keyboards/reply_main.py b/tgbot/keyboards/reply_main.py
index 2d00caf..dd19eef 100644
--- a/tgbot/keyboards/reply_main.py
+++ b/tgbot/keyboards/reply_main.py
@@ -11,12 +11,12 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder()
keyboard.row(
- rkb("User Inline"), rkb("User Reply"),
+ rkb("User button"),
)
if user_id in get_admins():
keyboard.row(
- rkb("Admin Inline"), rkb("Admin Reply"),
+ rkb("Admin button"),
)
return keyboard.as_markup(resize_keyboard=True)
diff --git a/tgbot/keyboards/reply_misc.py b/tgbot/keyboards/reply_misc.py
deleted file mode 100644
index 7dd1c22..0000000
--- a/tgbot/keyboards/reply_misc.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# - *- coding: utf- 8 - *-
-from aiogram.utils.keyboard import ReplyKeyboardBuilder
-
-from tgbot.utils.const_functions import rkb
-
-# Тестовые админ реплай кнопки
-admin_rep = ReplyKeyboardBuilder(
-).row(
- rkb("Admin Reply 1"),
- rkb("Admin Reply 2"),
-).row(
- rkb("🔙 Main menu"),
-).as_markup(resize_keyboard=True)
-
-# Тестовые юзер реплай кнопки
-user_rep = ReplyKeyboardBuilder(
-).row(
- rkb("User Reply 1"),
- rkb("User Reply 2"),
-).row(
- rkb("🔙 Main menu"),
-).as_markup(resize_keyboard=True)
diff --git a/tgbot/middlewares/__init__.py b/tgbot/middlewares/__init__.py
index 7602a1d..9ca5772 100644
--- a/tgbot/middlewares/__init__.py
+++ b/tgbot/middlewares/__init__.py
@@ -1,11 +1,11 @@
# - *- coding: utf- 8 - *-
from aiogram import Dispatcher
-from tgbot.middlewares.middleware_user import ExistsUserMiddleware
from tgbot.middlewares.middleware_throttling import ThrottlingMiddleware
+from tgbot.middlewares.middleware_user import ExistsUserMiddleware
-# Регистрация всех миддлварей
+# Register all middlewares
def register_all_middlwares(dp: Dispatcher):
dp.callback_query.outer_middleware(ExistsUserMiddleware())
dp.message.outer_middleware(ExistsUserMiddleware())
diff --git a/tgbot/middlewares/middleware_throttling.py b/tgbot/middlewares/middleware_throttling.py
index 8130c5d..2d27667 100644
--- a/tgbot/middlewares/middleware_throttling.py
+++ b/tgbot/middlewares/middleware_throttling.py
@@ -8,7 +8,7 @@ from aiogram.types import Message, User
from cachetools import TTLCache
-# Антиспам
+# Antiflood
class ThrottlingMiddleware(BaseMiddleware):
def __init__(self, default_rate: Union[int, float] = 0.5) -> None:
self.default_rate = default_rate
@@ -49,15 +49,7 @@ class ThrottlingMiddleware(BaseMiddleware):
self.users[this_user.id]['count_throttled'] = 2
self.users[this_user.id]['now_rate'] = self.default_rate + 3
- await event.reply(
- "❗ Пожалуйста, не спамьте.\n"
- "❗ Please, do not spam.",
- )
+ await event.reply("❗ Please, do not spam.")
elif self.users[this_user.id]['count_throttled'] == 2:
self.users[this_user.id]['count_throttled'] = 3
self.users[this_user.id]['now_rate'] = self.default_rate + 5
-
- await event.reply(
- "❗ Бот не будет отвечать до прекращения спама.\n"
- "❗ The bot will not respond until the spam stops.",
- )
diff --git a/tgbot/middlewares/middleware_user.py b/tgbot/middlewares/middleware_user.py
index ad10d25..d54e75c 100644
--- a/tgbot/middlewares/middleware_user.py
+++ b/tgbot/middlewares/middleware_user.py
@@ -1,7 +1,7 @@
# - *- coding: utf- 8 - *-
from aiogram import BaseMiddleware
-from tgbot.database.db_users import Userx
+from tgbot.database.db_users import Usersx
from tgbot.utils.const_functions import clear_html
@@ -11,7 +11,7 @@ class ExistsUserMiddleware(BaseMiddleware):
this_user = data.get("event_from_user")
if not this_user.is_bot:
- get_user = Userx.get(user_id=this_user.id)
+ get_user = Usersx().get(user_id=this_user.id)
user_id = this_user.id
user_login = this_user.username
@@ -29,20 +29,20 @@ class ExistsUserMiddleware(BaseMiddleware):
if len(user_surname) >= 1: user_fullname += f" {user_surname}"
if get_user is None:
- Userx.add(user_id, user_login.lower(), user_name, user_surname, user_fullname)
+ Usersx().add(user_id, user_login.lower(), user_name, user_surname, user_fullname)
else:
if user_name != get_user.user_name:
- Userx.update(get_user.user_id, user_name=user_name)
+ Usersx().update(get_user.user_id, user_name=user_name)
if user_surname != get_user.user_surname:
- Userx.update(get_user.user_id, user_surname=user_surname)
+ Usersx().update(get_user.user_id, user_surname=user_surname)
if user_fullname != get_user.user_fullname:
- Userx.update(get_user.user_id, user_fullname=user_fullname)
+ Usersx().update(get_user.user_id, user_fullname=user_fullname)
if user_login.lower() != get_user.user_login:
- Userx.update(get_user.user_id, user_login=user_login.lower())
+ Usersx().update(get_user.user_id, user_login=user_login.lower())
- data['User'] = Userx.get(user_id=user_id)
+ data['User'] = Usersx().get(user_id=user_id)
return await handler(event, data)
diff --git a/tgbot/routers/__init__.py b/tgbot/routers/__init__.py
index bb7d82c..5226391 100644
--- a/tgbot/routers/__init__.py
+++ b/tgbot/routers/__init__.py
@@ -7,9 +7,9 @@ from tgbot.routers.user import user_menu
from tgbot.utils.misc.bot_filters import IsAdmin
-# Регистрация всех роутеров
+# Register all routers
def register_all_routers(dp: Dispatcher):
- # Подключение фильтров
+ # Connect filters
main_errors.router.message.filter(F.chat.type == "private")
main_start.router.message.filter(F.chat.type == "private")
@@ -18,13 +18,13 @@ def register_all_routers(dp: Dispatcher):
main_missed.router.message.filter(F.chat.type == "private")
- # Подключение обязательных роутеров
- dp.include_router(main_errors.router) # Роутер ошибки
- dp.include_router(main_start.router) # Роутер основных команд
+ # Connect needed routers
+ dp.include_router(main_errors.router) # Router - error
+ dp.include_router(main_start.router) # Router - main
- # Подключение пользовательских роутеров (юзеров и админов)
- dp.include_router(user_menu.router) # Юзер роутер
- dp.include_router(admin_menu.router) # Админ роутер
+ # Connect usebles routers (users и admins)
+ dp.include_router(user_menu.router) # Router - user
+ dp.include_router(admin_menu.router) # Router - admin
- # Подключение обязательных роутеров
- dp.include_router(main_missed.router) # Роутер пропущенных апдейтов
+ # Connect needed routers
+ dp.include_router(main_missed.router) # Router - missed handlers
diff --git a/tgbot/routers/admin/admin_menu.py b/tgbot/routers/admin/admin_menu.py
index 26bc1a8..8326ba0 100644
--- a/tgbot/routers/admin/admin_menu.py
+++ b/tgbot/routers/admin/admin_menu.py
@@ -8,38 +8,32 @@ from aiogram.types import FSInputFile, Message, CallbackQuery
from aiogram.utils.media_group import MediaGroupBuilder
from tgbot.data.config import PATH_DATABASE, PATH_LOGS
-from tgbot.database.db_users import UserModel
-from tgbot.keyboards.inline_misc import admin_inl
-from tgbot.keyboards.reply_misc import admin_rep
+from tgbot.database.db_users import BaseModel as UserModel
+from tgbot.keyboards.inline_main import admin_finl
from tgbot.utils.const_functions import get_date
from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__)
-# Кнопка - Admin Inline
-@router.message(F.text == 'Admin Inline')
+# Message - Admin button
+@router.message(F.text == 'Admin button')
async def admin_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
- await message.answer("Click Button - Admin Inline", reply_markup=admin_inl)
+ await message.answer(
+ "Inline admin keyboards",
+ reply_markup=admin_finl()
+ )
-# Кнопка - Admin Reply
-@router.message(F.text == 'Admin Reply')
-async def admin_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
- await state.clear()
-
- await message.answer("Click Button - Admin Reply", reply_markup=admin_rep)
-
-
-# Колбэк - Admin X
+# Callback - Admin X
@router.callback_query(F.data == 'admin_inline_x')
async def admin_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await call.answer(f"Click Admin X")
-# Колбэк - Admin
+# Callback - Admin
@router.callback_query(F.data.startswith('admin_inline:'))
async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
get_data = call.data.split(":")[1]
@@ -47,7 +41,7 @@ async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSes
await call.answer(f"Click Admin - {get_data}", True)
-# Получение Базы Данных
+# Get Database file
@router.message(Command(commands=['db', 'database']))
async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
@@ -58,7 +52,7 @@ async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS,
)
-# Получение логов
+# Get Logs file
@router.message(Command(commands=['log', 'logs']))
async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
@@ -79,7 +73,7 @@ async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User
await message.answer_media_group(media=media_group.build())
-# Очистить логи
+# Clear logs file
@router.message(Command(commands=['clear_log', 'clear_logs', 'log_clear', 'logs_clear']))
async def admin_logs_clear(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
diff --git a/tgbot/routers/main_errors.py b/tgbot/routers/main_errors.py
index 11dea21..75ce878 100644
--- a/tgbot/routers/main_errors.py
+++ b/tgbot/routers/main_errors.py
@@ -8,14 +8,14 @@ from tgbot.utils.misc.bot_logging import bot_logger
router = Router(name=__name__)
-# Ошибка с блокировкой бота пользователем
+# Error with block forbidden user
# @router.errors(ExceptionTypeFilter(TelegramForbiddenError))
# class MyHandler(ErrorHandler):
# async def handle(self):
# ...
-# Ошибка с редактированием одинакового сообщения
+# Error with edit duplicate message
@router.errors(ExceptionMessageFilter(
"Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")
)
diff --git a/tgbot/routers/main_missed.py b/tgbot/routers/main_missed.py
index d371b1a..9338bcc 100644
--- a/tgbot/routers/main_missed.py
+++ b/tgbot/routers/main_missed.py
@@ -2,34 +2,34 @@
from aiogram import Router, Bot, F
from aiogram.types import CallbackQuery, Message
-from tgbot.database.db_users import UserModel
+from tgbot.database import ModelUsers
from tgbot.utils.const_functions import del_message
from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__)
-# Колбэк с удалением сообщения
+# Callback with delete message
@router.callback_query(F.data == 'close_this')
-async def main_callback_close(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
+async def main_callback_close(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: ModelUsers):
await del_message(call.message)
-# Колбэк с обработкой кнопки
+# Callback with processing miss button
@router.callback_query(F.data == '...')
-async def main_callback_answer(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
+async def main_callback_answer(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: ModelUsers):
await call.answer(cache_time=30)
-# Колбэк с обработкой удаления сообщений потерявших стейт
+# Callback with processing miss callback data
@router.callback_query()
-async def main_callback_missed(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
+async def main_callback_missed(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: ModelUsers):
await call.answer(f"❗️ Miss callback: {call.data}", True)
-# Обработка всех неизвестных команд
+# Processing all unknowns callback datas
@router.message()
-async def main_message_missed(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
+async def main_message_missed(message: Message, bot: Bot, state: FSM, arSession: ARS, User: ModelUsers):
await message.answer(
"♦️ Unknown command.\n"
"♦️ Enter /start",
diff --git a/tgbot/routers/main_start.py b/tgbot/routers/main_start.py
index f464fb0..2948726 100644
--- a/tgbot/routers/main_start.py
+++ b/tgbot/routers/main_start.py
@@ -3,7 +3,7 @@ from aiogram import Router, Bot, F
from aiogram.filters import Command
from aiogram.types import Message
-from tgbot.database.db_users import UserModel
+from tgbot.database.db_users import BaseModel as UserModel
from tgbot.keyboards.reply_main import menu_frep
from tgbot.utils.const_functions import ded
from tgbot.utils.misc.bot_models import FSM, ARS
@@ -11,8 +11,8 @@ from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__)
-# Открытие главного меню
-@router.message(F.text.in_(('🔙 Main menu', '🔙 Return')))
+# Main menu
+@router.message(F.text.in_(('menu', 'return', 'start')))
@router.message(Command(commands=['start']))
async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
@@ -20,7 +20,7 @@ async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, Use
await message.answer(
ded(f"""
🔸 Hello, {User.user_name}
- 🔸 Enter /start or /inline
+ 🔸 Enter /start or /menu
"""),
reply_markup=menu_frep(message.from_user.id),
)
diff --git a/tgbot/routers/user/user_menu.py b/tgbot/routers/user/user_menu.py
index a3bc976..4c4c1dc 100644
--- a/tgbot/routers/user/user_menu.py
+++ b/tgbot/routers/user/user_menu.py
@@ -3,55 +3,43 @@ from aiogram import Router, Bot, F
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery
-from tgbot.database.db_users import UserModel
-from tgbot.keyboards.inline_main import menu_finl
-from tgbot.keyboards.inline_misc import user_inl
-from tgbot.keyboards.reply_misc import user_rep
+from tgbot.database.db_users import BaseModel as UserModel
+from tgbot.keyboards.inline_main import user_finl
+from tgbot.keyboards.reply_main import menu_frep
from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__)
-# Кнопка - User Inline
-@router.message(F.text == 'User Inline')
+# Message - User button
+@router.message(F.text == 'User button')
async def user_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
await message.answer(
- "Click Button - User Inline",
- reply_markup=user_inl,
+ "Inline user keyboards",
+ reply_markup=user_finl()
)
-# Кнопка - User Reply
-@router.message(F.text == 'User Reply')
-async def user_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
+# Command - /menu
+@router.message(Command(commands="menu"))
+async def user_command_menu(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear()
await message.answer(
- "Click Button - User Reply",
- reply_markup=user_rep,
+ "Enter command - /menu",
+ reply_markup=menu_frep(message.from_user.id),
)
-# Команда - /inline
-@router.message(Command(commands="inline"))
-async def user_command_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
- await state.clear()
-
- await message.answer(
- "Click command - /inline",
- reply_markup=menu_finl(message.from_user.id),
- )
-
-
-# Колбэк - User X
+# Callback - User X
@router.callback_query(F.data == 'user_inline_x')
async def user_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await call.answer(f"Click User X")
-# Колбэк - User
+# Callback - User
@router.callback_query(F.data.startswith('user_inline:'))
async def user_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
get_data = call.data.split(":")[1]
diff --git a/tgbot/utils/const_functions.py b/tgbot/utils/const_functions.py
index 1492651..07e08bf 100644
--- a/tgbot/utils/const_functions.py
+++ b/tgbot/utils/const_functions.py
@@ -7,19 +7,19 @@ from typing import Union
import pytz
from aiogram import Bot
-from aiogram.types import InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup, \
- ReplyKeyboardMarkup
+from aiogram.types import (InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup,
+ ReplyKeyboardMarkup)
from tgbot.data.config import get_admins, BOT_TIMEZONE
-######################################## AIOGRAM ########################################
-# Генерация реплай кнопки
+#################################### AIOGRAM ###################################
+# Generate replay button
def rkb(text: str) -> KeyboardButton:
return KeyboardButton(text=text)
-# Генерация инлайн кнопки
+# Generate inline button
def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: str = None) -> InlineKeyboardButton:
if data is not None:
return InlineKeyboardButton(text=text, callback_data=data)
@@ -29,9 +29,11 @@ def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: s
return InlineKeyboardButton(text=text, switch_inline_query=switch)
elif web is not None:
return InlineKeyboardButton(text=text, web_app=WebAppInfo(url=web))
+ else:
+ raise "Unknown data"
-# Удаление сообщения с обработкой ошибки от телеграма
+# Deleting a message with error handling from Telegram
async def del_message(message: Message):
try:
await message.delete()
@@ -39,7 +41,7 @@ async def del_message(message: Message):
...
-# Умная отправка сообщений (автоотправка сообщения с фото или без)
+# Smart messaging (automatic sending of messages with or without photos)
async def smart_message(
bot: Bot,
user_id: int,
@@ -62,7 +64,7 @@ async def smart_message(
)
-# Отправка сообщения всем админам
+# Send a message to all administrators
async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
for admin in get_admins():
try:
@@ -77,8 +79,8 @@ async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
...
-######################################## ПРОЧЕЕ ########################################
-# Удаление отступов в многострочной строке ("""text""")
+##################################### MISC #####################################
+# Removing indents in a multi-line string ("""text""")
def ded(get_text: str) -> str:
if get_text is not None:
split_text = get_text.split("\n")
@@ -98,7 +100,7 @@ def ded(get_text: str) -> str:
return get_text
-# Очистка текста от HTML тэгов ('test' -> *b*test*/b*)
+# Cleaning text of HTML tags ('test' -> *b*test*/b*)
def clear_html(get_text: str) -> str:
if get_text is not None:
if "" in get_text: get_text = get_text.replace("<", "*")
@@ -110,7 +112,7 @@ def clear_html(get_text: str) -> str:
return get_text
-# Очистка пробелов в списке (['', 1, ' ', 2] -> [1, 2])
+# Cleaning up gaps in the list (['', 1, ' ', 2] -> [1, 2])
def clear_list(get_list: list) -> list:
while "" in get_list: get_list.remove("")
while " " in get_list: get_list.remove(" ")
@@ -122,12 +124,12 @@ def clear_list(get_list: list) -> list:
return get_list
-# Разбив списка на несколько частей ([1, 2, 3, 4] 2 -> [[1, 2], [3, 4]])
+# Split the list into several parts ([1, 2, 3, 4] 2 -> [[1, 2], [3, 4]])
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)]
-# Получение текущей даты (True - дата с временем, False - дата без времени)
+# Get the current date (True - date with time, False - date without time)
def get_date(full: bool = True) -> str:
if full:
return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S")
@@ -135,7 +137,7 @@ def get_date(full: bool = True) -> str:
return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y")
-# Получение текущего unix времени (True - время в наносекундах, False - время в секундах)
+# Get the current Unix time (True - time in nanoseconds, False - time in seconds)
def get_unix(full: bool = False) -> int:
if full:
return time.time_ns()
@@ -143,7 +145,7 @@ def get_unix(full: bool = False) -> int:
return int(time.time())
-# Конвертация unix в дату и даты в unix
+# Converting Unix to date and dates to Unix
def convert_date(from_time, full=True, second=True) -> Union[str, int]:
from tgbot.data.config import BOT_TIMEZONE
@@ -194,7 +196,7 @@ def convert_date(from_time, full=True, second=True) -> Union[str, int]:
return to_time
-# Генерация уникального айди
+# Generation of a unique ID
def gen_id(len_id: int = 16) -> int:
mac_address = uuid.getnode()
time_unix = int(str(time.time_ns())[:len_id])
@@ -203,8 +205,10 @@ def gen_id(len_id: int = 16) -> int:
return mac_address + time_unix + random_int
-# Генерация пароля | default, number, letter, onechar
+# Password generation | default, number, letter, onechar
def gen_password(len_password: int = 16, type_password: str = "default") -> str:
+ char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
+
if type_password == "default":
char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
elif type_password == "letter":
@@ -223,7 +227,7 @@ def gen_password(len_password: int = 16, type_password: str = "default") -> str:
return random_chars
-# Дополнение к числу корректного времени (1 -> 1 день, 3 -> 3 дня)
+# Addition to the correct time (1 -> 1 day, 3 -> 3 days)
def convert_times(get_time: int, get_type: str = "day") -> str:
get_time = int(get_time)
if get_time < 0: get_time = 0
@@ -251,7 +255,7 @@ def convert_times(get_time: int, get_type: str = "day") -> str:
return f"{get_time} {get_list[count]}"
-# Проверка на булевый тип
+# Boolean type check
def is_bool(value: Union[bool, str, int]) -> bool:
value = str(value).lower()
@@ -263,8 +267,8 @@ def is_bool(value: Union[bool, str, int]) -> bool:
raise ValueError(f"invalid truth value {value}")
-######################################## ЧИСЛА ########################################
-# Преобразование экспоненциальных чисел в читаемый вид (1e-06 -> 0.000001)
+################################### NUMBERS ####################################
+# Converting exponential numbers to a readable form (1e-06 -> 0.000001)
def snum(amount: Union[int, float], remains: int = 2) -> str:
format_str = "{:." + str(remains) + "f}"
str_amount = format_str.format(float(amount))
@@ -284,7 +288,7 @@ def snum(amount: Union[int, float], remains: int = 2) -> str:
return str(str_amount)
-# Конвертация любого числа в вещественное, с удалением нулей в конце (remains - округление)
+# Convert any number to a real number, removing trailing zeros (remains - rounding)
def to_float(get_number, remains: int = 2) -> Union[int, float]:
if "," in str(get_number):
get_number = str(get_number).replace(",", ".")
@@ -313,7 +317,7 @@ def to_float(get_number, remains: int = 2) -> Union[int, float]:
return get_number
-# Конвертация вещественного числа в целочисленное
+# Converting a real number to an integer
def to_int(get_number: float) -> int:
if "," in get_number:
get_number = str(get_number).replace(",", ".")
@@ -323,7 +327,7 @@ def to_int(get_number: float) -> int:
return get_number
-# Проверка ввода на число
+# Data validation for numbers
def is_number(get_number: Union[str, int, float]) -> bool:
if str(get_number).isdigit():
return True
@@ -337,7 +341,7 @@ def is_number(get_number: Union[str, int, float]) -> bool:
return False
-# Преобразование числа в читаемый вид (123456789 -> 123 456 789)
+# Converting a number to a readable form (123456789 -> 123,456,789)
def format_rate(amount: Union[float, int], around: int = 2) -> str:
if "," in str(amount): amount = float(str(amount).replace(",", "."))
if " " in str(amount): amount = float(str(amount).replace(" ", ""))
diff --git a/tgbot/utils/misc/bot_commands.py b/tgbot/utils/misc/bot_commands.py
index 16064a0..bd62b83 100644
--- a/tgbot/utils/misc/bot_commands.py
+++ b/tgbot/utils/misc/bot_commands.py
@@ -4,22 +4,22 @@ from aiogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeDefaul
from tgbot.data.config import get_admins
-# Команды для юзеров
+# Commands for users
user_commands = [
BotCommand(command="start", description="♻️ Restart bot"),
- BotCommand(command="inline", description="🌀 Get Inline keyboard"),
+ BotCommand(command="menu", description="🌀 Get keyboards"),
]
-# Команды для админов
+# Commands for admins
admin_commands = [
BotCommand(command="start", description="♻️ Restart bot"),
- BotCommand(command="inline", description="🌀 Get Inline keyboard"),
+ BotCommand(command="menu", description="🌀 Get keyboards"),
BotCommand(command="log", description="🖨 Get Logs"),
BotCommand(command="db", description="📦 Get Database"),
]
-# Установка команд
+# Set commands
async def set_commands(bot: Bot):
await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
diff --git a/tgbot/utils/misc/bot_filters.py b/tgbot/utils/misc/bot_filters.py
index 5114210..ffbde4b 100644
--- a/tgbot/utils/misc/bot_filters.py
+++ b/tgbot/utils/misc/bot_filters.py
@@ -5,7 +5,7 @@ from aiogram.types import Message
from tgbot.data.config import get_admins
-# Проверка на админа
+# Filter on admin right
class IsAdmin(BaseFilter):
async def __call__(self, message: Message) -> bool:
if message.from_user.id in get_admins():
diff --git a/tgbot/utils/misc/bot_logging.py b/tgbot/utils/misc/bot_logging.py
index 1246e97..043a720 100644
--- a/tgbot/utils/misc/bot_logging.py
+++ b/tgbot/utils/misc/bot_logging.py
@@ -5,24 +5,24 @@ import colorlog
from tgbot.data.config import PATH_LOGS
-# Формат логгирования
+# Logging format
log_formatter_file = bot_logger.Formatter("%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s")
log_formatter_console = colorlog.ColoredFormatter(
"%(purple)s%(levelname)s %(blue)s|%(purple)s %(asctime)s %(blue)s|%(purple)s %(filename)s:%(lineno)d %(blue)s|%(purple)s %(message)s%(red)s",
datefmt="%d-%m-%Y %H:%M:%S",
)
-# Логгирование в файл logs.log
+# Logging in file logs.log
file_handler = bot_logger.FileHandler(PATH_LOGS, "w", "utf-8")
file_handler.setFormatter(log_formatter_file)
file_handler.setLevel(bot_logger.INFO)
-# Логгирование в консоль
+# Logging in console
console_handler = bot_logger.StreamHandler()
console_handler.setFormatter(log_formatter_console)
console_handler.setLevel(bot_logger.CRITICAL)
-# Подключение настроек логгирования
+# Connect logging settings
bot_logger.basicConfig(
format="%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s",
handlers=[
diff --git a/tgbot/utils/misc_functions.py b/tgbot/utils/misc_functions.py
index 2a41f52..0c34545 100644
--- a/tgbot/utils/misc_functions.py
+++ b/tgbot/utils/misc_functions.py
@@ -6,13 +6,13 @@ from tgbot.data.config import get_admins, PATH_DATABASE, BOT_STATUS_NOTIFICATION
from tgbot.utils.const_functions import get_date, send_admins
-# Выполнение функции после запуска бота (рассылка админам о запуске бота)
+# Notification after run bot (for all admins)
async def startup_notify(bot: Bot):
if len(get_admins()) >= 1 and BOT_STATUS_NOTIFICATION:
await send_admins(bot, "✅ Bot was started")
-# Автоматические бэкапы БД
+# Autobackup Database
async def autobackup_admin(bot: Bot):
for admin in get_admins():
try: