This commit is contained in:
dj
2025-10-10 07:04:08 +03:00
parent 9a48d6f60f
commit c9099dac69
26 changed files with 738 additions and 405 deletions
+21 -25
View File
@@ -6,9 +6,10 @@ import sys
import colorama import colorama
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from tgbot.data.config import BOT_TOKEN, BOT_SCHEDULER, get_admins 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.middlewares import register_all_middlwares
from tgbot.routers import register_all_routers from tgbot.routers import register_all_routers
from tgbot.services.api_session import AsyncRequestSession from tgbot.services.api_session import AsyncRequestSession
@@ -19,30 +20,31 @@ from tgbot.utils.misc_functions import autobackup_admin, startup_notify
colorama.init() colorama.init()
# Запуск шедулеров # Start schedulers
async def scheduler_start(bot): async def scheduler_start(bot):
BOT_SCHEDULER.add_job(autobackup_admin, trigger="cron", hour=00, args=(bot,)) # Ежедневный Автобэкап в 00:00 BOT_SCHEDULER.add_job(autobackup_admin, trigger="cron", hour=00, args=(bot,)) # Ежедневный Автобэкап в 00:00
# Запуск бота и базовых функций # Start bot and basic functions
async def main(): async def main():
BOT_SCHEDULER.start() # Запуск Шедулера BOT_SCHEDULER.start() # Start scheduler
dp = Dispatcher() # Образ Диспетчера dp = Dispatcher() # Dispatcher image
arSession = AsyncRequestSession() # Пул асинхронной сессии запросов arSession = AsyncRequestSession() # Async session pool (aiohttp)
bot = Bot( # Образ Бота bot = Bot( # Образ Бота
token=BOT_TOKEN, token=BOT_TOKEN,
default=DefaultBotProperties( default=DefaultBotProperties(
parse_mode="HTML", parse_mode=ParseMode.HTML
), ),
) )
register_all_middlwares(dp) # Регистрация всех мидлварей register_all_middlwares(dp) # Register all middlewares
register_all_routers(dp) # Регистрация всех роутеров register_all_routers(dp) # Register all routers
try: try:
await set_commands(bot) # Установка пользовательских команд в боте await set_commands(bot) # Set commands for users
await startup_notify(bot) # Уведомление админов о запуске бота await startup_notify(bot) # Notification that bot was started
await scheduler_start(bot) # Подключение шедулеров await scheduler_start(bot) # Connect schedulers
bot_logger.warning("BOT WAS STARTED") bot_logger.warning("BOT WAS STARTED")
print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Bot was started - @{(await bot.get_me()).username} ~~~~~") 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 *****") if len(get_admins()) == 0: print("***** ENTER ADMIN ID IN settings.ini *****")
await bot.delete_webhook() # Удаление вебхуков, если они имеются await bot.delete_webhook() # Deletes webhooks, if they was have
await bot.get_updates(offset=-1) # Сброс пендинг апдейтов await bot.get_updates(offset=-1) # Reset update pengings
# Запуск бота (поллинга) # Run bot (polling method)
await dp.start_polling( await dp.start_polling(
bot, bot,
arSession=arSession, arSession=arSession,
allowed_updates=dp.resolve_used_update_types(), allowed_updates=dp.resolve_used_update_types(),
) )
finally: finally:
await arSession.close() # Закрытие Асинхронной Сессии для прочих запросов await arSession.close() # Close async session (aiohttp)
await bot.session.close() # Закрытие сессии бота await bot.session.close() # Close bot session
if __name__ == "__main__": if __name__ == "__main__":
create_dbx() # Генерация Базы Данных и Таблиц database_initialization() # Initializate Database, tables and columns
try: try:
if sys.platform == 'win32': # Запуск на 32-х битных системах asyncio.run(main())
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
else:
asyncio.run(main()) # Запуск на всех других системах
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
bot_logger.warning("Bot was stopped") bot_logger.warning("Bot was stopped")
finally: finally:
+8 -10
View File
@@ -1,10 +1,8 @@
APScheduler==3.10.4 APScheduler==3.11.0
aiogram==3.5.0 aiogram==3.22.0
colorlog==6.8.2 colorlog==6.9.0
pytz==2024.1 pytz==2025.2
typing~=3.7.4.3 DateTime==5.5
aiohttp~=3.9.1 typing==3.7.4.3
cachetools==5.3.3 aiofiles==25.1.0
colorama~=0.4.5 colorama==0.4.6
aiofiles~=23.2.1
pydantic~=2.5.2
+2
View File
@@ -0,0 +1,2 @@
from .db_settings import Settingsx, ModelBase as ModelSettings
from .db_users import Usersx, ModelBase as ModelUsers
+494
View File
@@ -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()
+36
View File
@@ -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',
# },
}
-88
View File
@@ -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...")
+23 -16
View File
@@ -4,33 +4,40 @@ import sqlite3
from pydantic import BaseModel from pydantic import BaseModel
from tgbot.data.config import PATH_DATABASE 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
# Модель таблицы # Table model
class SettingsModel(BaseModel): class ModelBase(BaseModel):
status_work: str # Статус работы бота status_work: str # Status bot work
class Config:
json_schema_extra = {
'storage_name': 'storage_settings'
}
# Работа с настройками # Settings
class Settingsx: class Settingsx(Databasex[ModelBase]):
storage_name = "storage_settings" def __init__(self):
super().__init__()
self.table_model = ModelBase
self.storage_name = self.table_model.model_json_schema()['storage_name']
self.column_one = True
# Получение записи # Get entry
@staticmethod def get(self) -> ModelBase:
def get() -> SettingsModel:
with sqlite3.connect(PATH_DATABASE) as con: with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory 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())
# Редактирование записи # Edit entry
@staticmethod def update(self, **kwargs):
def update(**kwargs):
with sqlite3.connect(PATH_DATABASE) as con: with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory 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) sql, parameters = update_format(sql, kwargs)
con.execute(sql, parameters) con.execute(sql, parameters)
+28 -83
View File
@@ -4,28 +4,37 @@ import sqlite3
from pydantic import BaseModel from pydantic import BaseModel
from tgbot.data.config import PATH_DATABASE 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 from tgbot.utils.const_functions import get_unix, ded
# Модель таблицы # Table model
class UserModel(BaseModel): class ModelBase(BaseModel):
increment: int # Инкремент записи increment: int # increment
user_id: int # Айди пользователя user_id: int # User id
user_login: str # Юзернейм пользователя user_login: str # User username
user_name: str # Имя пользователя user_name: str # User name
user_surname: str # Фамилия пользователя user_surname: str # User surname
user_fullname: str # Полное имя + фамилия пользователя user_fullname: str # User fullname (name + surname)
user_unix: int # Дата регистрации пользователя в боте (в UNIX времени) user_unix: int # Date of user registration in the bot (in UNIX time)
class Config:
json_schema_extra = {
'storage_name': 'storage_users'
}
# Работа с юзером # Users
class Userx: class Usersx(Databasex[ModelBase]):
storage_name = "storage_users" def __init__(self):
super().__init__()
self.table_model = ModelBase
self.storage_name = self.table_model.model_json_schema()['storage_name']
self.column_one = False
# Добавление записи # Add entry
@staticmethod
def add( def add(
self,
user_id: int, user_id: int,
user_login: str, user_login: str,
user_name: str, user_name: str,
@@ -39,7 +48,7 @@ class Userx:
con.execute( con.execute(
ded(f""" ded(f"""
INSERT INTO {Userx.storage_name} ( INSERT INTO {self.storage_name} (
user_id, user_id,
user_login, user_login,
user_name, user_name,
@@ -58,76 +67,12 @@ class Userx:
], ],
) )
# Получение записи # Edit entry
@staticmethod def update(self, user_id: int, **kwargs):
def get(**kwargs) -> UserModel:
with sqlite3.connect(PATH_DATABASE) as con: with sqlite3.connect(PATH_DATABASE) as con:
con.row_factory = dict_factory con.row_factory = dict_factory
sql = f"SELECT * FROM {Userx.storage_name}" sql = f"UPDATE {self.storage_name} SET"
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, parameters = update_format(sql, kwargs) sql, parameters = update_format(sql, kwargs)
parameters.append(user_id) parameters.append(user_id)
con.execute(sql + "WHERE user_id = ?", parameters) 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)
+18 -9
View File
@@ -2,25 +2,34 @@
from aiogram.types import InlineKeyboardMarkup from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder
from tgbot.data.config import get_admins
from tgbot.utils.const_functions import ikb from tgbot.utils.const_functions import ikb
# Кнопки инлайн меню # Inline keyboards for users
def menu_finl(user_id: int) -> InlineKeyboardMarkup: def user_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
keyboard.row( keyboard.row(
ikb("User X", data="user_inline_x"), ikb("User X", data="user_inline_x"),
ikb("User 1", data="user_inline:user_btn"), ikb("User 1", data="user_inline:user_btn"),
ikb("User 2", data="..."), ikb("User 2", data="..."),
).row(
ikb("User Unknown", data="unknown"),
) )
if user_id in get_admins(): return keyboard.as_markup()
keyboard.row(
ikb("Admin X", data="admin_inline_x"),
ikb("Admin 1", data="admin_inline:admin_btn"), # Inline keyboards for admins
ikb("Admin 2", data="unknown"), 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() return keyboard.as_markup()
-22
View File
@@ -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()
+2 -2
View File
@@ -11,12 +11,12 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
keyboard = ReplyKeyboardBuilder() keyboard = ReplyKeyboardBuilder()
keyboard.row( keyboard.row(
rkb("User Inline"), rkb("User Reply"), rkb("User button"),
) )
if user_id in get_admins(): if user_id in get_admins():
keyboard.row( keyboard.row(
rkb("Admin Inline"), rkb("Admin Reply"), rkb("Admin button"),
) )
return keyboard.as_markup(resize_keyboard=True) return keyboard.as_markup(resize_keyboard=True)
-22
View File
@@ -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)
+2 -2
View File
@@ -1,11 +1,11 @@
# - *- coding: utf- 8 - *- # - *- coding: utf- 8 - *-
from aiogram import Dispatcher from aiogram import Dispatcher
from tgbot.middlewares.middleware_user import ExistsUserMiddleware
from tgbot.middlewares.middleware_throttling import ThrottlingMiddleware from tgbot.middlewares.middleware_throttling import ThrottlingMiddleware
from tgbot.middlewares.middleware_user import ExistsUserMiddleware
# Регистрация всех миддлварей # Register all middlewares
def register_all_middlwares(dp: Dispatcher): def register_all_middlwares(dp: Dispatcher):
dp.callback_query.outer_middleware(ExistsUserMiddleware()) dp.callback_query.outer_middleware(ExistsUserMiddleware())
dp.message.outer_middleware(ExistsUserMiddleware()) dp.message.outer_middleware(ExistsUserMiddleware())
+2 -10
View File
@@ -8,7 +8,7 @@ from aiogram.types import Message, User
from cachetools import TTLCache from cachetools import TTLCache
# Антиспам # Antiflood
class ThrottlingMiddleware(BaseMiddleware): class ThrottlingMiddleware(BaseMiddleware):
def __init__(self, default_rate: Union[int, float] = 0.5) -> None: def __init__(self, default_rate: Union[int, float] = 0.5) -> None:
self.default_rate = default_rate 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]['count_throttled'] = 2
self.users[this_user.id]['now_rate'] = self.default_rate + 3 self.users[this_user.id]['now_rate'] = self.default_rate + 3
await event.reply( await event.reply("<b>❗ Please, do not spam.")
"<b>❗ Пожалуйста, не спамьте.\n"
"❗ Please, do not spam.</b>",
)
elif self.users[this_user.id]['count_throttled'] == 2: elif self.users[this_user.id]['count_throttled'] == 2:
self.users[this_user.id]['count_throttled'] = 3 self.users[this_user.id]['count_throttled'] = 3
self.users[this_user.id]['now_rate'] = self.default_rate + 5 self.users[this_user.id]['now_rate'] = self.default_rate + 5
await event.reply(
"<b>❗ Бот не будет отвечать до прекращения спама.\n"
"❗ The bot will not respond until the spam stops.</b>",
)
+8 -8
View File
@@ -1,7 +1,7 @@
# - *- coding: utf- 8 - *- # - *- coding: utf- 8 - *-
from aiogram import BaseMiddleware 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 from tgbot.utils.const_functions import clear_html
@@ -11,7 +11,7 @@ class ExistsUserMiddleware(BaseMiddleware):
this_user = data.get("event_from_user") this_user = data.get("event_from_user")
if not this_user.is_bot: 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_id = this_user.id
user_login = this_user.username user_login = this_user.username
@@ -29,20 +29,20 @@ class ExistsUserMiddleware(BaseMiddleware):
if len(user_surname) >= 1: user_fullname += f" {user_surname}" if len(user_surname) >= 1: user_fullname += f" {user_surname}"
if get_user is None: 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: else:
if user_name != get_user.user_name: 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: 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: 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: 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) return await handler(event, data)
+10 -10
View File
@@ -7,9 +7,9 @@ from tgbot.routers.user import user_menu
from tgbot.utils.misc.bot_filters import IsAdmin from tgbot.utils.misc.bot_filters import IsAdmin
# Регистрация всех роутеров # Register all routers
def register_all_routers(dp: Dispatcher): def register_all_routers(dp: Dispatcher):
# Подключение фильтров # Connect filters
main_errors.router.message.filter(F.chat.type == "private") main_errors.router.message.filter(F.chat.type == "private")
main_start.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") main_missed.router.message.filter(F.chat.type == "private")
# Подключение обязательных роутеров # Connect needed routers
dp.include_router(main_errors.router) # Роутер ошибки dp.include_router(main_errors.router) # Router - error
dp.include_router(main_start.router) # Роутер основных команд dp.include_router(main_start.router) # Router - main
# Подключение пользовательских роутеров (юзеров и админов) # Connect usebles routers (users и admins)
dp.include_router(user_menu.router) # Юзер роутер dp.include_router(user_menu.router) # Router - user
dp.include_router(admin_menu.router) # Админ роутер dp.include_router(admin_menu.router) # Router - admin
# Подключение обязательных роутеров # Connect needed routers
dp.include_router(main_missed.router) # Роутер пропущенных апдейтов dp.include_router(main_missed.router) # Router - missed handlers
+13 -19
View File
@@ -8,38 +8,32 @@ from aiogram.types import FSInputFile, Message, CallbackQuery
from aiogram.utils.media_group import MediaGroupBuilder from aiogram.utils.media_group import MediaGroupBuilder
from tgbot.data.config import PATH_DATABASE, PATH_LOGS from tgbot.data.config import PATH_DATABASE, PATH_LOGS
from tgbot.database.db_users import UserModel from tgbot.database.db_users import BaseModel as UserModel
from tgbot.keyboards.inline_misc import admin_inl from tgbot.keyboards.inline_main import admin_finl
from tgbot.keyboards.reply_misc import admin_rep
from tgbot.utils.const_functions import get_date from tgbot.utils.const_functions import get_date
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__) router = Router(name=__name__)
# Кнопка - Admin Inline # Message - Admin button
@router.message(F.text == 'Admin Inline') @router.message(F.text == 'Admin button')
async def admin_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def admin_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() 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 # Callback - Admin X
@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
@router.callback_query(F.data == 'admin_inline_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): async def admin_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await call.answer(f"Click Admin X") await call.answer(f"Click Admin X")
# Колбэк - Admin # Callback - Admin
@router.callback_query(F.data.startswith('admin_inline:')) @router.callback_query(F.data.startswith('admin_inline:'))
async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
get_data = call.data.split(":")[1] 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) await call.answer(f"Click Admin - {get_data}", True)
# Получение Базы Данных # Get Database file
@router.message(Command(commands=['db', 'database'])) @router.message(Command(commands=['db', 'database']))
async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() 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'])) @router.message(Command(commands=['log', 'logs']))
async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() 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()) await message.answer_media_group(media=media_group.build())
# Очистить логи # Clear logs file
@router.message(Command(commands=['clear_log', 'clear_logs', 'log_clear', 'logs_clear'])) @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): async def admin_logs_clear(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() await state.clear()
+2 -2
View File
@@ -8,14 +8,14 @@ from tgbot.utils.misc.bot_logging import bot_logger
router = Router(name=__name__) router = Router(name=__name__)
# Ошибка с блокировкой бота пользователем # Error with block forbidden user
# @router.errors(ExceptionTypeFilter(TelegramForbiddenError)) # @router.errors(ExceptionTypeFilter(TelegramForbiddenError))
# class MyHandler(ErrorHandler): # class MyHandler(ErrorHandler):
# async def handle(self): # async def handle(self):
# ... # ...
# Ошибка с редактированием одинакового сообщения # Error with edit duplicate message
@router.errors(ExceptionMessageFilter( @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") "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")
) )
+9 -9
View File
@@ -2,34 +2,34 @@
from aiogram import Router, Bot, F from aiogram import Router, Bot, F
from aiogram.types import CallbackQuery, Message 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.const_functions import del_message
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__) router = Router(name=__name__)
# Колбэк с удалением сообщения # Callback with delete message
@router.callback_query(F.data == 'close_this') @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) await del_message(call.message)
# Колбэк с обработкой кнопки # Callback with processing miss button
@router.callback_query(F.data == '...') @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) await call.answer(cache_time=30)
# Колбэк с обработкой удаления сообщений потерявших стейт # Callback with processing miss callback data
@router.callback_query() @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) await call.answer(f"❗️ Miss callback: {call.data}", True)
# Обработка всех неизвестных команд # Processing all unknowns callback datas
@router.message() @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( await message.answer(
"♦️ Unknown command.\n" "♦️ Unknown command.\n"
"♦️ Enter /start", "♦️ Enter /start",
+4 -4
View File
@@ -3,7 +3,7 @@ from aiogram import Router, Bot, F
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import Message 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.keyboards.reply_main import menu_frep
from tgbot.utils.const_functions import ded from tgbot.utils.const_functions import ded
from tgbot.utils.misc.bot_models import FSM, ARS 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 = Router(name=__name__)
# Открытие главного меню # Main menu
@router.message(F.text.in_(('🔙 Main menu', '🔙 Return'))) @router.message(F.text.in_(('menu', 'return', 'start')))
@router.message(Command(commands=['start'])) @router.message(Command(commands=['start']))
async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() await state.clear()
@@ -20,7 +20,7 @@ async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, Use
await message.answer( await message.answer(
ded(f""" ded(f"""
🔸 Hello, {User.user_name} 🔸 Hello, {User.user_name}
🔸 Enter /start or /inline 🔸 Enter /start or /menu
"""), """),
reply_markup=menu_frep(message.from_user.id), reply_markup=menu_frep(message.from_user.id),
) )
+14 -26
View File
@@ -3,55 +3,43 @@ from aiogram import Router, Bot, F
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from tgbot.database.db_users import UserModel from tgbot.database.db_users import BaseModel as UserModel
from tgbot.keyboards.inline_main import menu_finl from tgbot.keyboards.inline_main import user_finl
from tgbot.keyboards.inline_misc import user_inl from tgbot.keyboards.reply_main import menu_frep
from tgbot.keyboards.reply_misc import user_rep
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
router = Router(name=__name__) router = Router(name=__name__)
# Кнопка - User Inline # Message - User button
@router.message(F.text == 'User Inline') @router.message(F.text == 'User button')
async def user_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def user_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() await state.clear()
await message.answer( await message.answer(
"Click Button - User Inline", "Inline user keyboards",
reply_markup=user_inl, reply_markup=user_finl()
) )
# Кнопка - User Reply # Command - /menu
@router.message(F.text == 'User Reply') @router.message(Command(commands="menu"))
async def user_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def user_command_menu(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await state.clear() await state.clear()
await message.answer( await message.answer(
"Click Button - User Reply", "Enter command - /menu",
reply_markup=user_rep, reply_markup=menu_frep(message.from_user.id),
) )
# Команда - /inline # Callback - User X
@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
@router.callback_query(F.data == 'user_inline_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): async def user_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
await call.answer(f"Click User X") await call.answer(f"Click User X")
# Колбэк - User # Callback - User
@router.callback_query(F.data.startswith('user_inline:')) @router.callback_query(F.data.startswith('user_inline:'))
async def user_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): async def user_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel):
get_data = call.data.split(":")[1] get_data = call.data.split(":")[1]
+30 -26
View File
@@ -7,19 +7,19 @@ from typing import Union
import pytz import pytz
from aiogram import Bot from aiogram import Bot
from aiogram.types import InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup, \ from aiogram.types import (InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup,
ReplyKeyboardMarkup ReplyKeyboardMarkup)
from tgbot.data.config import get_admins, BOT_TIMEZONE from tgbot.data.config import get_admins, BOT_TIMEZONE
######################################## AIOGRAM ######################################## #################################### AIOGRAM ###################################
# Генерация реплай кнопки # Generate replay button
def rkb(text: str) -> KeyboardButton: def rkb(text: str) -> KeyboardButton:
return KeyboardButton(text=text) return KeyboardButton(text=text)
# Генерация инлайн кнопки # Generate inline button
def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: str = None) -> InlineKeyboardButton: def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: str = None) -> InlineKeyboardButton:
if data is not None: if data is not None:
return InlineKeyboardButton(text=text, callback_data=data) return InlineKeyboardButton(text=text, callback_data=data)
@@ -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) return InlineKeyboardButton(text=text, switch_inline_query=switch)
elif web is not None: elif web is not None:
return InlineKeyboardButton(text=text, web_app=WebAppInfo(url=web)) 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): async def del_message(message: Message):
try: try:
await message.delete() 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( async def smart_message(
bot: Bot, bot: Bot,
user_id: int, 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): async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
for admin in get_admins(): for admin in get_admins():
try: try:
@@ -77,8 +79,8 @@ async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
... ...
######################################## ПРОЧЕЕ ######################################## ##################################### MISC #####################################
# Удаление отступов в многострочной строке ("""text""") # Removing indents in a multi-line string ("""text""")
def ded(get_text: str) -> str: def ded(get_text: str) -> str:
if get_text is not None: if get_text is not None:
split_text = get_text.split("\n") split_text = get_text.split("\n")
@@ -98,7 +100,7 @@ def ded(get_text: str) -> str:
return get_text return get_text
# Очистка текста от HTML тэгов ('<b>test</b>' -> *b*test*/b*) # Cleaning text of HTML tags ('<b>test</b>' -> *b*test*/b*)
def clear_html(get_text: str) -> str: def clear_html(get_text: str) -> str:
if get_text is not None: if get_text is not None:
if "</" in get_text: get_text = get_text.replace("<", "*") if "</" in get_text: get_text = get_text.replace("<", "*")
@@ -110,7 +112,7 @@ def clear_html(get_text: str) -> str:
return get_text return get_text
# Очистка пробелов в списке (['', 1, ' ', 2] -> [1, 2]) # Cleaning up gaps in the list (['', 1, ' ', 2] -> [1, 2])
def clear_list(get_list: list) -> list: def clear_list(get_list: list) -> list:
while "" in get_list: get_list.remove("") while "" in get_list: get_list.remove("")
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 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]: 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)]
# Получение текущей даты (True - дата с временем, False - дата без времени) # Get the current date (True - date with time, False - date without time)
def get_date(full: bool = True) -> str: def get_date(full: bool = True) -> str:
if full: if full:
return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S") 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") 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: def get_unix(full: bool = False) -> int:
if full: if full:
return time.time_ns() return time.time_ns()
@@ -143,7 +145,7 @@ def get_unix(full: bool = False) -> int:
return int(time.time()) 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]: def convert_date(from_time, full=True, second=True) -> Union[str, int]:
from tgbot.data.config import BOT_TIMEZONE 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 return to_time
# Генерация уникального айди # Generation of a unique ID
def gen_id(len_id: int = 16) -> int: def gen_id(len_id: int = 16) -> int:
mac_address = uuid.getnode() mac_address = uuid.getnode()
time_unix = int(str(time.time_ns())[:len_id]) 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 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: def gen_password(len_password: int = 16, type_password: str = "default") -> str:
char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
if type_password == "default": if type_password == "default":
char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ") char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ")
elif type_password == "letter": elif type_password == "letter":
@@ -223,7 +227,7 @@ def gen_password(len_password: int = 16, type_password: str = "default") -> str:
return random_chars 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: def convert_times(get_time: int, get_type: str = "day") -> str:
get_time = int(get_time) get_time = int(get_time)
if get_time < 0: get_time = 0 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]}" return f"{get_time} {get_list[count]}"
# Проверка на булевый тип # Boolean type check
def is_bool(value: Union[bool, str, int]) -> bool: def is_bool(value: Union[bool, str, int]) -> bool:
value = str(value).lower() value = str(value).lower()
@@ -263,8 +267,8 @@ def is_bool(value: Union[bool, str, int]) -> bool:
raise ValueError(f"invalid truth value {value}") raise ValueError(f"invalid truth value {value}")
######################################## ЧИСЛА ######################################## ################################### NUMBERS ####################################
# Преобразование экспоненциальных чисел в читаемый вид (1e-06 -> 0.000001) # Converting exponential numbers to a readable form (1e-06 -> 0.000001)
def snum(amount: Union[int, float], remains: int = 2) -> str: def snum(amount: Union[int, float], remains: int = 2) -> str:
format_str = "{:." + str(remains) + "f}" format_str = "{:." + str(remains) + "f}"
str_amount = format_str.format(float(amount)) 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) 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]: def to_float(get_number, remains: int = 2) -> Union[int, float]:
if "," in str(get_number): if "," in str(get_number):
get_number = str(get_number).replace(",", ".") get_number = str(get_number).replace(",", ".")
@@ -313,7 +317,7 @@ def to_float(get_number, remains: int = 2) -> Union[int, float]:
return get_number return get_number
# Конвертация вещественного числа в целочисленное # Converting a real number to an integer
def to_int(get_number: float) -> int: def to_int(get_number: float) -> int:
if "," in get_number: if "," in get_number:
get_number = str(get_number).replace(",", ".") get_number = str(get_number).replace(",", ".")
@@ -323,7 +327,7 @@ def to_int(get_number: float) -> int:
return get_number return get_number
# Проверка ввода на число # Data validation for numbers
def is_number(get_number: Union[str, int, float]) -> bool: def is_number(get_number: Union[str, int, float]) -> bool:
if str(get_number).isdigit(): if str(get_number).isdigit():
return True return True
@@ -337,7 +341,7 @@ def is_number(get_number: Union[str, int, float]) -> bool:
return False 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: 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(",", "."))
if " " in str(amount): amount = float(str(amount).replace(" ", "")) if " " in str(amount): amount = float(str(amount).replace(" ", ""))
+5 -5
View File
@@ -4,22 +4,22 @@ from aiogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeDefaul
from tgbot.data.config import get_admins from tgbot.data.config import get_admins
# Команды для юзеров # Commands for users
user_commands = [ user_commands = [
BotCommand(command="start", description="♻️ Restart bot"), BotCommand(command="start", description="♻️ Restart bot"),
BotCommand(command="inline", description="🌀 Get Inline keyboard"), BotCommand(command="menu", description="🌀 Get keyboards"),
] ]
# Команды для админов # Commands for admins
admin_commands = [ admin_commands = [
BotCommand(command="start", description="♻️ Restart bot"), 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="log", description="🖨 Get Logs"),
BotCommand(command="db", description="📦 Get Database"), BotCommand(command="db", description="📦 Get Database"),
] ]
# Установка команд # Set commands
async def set_commands(bot: Bot): async def set_commands(bot: Bot):
await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault()) await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
+1 -1
View File
@@ -5,7 +5,7 @@ from aiogram.types import Message
from tgbot.data.config import get_admins from tgbot.data.config import get_admins
# Проверка на админа # Filter on admin right
class IsAdmin(BaseFilter): class IsAdmin(BaseFilter):
async def __call__(self, message: Message) -> bool: async def __call__(self, message: Message) -> bool:
if message.from_user.id in get_admins(): if message.from_user.id in get_admins():
+4 -4
View File
@@ -5,24 +5,24 @@ import colorlog
from tgbot.data.config import PATH_LOGS 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_file = bot_logger.Formatter("%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s")
log_formatter_console = colorlog.ColoredFormatter( 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", "%(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", 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 = bot_logger.FileHandler(PATH_LOGS, "w", "utf-8")
file_handler.setFormatter(log_formatter_file) file_handler.setFormatter(log_formatter_file)
file_handler.setLevel(bot_logger.INFO) file_handler.setLevel(bot_logger.INFO)
# Логгирование в консоль # Logging in console
console_handler = bot_logger.StreamHandler() console_handler = bot_logger.StreamHandler()
console_handler.setFormatter(log_formatter_console) console_handler.setFormatter(log_formatter_console)
console_handler.setLevel(bot_logger.CRITICAL) console_handler.setLevel(bot_logger.CRITICAL)
# Подключение настроек логгирования # Connect logging settings
bot_logger.basicConfig( bot_logger.basicConfig(
format="%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s", format="%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s",
handlers=[ handlers=[
+2 -2
View File
@@ -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 from tgbot.utils.const_functions import get_date, send_admins
# Выполнение функции после запуска бота (рассылка админам о запуске бота) # Notification after run bot (for all admins)
async def startup_notify(bot: Bot): async def startup_notify(bot: Bot):
if len(get_admins()) >= 1 and BOT_STATUS_NOTIFICATION: if len(get_admins()) >= 1 and BOT_STATUS_NOTIFICATION:
await send_admins(bot, "<b>✅ Bot was started</b>") await send_admins(bot, "<b>✅ Bot was started</b>")
# Автоматические бэкапы БД # Autobackup Database
async def autobackup_admin(bot: Bot): async def autobackup_admin(bot: Bot):
for admin in get_admins(): for admin in get_admins():
try: try: