├── .gitignore ├── README.md ├── dj_bot.conf ├── main.py ├── requirements.txt ├── settings.ini └── tgbot ├── __init__.py ├── data ├── __init__.py └── config.py ├── database ├── __init__.py ├── db_helper.py ├── db_settings.py └── db_users.py ├── keyboards ├── __init__.py ├── inline_main.py ├── inline_misc.py ├── reply_main.py └── reply_misc.py ├── middlewares ├── __init__.py ├── middleware_throttling.py └── middleware_user.py ├── routers ├── __init__.py ├── admin │ ├── __init__.py │ └── admin_menu.py ├── main_errors.py ├── main_missed.py ├── main_start.py └── user │ ├── __init__.py │ └── user_menu.py ├── services ├── __init__.py └── api_session.py └── utils ├── __init__.py ├── const_functions.py ├── misc ├── __init__.py ├── bot_commands.py ├── bot_filters.py ├── bot_logging.py └── bot_models.py └── misc_functions.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | ### VirtualEnv template 91 | # Virtualenv 92 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 93 | [Bb]in 94 | [Ii]nclude 95 | [Ll]ib 96 | [Ll]ib64 97 | [Ll]ocal 98 | [Ss]cripts 99 | pyvenv.cfg 100 | .venv 101 | pip-selfcheck.json 102 | 103 | ### JetBrains template 104 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 105 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 106 | 107 | # User-specific stuff 108 | .idea/**/workspace.xml 109 | .idea/**/tasks.xml 110 | .idea/**/usage.statistics.xml 111 | .idea/**/dictionaries 112 | .idea/**/shelf 113 | 114 | # AWS User-specific 115 | .idea/**/aws.xml 116 | 117 | # Generated files 118 | .idea/**/contentModel.xml 119 | 120 | # Sensitive or high-churn files 121 | .idea/**/dataSources/ 122 | .idea/**/dataSources.ids 123 | .idea/**/dataSources.local.xml 124 | .idea/**/sqlDataSources.xml 125 | .idea/**/dynamic.xml 126 | .idea/**/uiDesigner.xml 127 | .idea/**/dbnavigator.xml 128 | 129 | # Gradle 130 | .idea/**/gradle.xml 131 | .idea/**/libraries 132 | 133 | # Gradle and Maven with auto-import 134 | # When using Gradle or Maven with auto-import, you should exclude module files, 135 | # since they will be recreated, and may cause churn. Uncomment if using 136 | auto-import. 137 | .idea/artifacts 138 | .idea/compiler.xml 139 | .idea/jarRepositories.xml 140 | .idea/modules.xml 141 | .idea/*.iml 142 | .idea/modules 143 | *.iml 144 | *.ipr 145 | 146 | # CMake 147 | cmake-build-*/ 148 | 149 | # Mongo Explorer plugin 150 | .idea/**/mongoSettings.xml 151 | 152 | # File-based project format 153 | *.iws 154 | 155 | # IntelliJ 156 | out/ 157 | 158 | # mpeltonen/sbt-idea plugin 159 | .idea_modules/ 160 | 161 | # JIRA plugin 162 | atlassian-ide-plugin.xml 163 | 164 | # Cursive Clojure plugin 165 | .idea/replstate.xml 166 | 167 | # SonarLint plugin 168 | .idea/sonarlint/ 169 | 170 | # Crashlytics plugin (for Android Studio and IntelliJ) 171 | com_crashlytics_export_strings.xml 172 | crashlytics.properties 173 | crashlytics-build.properties 174 | fabric.properties 175 | 176 | # Editor-based Rest Client 177 | .idea/httpRequests 178 | 179 | # Android studio 3.1+ serialized cache file 180 | .idea/caches/build_file_checksums.ser 181 | 182 | # idea folder, uncomment if you don't need it 183 | .idea 184 | 185 | # Users exceptions 186 | /other/ 187 | /tgbot/data/database.db 188 | /tgbot/data/logs.log 189 | settings.ini 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### [![Python](https://img.shields.io/badge/Python-3.10-blue)](https://www.python.org/downloads/release/python-399/) [![Aiogram](https://img.shields.io/badge/aiogram-3.4.1-blue)](https://pypi.org/project/aiogram/) 2 | 3 | # Template telegram bot for Aiogram 3 by Djimbo 4 | -------------------------------------------------------------------------------- /dj_bot.conf: -------------------------------------------------------------------------------- 1 | # supervisorctl 2 | [program:dj_bot] 3 | directory=/root/djimbo_template/ 4 | command=python3.10 main.py 5 | 6 | autostart=True 7 | autorestart=True 8 | 9 | stderr_logfile=/root/djimbo_template/tgbot/data/sv_log_err.log 10 | ; stderr_logfile_maxbytes=10MB 11 | stdout_logfile=/root/djimbo_template/tgbot/data/sv_log_out.log 12 | ; stdout_logfile_maxbytes=10MB -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import asyncio 3 | import os 4 | import sys 5 | 6 | import colorama 7 | from aiogram import Bot, Dispatcher 8 | from aiogram.client.default import DefaultBotProperties 9 | 10 | from tgbot.data.config import BOT_TOKEN, BOT_SCHEDULER, get_admins 11 | from tgbot.database.db_helper import create_dbx 12 | from tgbot.middlewares import register_all_middlwares 13 | from tgbot.routers import register_all_routers 14 | from tgbot.services.api_session import AsyncRequestSession 15 | from tgbot.utils.misc.bot_commands import set_commands 16 | from tgbot.utils.misc.bot_logging import bot_logger 17 | from tgbot.utils.misc_functions import autobackup_admin, startup_notify 18 | 19 | colorama.init() 20 | 21 | 22 | # Запуск шедулеров 23 | async def scheduler_start(bot): 24 | BOT_SCHEDULER.add_job(autobackup_admin, trigger="cron", hour=00, args=(bot,)) # Ежедневный Автобэкап в 00:00 25 | 26 | 27 | # Запуск бота и базовых функций 28 | async def main(): 29 | BOT_SCHEDULER.start() # Запуск Шедулера 30 | dp = Dispatcher() # Образ Диспетчера 31 | arSession = AsyncRequestSession() # Пул асинхронной сессии запросов 32 | bot = Bot( # Образ Бота 33 | token=BOT_TOKEN, 34 | default=DefaultBotProperties( 35 | parse_mode="HTML", 36 | ), 37 | ) 38 | 39 | register_all_middlwares(dp) # Регистрация всех мидлварей 40 | register_all_routers(dp) # Регистрация всех роутеров 41 | 42 | try: 43 | await set_commands(bot) # Установка пользовательских команд в боте 44 | await startup_notify(bot) # Уведомление админов о запуске бота 45 | await scheduler_start(bot) # Подключение шедулеров 46 | 47 | bot_logger.warning("BOT WAS STARTED") 48 | print(colorama.Fore.LIGHTYELLOW_EX + f"~~~~~ Bot was started - @{(await bot.get_me()).username} ~~~~~") 49 | print(colorama.Fore.LIGHTBLUE_EX + "~~~~~ TG developer - @djimbox ~~~~~") 50 | print(colorama.Fore.RESET) 51 | 52 | if len(get_admins()) == 0: print("***** ENTER ADMIN ID IN settings.ini *****") 53 | 54 | await bot.delete_webhook() # Удаление вебхуков, если они имеются 55 | await bot.get_updates(offset=-1) # Сброс пендинг апдейтов 56 | 57 | # Запуск бота (поллинга) 58 | await dp.start_polling( 59 | bot, 60 | arSession=arSession, 61 | allowed_updates=dp.resolve_used_update_types(), 62 | ) 63 | finally: 64 | await arSession.close() # Закрытие Асинхронной Сессии для прочих запросов 65 | await bot.session.close() # Закрытие сессии бота 66 | 67 | 68 | if __name__ == "__main__": 69 | create_dbx() # Генерация Базы Данных и Таблиц 70 | 71 | try: 72 | if sys.platform == 'win32': # Запуск на 32-х битных системах 73 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 74 | loop = asyncio.ProactorEventLoop() 75 | asyncio.set_event_loop(loop) 76 | loop.run_until_complete(main()) 77 | else: 78 | asyncio.run(main()) # Запуск на всех других системах 79 | except (KeyboardInterrupt, SystemExit): 80 | bot_logger.warning("Bot was stopped") 81 | finally: 82 | if sys.platform.startswith("win"): 83 | os.system("cls") 84 | else: 85 | os.system("clear") 86 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | APScheduler==3.10.4 2 | aiogram==3.5.0 3 | colorlog==6.8.2 4 | pytz==2024.1 5 | typing~=3.7.4.3 6 | aiohttp~=3.9.1 7 | cachetools==5.3.3 8 | colorama~=0.4.5 9 | aiofiles~=23.2.1 10 | pydantic~=2.5.2 -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [settings] 2 | bot_token= 3 | admin_id= 4 | -------------------------------------------------------------------------------- /tgbot/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tgbot/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/data/__init__.py -------------------------------------------------------------------------------- /tgbot/data/config.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import configparser 3 | 4 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 5 | 6 | # Токен бота 7 | BOT_TOKEN = configparser.ConfigParser() 8 | BOT_TOKEN.read("settings.ini") 9 | BOT_TOKEN = BOT_TOKEN['settings']['bot_token'].strip().replace(' ', '') 10 | 11 | # Пути к файлам 12 | PATH_DATABASE = "tgbot/data/database.db" # Путь к БД 13 | PATH_LOGS = "tgbot/data/logs.log" # Путь к Логам 14 | 15 | # Образы и конфиги 16 | BOT_STATUS_NOTIFICATION = True # Оповещение админам о запуске бота (True или False) 17 | BOT_TIMEZONE = "Europe/Moscow" # Временная зона бота 18 | BOT_SCHEDULER = AsyncIOScheduler(timezone=BOT_TIMEZONE) # Образ шедулера 19 | 20 | 21 | # Получение администраторов бота 22 | def get_admins() -> list[int]: 23 | read_admins = configparser.ConfigParser() 24 | read_admins.read('settings.ini') 25 | 26 | admins = read_admins['settings']['admin_id'].strip().replace(" ", "") 27 | 28 | if "," in admins: 29 | admins = admins.split(",") 30 | else: 31 | if len(admins) >= 1: 32 | admins = [admins] 33 | else: 34 | admins = [] 35 | 36 | while "" in admins: admins.remove("") 37 | while " " in admins: admins.remove(" ") 38 | while "," in admins: admins.remove(",") 39 | while "\r" in admins: admins.remove("\r") 40 | while "\n" in admins: admins.remove("\n") 41 | 42 | return list(map(int, admins)) 43 | -------------------------------------------------------------------------------- /tgbot/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/database/__init__.py -------------------------------------------------------------------------------- /tgbot/database/db_helper.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import sqlite3 3 | 4 | from tgbot.data.config import PATH_DATABASE 5 | from tgbot.utils.const_functions import ded 6 | 7 | 8 | # Преобразование полученного списка в словарь 9 | def dict_factory(cursor, row) -> dict: 10 | save_dict = {} 11 | 12 | for idx, col in enumerate(cursor.description): 13 | save_dict[col[0]] = row[idx] 14 | 15 | return save_dict 16 | 17 | 18 | # Форматирование запроса без аргументов 19 | def update_format(sql, parameters: dict) -> tuple[str, list]: 20 | values = ", ".join([ 21 | f"{item} = ?" for item in parameters 22 | ]) 23 | sql += f" {values}" 24 | 25 | return sql, list(parameters.values()) 26 | 27 | 28 | # Форматирование запроса с аргументами 29 | def update_format_where(sql, parameters: dict) -> tuple[str, list]: 30 | sql += " WHERE " 31 | 32 | sql += " AND ".join([ 33 | f"{item} = ?" for item in parameters 34 | ]) 35 | 36 | return sql, list(parameters.values()) 37 | 38 | 39 | ################################################################################ 40 | # Создание всех таблиц для БД 41 | def create_dbx(): 42 | with sqlite3.connect(PATH_DATABASE) as con: 43 | con.row_factory = dict_factory 44 | 45 | ############################################################ 46 | # Создание таблицы с хранением - пользователей 47 | if len(con.execute("PRAGMA table_info(storage_users)").fetchall()) == 7: 48 | print("DB was found(1/2)") 49 | else: 50 | con.execute( 51 | ded(f""" 52 | CREATE TABLE storage_users( 53 | increment INTEGER PRIMARY KEY AUTOINCREMENT, 54 | user_id INTEGER, 55 | user_login TEXT, 56 | user_name TEXT, 57 | user_surname TEXT, 58 | user_fullname TEXT, 59 | user_unix INTEGER 60 | ) 61 | """) 62 | ) 63 | print("DB was not found(1/2) | Creating...") 64 | 65 | # Создание таблицы с хранением - настроек 66 | if len(con.execute("PRAGMA table_info(storage_settings)").fetchall()) == 1: 67 | print("DB was found(2/2)") 68 | else: 69 | con.execute( 70 | ded(f""" 71 | CREATE TABLE storage_settings( 72 | status_work TEXT 73 | ) 74 | """) 75 | ) 76 | 77 | con.execute( 78 | ded(f""" 79 | INSERT INTO storage_settings( 80 | status_work 81 | ) 82 | VALUES (?) 83 | """), 84 | [ 85 | 'True', 86 | ] 87 | ) 88 | print("DB was not found(2/2) | Creating...") 89 | -------------------------------------------------------------------------------- /tgbot/database/db_settings.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import sqlite3 3 | 4 | from pydantic import BaseModel 5 | 6 | from tgbot.data.config import PATH_DATABASE 7 | from tgbot.database.db_helper import dict_factory, update_format 8 | 9 | 10 | # Модель таблицы 11 | class SettingsModel(BaseModel): 12 | status_work: str # Статус работы бота 13 | 14 | 15 | # Работа с настройками 16 | class Settingsx: 17 | storage_name = "storage_settings" 18 | 19 | # Получение записи 20 | @staticmethod 21 | def get() -> SettingsModel: 22 | with sqlite3.connect(PATH_DATABASE) as con: 23 | con.row_factory = dict_factory 24 | sql = f"SELECT * FROM {Settingsx.storage_name}" 25 | 26 | return SettingsModel(**con.execute(sql).fetchone()) 27 | 28 | # Редактирование записи 29 | @staticmethod 30 | def update(**kwargs): 31 | with sqlite3.connect(PATH_DATABASE) as con: 32 | con.row_factory = dict_factory 33 | sql = f"UPDATE {Settingsx.storage_name} SET" 34 | sql, parameters = update_format(sql, kwargs) 35 | 36 | con.execute(sql, parameters) 37 | -------------------------------------------------------------------------------- /tgbot/database/db_users.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import sqlite3 3 | 4 | from pydantic import BaseModel 5 | 6 | from tgbot.data.config import PATH_DATABASE 7 | from tgbot.database.db_helper import dict_factory, update_format_where, update_format 8 | from tgbot.utils.const_functions import get_unix, ded 9 | 10 | 11 | # Модель таблицы 12 | class UserModel(BaseModel): 13 | increment: int # Инкремент записи 14 | user_id: int # Айди пользователя 15 | user_login: str # Юзернейм пользователя 16 | user_name: str # Имя пользователя 17 | user_surname: str # Фамилия пользователя 18 | user_fullname: str # Полное имя + фамилия пользователя 19 | user_unix: int # Дата регистрации пользователя в боте (в UNIX времени) 20 | 21 | 22 | # Работа с юзером 23 | class Userx: 24 | storage_name = "storage_users" 25 | 26 | # Добавление записи 27 | @staticmethod 28 | def add( 29 | user_id: int, 30 | user_login: str, 31 | user_name: str, 32 | user_surname: str, 33 | user_fullname: str, 34 | ): 35 | user_unix = get_unix() 36 | 37 | with sqlite3.connect(PATH_DATABASE) as con: 38 | con.row_factory = dict_factory 39 | 40 | con.execute( 41 | ded(f""" 42 | INSERT INTO {Userx.storage_name} ( 43 | user_id, 44 | user_login, 45 | user_name, 46 | user_surname, 47 | user_fullname, 48 | user_unix 49 | ) VALUES (?, ?, ?, ?, ?, ?) 50 | """), 51 | [ 52 | user_id, 53 | user_login, 54 | user_name, 55 | user_surname, 56 | user_fullname, 57 | user_unix, 58 | ], 59 | ) 60 | 61 | # Получение записи 62 | @staticmethod 63 | def get(**kwargs) -> UserModel: 64 | with sqlite3.connect(PATH_DATABASE) as con: 65 | con.row_factory = dict_factory 66 | sql = f"SELECT * FROM {Userx.storage_name}" 67 | sql, parameters = update_format_where(sql, kwargs) 68 | 69 | response = con.execute(sql, parameters).fetchone() 70 | 71 | if response is not None: 72 | response = UserModel(**response) 73 | 74 | return response 75 | 76 | # Получение записей 77 | @staticmethod 78 | def gets(**kwargs) -> list[UserModel]: 79 | with sqlite3.connect(PATH_DATABASE) as con: 80 | con.row_factory = dict_factory 81 | sql = f"SELECT * FROM {Userx.storage_name}" 82 | sql, parameters = update_format_where(sql, kwargs) 83 | 84 | response = con.execute(sql, parameters).fetchall() 85 | 86 | if len(response) >= 1: 87 | response = [UserModel(**cache_object) for cache_object in response] 88 | 89 | return response 90 | 91 | # Получение всех записей 92 | @staticmethod 93 | def get_all() -> list[UserModel]: 94 | with sqlite3.connect(PATH_DATABASE) as con: 95 | con.row_factory = dict_factory 96 | sql = f"SELECT * FROM {Userx.storage_name}" 97 | 98 | response = con.execute(sql).fetchall() 99 | 100 | if len(response) >= 1: 101 | response = [UserModel(**cache_object) for cache_object in response] 102 | 103 | return response 104 | 105 | # Редактирование записи 106 | @staticmethod 107 | def update(user_id, **kwargs): 108 | with sqlite3.connect(PATH_DATABASE) as con: 109 | con.row_factory = dict_factory 110 | sql = f"UPDATE {Userx.storage_name} SET" 111 | sql, parameters = update_format(sql, kwargs) 112 | parameters.append(user_id) 113 | 114 | con.execute(sql + "WHERE user_id = ?", parameters) 115 | 116 | # Удаление записи 117 | @staticmethod 118 | def delete(**kwargs): 119 | with sqlite3.connect(PATH_DATABASE) as con: 120 | con.row_factory = dict_factory 121 | sql = f"DELETE FROM {Userx.storage_name}" 122 | sql, parameters = update_format_where(sql, kwargs) 123 | 124 | con.execute(sql, parameters) 125 | 126 | # Очистка всех записей 127 | @staticmethod 128 | def clear(): 129 | with sqlite3.connect(PATH_DATABASE) as con: 130 | con.row_factory = dict_factory 131 | sql = f"DELETE FROM {Userx.storage_name}" 132 | 133 | con.execute(sql) 134 | -------------------------------------------------------------------------------- /tgbot/keyboards/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/keyboards/__init__.py -------------------------------------------------------------------------------- /tgbot/keyboards/inline_main.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.types import InlineKeyboardMarkup 3 | from aiogram.utils.keyboard import InlineKeyboardBuilder 4 | 5 | from tgbot.data.config import get_admins 6 | from tgbot.utils.const_functions import ikb 7 | 8 | 9 | # Кнопки инлайн меню 10 | def menu_finl(user_id: int) -> InlineKeyboardMarkup: 11 | keyboard = InlineKeyboardBuilder() 12 | 13 | keyboard.row( 14 | ikb("User X", data="user_inline_x"), 15 | ikb("User 1", data="user_inline:user_btn"), 16 | ikb("User 2", data="..."), 17 | ) 18 | 19 | if user_id in get_admins(): 20 | keyboard.row( 21 | ikb("Admin X", data="admin_inline_x"), 22 | ikb("Admin 1", data="admin_inline:admin_btn"), 23 | ikb("Admin 2", data="unknown"), 24 | ) 25 | 26 | return keyboard.as_markup() 27 | -------------------------------------------------------------------------------- /tgbot/keyboards/inline_misc.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.utils.keyboard import InlineKeyboardBuilder 3 | 4 | from tgbot.utils.const_functions import ikb 5 | 6 | # Тестовые админ инлайн кнопки 7 | admin_inl = InlineKeyboardBuilder( 8 | ).row( 9 | ikb("Admin Inline 1", data="..."), 10 | ikb("Admin Inline 2", data="..."), 11 | ).row( 12 | ikb("Admin Inline 3", data="..."), 13 | ).as_markup() 14 | 15 | # Тестовые юзер инлайн кнопки 16 | user_inl = InlineKeyboardBuilder( 17 | ).row( 18 | ikb("User Inline 1", data="..."), 19 | ikb("User Inline 2", data="..."), 20 | ).row( 21 | ikb("User Inline 3", data="..."), 22 | ).as_markup() 23 | -------------------------------------------------------------------------------- /tgbot/keyboards/reply_main.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.types import ReplyKeyboardMarkup 3 | from aiogram.utils.keyboard import ReplyKeyboardBuilder 4 | 5 | from tgbot.data.config import get_admins 6 | from tgbot.utils.const_functions import rkb 7 | 8 | 9 | # Кнопки главного меню 10 | def menu_frep(user_id: int) -> ReplyKeyboardMarkup: 11 | keyboard = ReplyKeyboardBuilder() 12 | 13 | keyboard.row( 14 | rkb("User Inline"), rkb("User Reply"), 15 | ) 16 | 17 | if user_id in get_admins(): 18 | keyboard.row( 19 | rkb("Admin Inline"), rkb("Admin Reply"), 20 | ) 21 | 22 | return keyboard.as_markup(resize_keyboard=True) 23 | -------------------------------------------------------------------------------- /tgbot/keyboards/reply_misc.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder 3 | 4 | from tgbot.utils.const_functions import rkb 5 | 6 | # Тестовые админ реплай кнопки 7 | admin_rep = ReplyKeyboardBuilder( 8 | ).row( 9 | rkb("Admin Reply 1"), 10 | rkb("Admin Reply 2"), 11 | ).row( 12 | rkb("🔙 Main menu"), 13 | ).as_markup(resize_keyboard=True) 14 | 15 | # Тестовые юзер реплай кнопки 16 | user_rep = ReplyKeyboardBuilder( 17 | ).row( 18 | rkb("User Reply 1"), 19 | rkb("User Reply 2"), 20 | ).row( 21 | rkb("🔙 Main menu"), 22 | ).as_markup(resize_keyboard=True) 23 | -------------------------------------------------------------------------------- /tgbot/middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Dispatcher 3 | 4 | from tgbot.middlewares.middleware_user import ExistsUserMiddleware 5 | from tgbot.middlewares.middleware_throttling import ThrottlingMiddleware 6 | 7 | 8 | # Регистрация всех миддлварей 9 | def register_all_middlwares(dp: Dispatcher): 10 | dp.callback_query.outer_middleware(ExistsUserMiddleware()) 11 | dp.message.outer_middleware(ExistsUserMiddleware()) 12 | 13 | dp.message.middleware(ThrottlingMiddleware()) 14 | -------------------------------------------------------------------------------- /tgbot/middlewares/middleware_throttling.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import time 3 | from typing import Any, Awaitable, Callable, Dict, Union 4 | 5 | from aiogram import BaseMiddleware 6 | from aiogram.dispatcher.flags import get_flag 7 | from aiogram.types import Message, User 8 | from cachetools import TTLCache 9 | 10 | 11 | # Антиспам 12 | class ThrottlingMiddleware(BaseMiddleware): 13 | def __init__(self, default_rate: Union[int, float] = 0.5) -> None: 14 | self.default_rate = default_rate 15 | 16 | self.users = TTLCache(maxsize=10_000, ttl=600) 17 | 18 | async def __call__(self, handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]], event: Message, data): 19 | this_user: User = data.get("event_from_user") 20 | 21 | if get_flag(data, "rate") is not None: 22 | self.default_rate = get_flag(data, "rate") 23 | 24 | if self.default_rate == 0: 25 | return await handler(event, data) 26 | 27 | if this_user.id not in self.users: 28 | self.users[this_user.id] = { 29 | 'last_throttled': int(time.time()), 30 | 'count_throttled': 0, 31 | 'now_rate': self.default_rate, 32 | } 33 | 34 | return await handler(event, data) 35 | else: 36 | if int(time.time()) - self.users[this_user.id]['last_throttled'] >= self.users[this_user.id]['now_rate']: 37 | self.users.pop(this_user.id) 38 | 39 | return await handler(event, data) 40 | else: 41 | self.users[this_user.id]['last_throttled'] = int(time.time()) 42 | 43 | if self.users[this_user.id]['count_throttled'] == 0: 44 | self.users[this_user.id]['count_throttled'] = 1 45 | self.users[this_user.id]['now_rate'] = self.default_rate + 2 46 | 47 | return await handler(event, data) 48 | elif self.users[this_user.id]['count_throttled'] == 1: 49 | self.users[this_user.id]['count_throttled'] = 2 50 | self.users[this_user.id]['now_rate'] = self.default_rate + 3 51 | 52 | await event.reply( 53 | "❗ Пожалуйста, не спамьте.\n" 54 | "❗ Please, do not spam.", 55 | ) 56 | elif self.users[this_user.id]['count_throttled'] == 2: 57 | self.users[this_user.id]['count_throttled'] = 3 58 | self.users[this_user.id]['now_rate'] = self.default_rate + 5 59 | 60 | await event.reply( 61 | "❗ Бот не будет отвечать до прекращения спама.\n" 62 | "❗ The bot will not respond until the spam stops.", 63 | ) 64 | -------------------------------------------------------------------------------- /tgbot/middlewares/middleware_user.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import BaseMiddleware 3 | 4 | from tgbot.database.db_users import Userx 5 | from tgbot.utils.const_functions import clear_html 6 | 7 | 8 | # Проверка юзера в БД и его добавление 9 | class ExistsUserMiddleware(BaseMiddleware): 10 | async def __call__(self, handler, event, data): 11 | this_user = data.get("event_from_user") 12 | 13 | if not this_user.is_bot: 14 | get_user = Userx.get(user_id=this_user.id) 15 | 16 | user_id = this_user.id 17 | user_login = this_user.username 18 | user_name = clear_html(this_user.first_name) 19 | user_surname = clear_html(this_user.last_name) 20 | user_fullname = clear_html(this_user.first_name) 21 | user_language = this_user.language_code 22 | 23 | if user_login is None: user_login = "" 24 | if user_name is None: user_name = "" 25 | if user_surname is None: user_surname = "" 26 | if user_fullname is None: user_fullname = "" 27 | if user_language != "ru": user_language = "en" 28 | 29 | if len(user_surname) >= 1: user_fullname += f" {user_surname}" 30 | 31 | if get_user is None: 32 | Userx.add(user_id, user_login.lower(), user_name, user_surname, user_fullname) 33 | else: 34 | if user_name != get_user.user_name: 35 | Userx.update(get_user.user_id, user_name=user_name) 36 | 37 | if user_surname != get_user.user_surname: 38 | Userx.update(get_user.user_id, user_surname=user_surname) 39 | 40 | if user_fullname != get_user.user_fullname: 41 | Userx.update(get_user.user_id, user_fullname=user_fullname) 42 | 43 | if user_login.lower() != get_user.user_login: 44 | Userx.update(get_user.user_id, user_login=user_login.lower()) 45 | 46 | data['User'] = Userx.get(user_id=user_id) 47 | 48 | return await handler(event, data) 49 | -------------------------------------------------------------------------------- /tgbot/routers/__init__.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Dispatcher, F 3 | 4 | from tgbot.routers import main_errors, main_missed, main_start 5 | from tgbot.routers.admin import admin_menu 6 | from tgbot.routers.user import user_menu 7 | from tgbot.utils.misc.bot_filters import IsAdmin 8 | 9 | 10 | # Регистрация всех роутеров 11 | def register_all_routers(dp: Dispatcher): 12 | # Подключение фильтров 13 | main_errors.router.message.filter(F.chat.type == "private") 14 | main_start.router.message.filter(F.chat.type == "private") 15 | 16 | user_menu.router.message.filter(F.chat.type == "private") 17 | admin_menu.router.message.filter(F.chat.type == "private", IsAdmin()) 18 | 19 | main_missed.router.message.filter(F.chat.type == "private") 20 | 21 | # Подключение обязательных роутеров 22 | dp.include_router(main_errors.router) # Роутер ошибки 23 | dp.include_router(main_start.router) # Роутер основных команд 24 | 25 | # Подключение пользовательских роутеров (юзеров и админов) 26 | dp.include_router(user_menu.router) # Юзер роутер 27 | dp.include_router(admin_menu.router) # Админ роутер 28 | 29 | # Подключение обязательных роутеров 30 | dp.include_router(main_missed.router) # Роутер пропущенных апдейтов 31 | -------------------------------------------------------------------------------- /tgbot/routers/admin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/routers/admin/__init__.py -------------------------------------------------------------------------------- /tgbot/routers/admin/admin_menu.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import os 3 | 4 | import aiofiles 5 | from aiogram import Router, Bot, F 6 | from aiogram.filters import Command 7 | from aiogram.types import FSInputFile, Message, CallbackQuery 8 | from aiogram.utils.media_group import MediaGroupBuilder 9 | 10 | from tgbot.data.config import PATH_DATABASE, PATH_LOGS 11 | from tgbot.database.db_users import UserModel 12 | from tgbot.keyboards.inline_misc import admin_inl 13 | from tgbot.keyboards.reply_misc import admin_rep 14 | from tgbot.utils.const_functions import get_date 15 | from tgbot.utils.misc.bot_models import FSM, ARS 16 | 17 | router = Router(name=__name__) 18 | 19 | 20 | # Кнопка - Admin Inline 21 | @router.message(F.text == 'Admin Inline') 22 | async def admin_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 23 | await state.clear() 24 | 25 | await message.answer("Click Button - Admin Inline", reply_markup=admin_inl) 26 | 27 | 28 | # Кнопка - Admin Reply 29 | @router.message(F.text == 'Admin Reply') 30 | async def admin_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 31 | await state.clear() 32 | 33 | await message.answer("Click Button - Admin Reply", reply_markup=admin_rep) 34 | 35 | 36 | # Колбэк - Admin X 37 | @router.callback_query(F.data == 'admin_inline_x') 38 | async def admin_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 39 | await call.answer(f"Click Admin X") 40 | 41 | 42 | # Колбэк - Admin 43 | @router.callback_query(F.data.startswith('admin_inline:')) 44 | async def admin_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 45 | get_data = call.data.split(":")[1] 46 | 47 | await call.answer(f"Click Admin - {get_data}", True) 48 | 49 | 50 | # Получение Базы Данных 51 | @router.message(Command(commands=['db', 'database'])) 52 | async def admin_database(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 53 | await state.clear() 54 | 55 | await message.answer_document( 56 | FSInputFile(PATH_DATABASE), 57 | caption=f"📦 #BACKUP | {get_date()}", 58 | ) 59 | 60 | 61 | # Получение логов 62 | @router.message(Command(commands=['log', 'logs'])) 63 | async def admin_log(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 64 | await state.clear() 65 | 66 | media_group = MediaGroupBuilder( 67 | caption=f"🖨 #LOGS | {get_date(full=False)}", 68 | ) 69 | 70 | if os.path.isfile(PATH_LOGS): 71 | media_group.add_document(media=FSInputFile(PATH_LOGS)) 72 | 73 | if os.path.isfile("tgbot/data/sv_log_err.log"): 74 | media_group.add_document(media=FSInputFile("tgbot/data/sv_log_err.log")) 75 | 76 | if os.path.isfile("tgbot/data/sv_log_out.log"): 77 | media_group.add_document(media=FSInputFile("tgbot/data/sv_log_out.log")) 78 | 79 | await message.answer_media_group(media=media_group.build()) 80 | 81 | 82 | # Очистить логи 83 | @router.message(Command(commands=['clear_log', 'clear_logs', 'log_clear', 'logs_clear'])) 84 | async def admin_logs_clear(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 85 | await state.clear() 86 | 87 | if os.path.isfile(PATH_LOGS): 88 | async with aiofiles.open(PATH_LOGS, "w") as file: 89 | await file.write(f"{get_date()} | LOGS WAS CLEAR") 90 | 91 | if os.path.isfile("tgbot/data/sv_log_err.log"): 92 | async with aiofiles.open("tgbot/data/sv_log_err.log", "w") as file: 93 | await file.write(f"{get_date()} | LOGS WAS CLEAR") 94 | 95 | if os.path.isfile("tgbot/data/sv_log_out.log"): 96 | async with aiofiles.open("tgbot/data/sv_log_out.log", "w") as file: 97 | await file.write(f"{get_date()} | LOGS WAS CLEAR") 98 | 99 | await message.answer("🖨 The logs have been cleared") 100 | -------------------------------------------------------------------------------- /tgbot/routers/main_errors.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Router 3 | from aiogram.filters import ExceptionMessageFilter 4 | from aiogram.handlers import ErrorHandler 5 | 6 | from tgbot.utils.misc.bot_logging import bot_logger 7 | 8 | router = Router(name=__name__) 9 | 10 | 11 | # Ошибка с блокировкой бота пользователем 12 | # @router.errors(ExceptionTypeFilter(TelegramForbiddenError)) 13 | # class MyHandler(ErrorHandler): 14 | # async def handle(self): 15 | # ... 16 | 17 | 18 | # Ошибка с редактированием одинакового сообщения 19 | @router.errors(ExceptionMessageFilter( 20 | "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") 21 | ) 22 | class MyHandler(ErrorHandler): 23 | async def handle(self): 24 | bot_logger.exception( 25 | f"====================\n" 26 | f"Exception name: {self.exception_name}\n" 27 | f"Exception message: {self.exception_message}\n" 28 | f"====================" 29 | ) 30 | -------------------------------------------------------------------------------- /tgbot/routers/main_missed.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Router, Bot, F 3 | from aiogram.types import CallbackQuery, Message 4 | 5 | from tgbot.database.db_users import UserModel 6 | from tgbot.utils.const_functions import del_message 7 | from tgbot.utils.misc.bot_models import FSM, ARS 8 | 9 | router = Router(name=__name__) 10 | 11 | 12 | # Колбэк с удалением сообщения 13 | @router.callback_query(F.data == 'close_this') 14 | async def main_callback_close(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 15 | await del_message(call.message) 16 | 17 | 18 | # Колбэк с обработкой кнопки 19 | @router.callback_query(F.data == '...') 20 | async def main_callback_answer(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 21 | await call.answer(cache_time=30) 22 | 23 | 24 | # Колбэк с обработкой удаления сообщений потерявших стейт 25 | @router.callback_query() 26 | async def main_callback_missed(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 27 | await call.answer(f"❗️ Miss callback: {call.data}", True) 28 | 29 | 30 | # Обработка всех неизвестных команд 31 | @router.message() 32 | async def main_message_missed(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 33 | await message.answer( 34 | "♦️ Unknown command.\n" 35 | "♦️ Enter /start", 36 | ) 37 | -------------------------------------------------------------------------------- /tgbot/routers/main_start.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Router, Bot, F 3 | from aiogram.filters import Command 4 | from aiogram.types import Message 5 | 6 | from tgbot.database.db_users import UserModel 7 | from tgbot.keyboards.reply_main import menu_frep 8 | from tgbot.utils.const_functions import ded 9 | from tgbot.utils.misc.bot_models import FSM, ARS 10 | 11 | router = Router(name=__name__) 12 | 13 | 14 | # Открытие главного меню 15 | @router.message(F.text.in_(('🔙 Main menu', '🔙 Return'))) 16 | @router.message(Command(commands=['start'])) 17 | async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 18 | await state.clear() 19 | 20 | await message.answer( 21 | ded(f""" 22 | 🔸 Hello, {User.user_name} 23 | 🔸 Enter /start or /inline 24 | """), 25 | reply_markup=menu_frep(message.from_user.id), 26 | ) 27 | -------------------------------------------------------------------------------- /tgbot/routers/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/routers/user/__init__.py -------------------------------------------------------------------------------- /tgbot/routers/user/user_menu.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Router, Bot, F 3 | from aiogram.filters import Command 4 | from aiogram.types import Message, CallbackQuery 5 | 6 | from tgbot.database.db_users import UserModel 7 | from tgbot.keyboards.inline_main import menu_finl 8 | from tgbot.keyboards.inline_misc import user_inl 9 | from tgbot.keyboards.reply_misc import user_rep 10 | from tgbot.utils.misc.bot_models import FSM, ARS 11 | 12 | router = Router(name=__name__) 13 | 14 | 15 | # Кнопка - User Inline 16 | @router.message(F.text == 'User Inline') 17 | async def user_button_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 18 | await state.clear() 19 | 20 | await message.answer( 21 | "Click Button - User Inline", 22 | reply_markup=user_inl, 23 | ) 24 | 25 | 26 | # Кнопка - User Reply 27 | @router.message(F.text == 'User Reply') 28 | async def user_button_reply(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 29 | await state.clear() 30 | 31 | await message.answer( 32 | "Click Button - User Reply", 33 | reply_markup=user_rep, 34 | ) 35 | 36 | 37 | # Команда - /inline 38 | @router.message(Command(commands="inline")) 39 | async def user_command_inline(message: Message, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 40 | await state.clear() 41 | 42 | await message.answer( 43 | "Click command - /inline", 44 | reply_markup=menu_finl(message.from_user.id), 45 | ) 46 | 47 | 48 | # Колбэк - User X 49 | @router.callback_query(F.data == 'user_inline_x') 50 | async def user_callback_inline_x(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 51 | await call.answer(f"Click User X") 52 | 53 | 54 | # Колбэк - User 55 | @router.callback_query(F.data.startswith('user_inline:')) 56 | async def user_callback_inline(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS, User: UserModel): 57 | get_data = call.data.split(":")[1] 58 | 59 | await call.answer(f"Click User - {get_data}", True) 60 | -------------------------------------------------------------------------------- /tgbot/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/services/__init__.py -------------------------------------------------------------------------------- /tgbot/services/api_session.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from typing import Optional 3 | 4 | import aiohttp 5 | 6 | 7 | # In handler 8 | # session = await arSession.get_session() 9 | # response = await session.get(...) 10 | # response = await session.post(...) 11 | 12 | # Асинхронная сессия для запросов 13 | class AsyncRequestSession: 14 | def __init__(self) -> None: 15 | self._session: Optional[aiohttp.ClientSession] = None 16 | 17 | # Получение сессии 18 | async def get_session(self) -> aiohttp.ClientSession: 19 | if self._session is None: 20 | new_session = aiohttp.ClientSession() 21 | self._session = new_session 22 | 23 | return self._session 24 | 25 | # Закрытие сессии 26 | async def close(self) -> None: 27 | if self._session is None: 28 | return None 29 | 30 | await self._session.close() 31 | -------------------------------------------------------------------------------- /tgbot/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/utils/__init__.py -------------------------------------------------------------------------------- /tgbot/utils/const_functions.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import random 3 | import time 4 | import uuid 5 | from datetime import datetime 6 | from typing import Union 7 | 8 | import pytz 9 | from aiogram import Bot 10 | from aiogram.types import InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup, \ 11 | ReplyKeyboardMarkup 12 | 13 | from tgbot.data.config import get_admins, BOT_TIMEZONE 14 | 15 | 16 | ######################################## AIOGRAM ######################################## 17 | # Генерация реплай кнопки 18 | def rkb(text: str) -> KeyboardButton: 19 | return KeyboardButton(text=text) 20 | 21 | 22 | # Генерация инлайн кнопки 23 | def ikb(text: str, data: str = None, url: str = None, switch: str = None, web: str = None) -> InlineKeyboardButton: 24 | if data is not None: 25 | return InlineKeyboardButton(text=text, callback_data=data) 26 | elif url is not None: 27 | return InlineKeyboardButton(text=text, url=url) 28 | elif switch is not None: 29 | return InlineKeyboardButton(text=text, switch_inline_query=switch) 30 | elif web is not None: 31 | return InlineKeyboardButton(text=text, web_app=WebAppInfo(url=web)) 32 | 33 | 34 | # Удаление сообщения с обработкой ошибки от телеграма 35 | async def del_message(message: Message): 36 | try: 37 | await message.delete() 38 | except: 39 | ... 40 | 41 | 42 | # Умная отправка сообщений (автоотправка сообщения с фото или без) 43 | async def smart_message( 44 | bot: Bot, 45 | user_id: int, 46 | text: str, 47 | keyboard: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup] = None, 48 | photo: Union[str, None] = None, 49 | ): 50 | if photo is not None and photo.title() != "None": 51 | await bot.send_photo( 52 | chat_id=user_id, 53 | photo=photo, 54 | caption=text, 55 | reply_markup=keyboard, 56 | ) 57 | else: 58 | await bot.send_message( 59 | chat_id=user_id, 60 | text=text, 61 | reply_markup=keyboard, 62 | ) 63 | 64 | 65 | # Отправка сообщения всем админам 66 | async def send_admins(bot: Bot, text: str, markup=None, not_me=0): 67 | for admin in get_admins(): 68 | try: 69 | if str(admin) != str(not_me): 70 | await bot.send_message( 71 | admin, 72 | text, 73 | reply_markup=markup, 74 | disable_web_page_preview=True, 75 | ) 76 | except: 77 | ... 78 | 79 | 80 | ######################################## ПРОЧЕЕ ######################################## 81 | # Удаление отступов в многострочной строке ("""text""") 82 | def ded(get_text: str) -> str: 83 | if get_text is not None: 84 | split_text = get_text.split("\n") 85 | if split_text[0] == "": split_text.pop(0) 86 | if split_text[-1] == "": split_text.pop() 87 | save_text = [] 88 | 89 | for text in split_text: 90 | while text.startswith(" "): 91 | text = text[1:].strip() 92 | 93 | save_text.append(text) 94 | get_text = "\n".join(save_text) 95 | else: 96 | get_text = "" 97 | 98 | return get_text 99 | 100 | 101 | # Очистка текста от HTML тэгов ('test' -> *b*test*/b*) 102 | def clear_html(get_text: str) -> str: 103 | if get_text is not None: 104 | if "" in get_text: get_text = get_text.replace(">", "*") 107 | else: 108 | get_text = "" 109 | 110 | return get_text 111 | 112 | 113 | # Очистка пробелов в списке (['', 1, ' ', 2] -> [1, 2]) 114 | def clear_list(get_list: list) -> list: 115 | while "" in get_list: get_list.remove("") 116 | while " " in get_list: get_list.remove(" ") 117 | while "." in get_list: get_list.remove(".") 118 | while "," in get_list: get_list.remove(",") 119 | while "\r" in get_list: get_list.remove("\r") 120 | while "\n" in get_list: get_list.remove("\n") 121 | 122 | return get_list 123 | 124 | 125 | # Разбив списка на несколько частей ([1, 2, 3, 4] 2 -> [[1, 2], [3, 4]]) 126 | def split_list(get_list: list, count: int) -> list[list]: 127 | return [get_list[i:i + count] for i in range(0, len(get_list), count)] 128 | 129 | 130 | # Получение текущей даты (True - дата с временем, False - дата без времени) 131 | def get_date(full: bool = True) -> str: 132 | if full: 133 | return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S") 134 | else: 135 | return datetime.now(pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y") 136 | 137 | 138 | # Получение текущего unix времени (True - время в наносекундах, False - время в секундах) 139 | def get_unix(full: bool = False) -> int: 140 | if full: 141 | return time.time_ns() 142 | else: 143 | return int(time.time()) 144 | 145 | 146 | # Конвертация unix в дату и даты в unix 147 | def convert_date(from_time, full=True, second=True) -> Union[str, int]: 148 | from tgbot.data.config import BOT_TIMEZONE 149 | 150 | if "-" in str(from_time): 151 | from_time = from_time.replace("-", ".") 152 | 153 | if str(from_time).isdigit(): 154 | if full: 155 | to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M:%S") 156 | elif second: 157 | to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y %H:%M") 158 | else: 159 | to_time = datetime.fromtimestamp(from_time, pytz.timezone(BOT_TIMEZONE)).strftime("%d.%m.%Y") 160 | else: 161 | if " " in str(from_time): 162 | cache_time = from_time.split(" ") 163 | 164 | if ":" in cache_time[0]: 165 | cache_date = cache_time[1].split(".") 166 | cache_time = cache_time[0].split(":") 167 | else: 168 | cache_date = cache_time[0].split(".") 169 | cache_time = cache_time[1].split(":") 170 | 171 | if len(cache_date[0]) == 4: 172 | x_year, x_month, x_day = cache_date[0], cache_date[1], cache_date[2] 173 | else: 174 | x_year, x_month, x_day = cache_date[2], cache_date[1], cache_date[0] 175 | 176 | x_hour, x_minute, x_second = cache_time[0], cache_time[1], cache_time[2] 177 | 178 | from_time = f"{x_day}.{x_month}.{x_year} {x_hour}:{x_minute}:{x_second}" 179 | else: 180 | cache_date = from_time.split(".") 181 | 182 | if len(cache_date[0]) == 4: 183 | x_year, x_month, x_day = cache_date[0], cache_date[1], cache_date[2] 184 | else: 185 | x_year, x_month, x_day = cache_date[2], cache_date[1], cache_date[0] 186 | 187 | from_time = f"{x_day}.{x_month}.{x_year}" 188 | 189 | if " " in str(from_time): 190 | to_time = int(datetime.strptime(from_time, "%d.%m.%Y %H:%M:%S").timestamp()) 191 | else: 192 | to_time = int(datetime.strptime(from_time, "%d.%m.%Y").timestamp()) 193 | 194 | return to_time 195 | 196 | 197 | # Генерация уникального айди 198 | def gen_id(len_id: int = 16) -> int: 199 | mac_address = uuid.getnode() 200 | time_unix = int(str(time.time_ns())[:len_id]) 201 | random_int = int(''.join(random.choices('0123456789', k=len_id))) 202 | 203 | return mac_address + time_unix + random_int 204 | 205 | 206 | # Генерация пароля | default, number, letter, onechar 207 | def gen_password(len_password: int = 16, type_password: str = "default") -> str: 208 | if type_password == "default": 209 | char_password = list("1234567890abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ") 210 | elif type_password == "letter": 211 | char_password = list("abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ") 212 | elif type_password == "number": 213 | char_password = list("1234567890") 214 | elif type_password == "onechar": 215 | char_password = list("1234567890") 216 | 217 | random.shuffle(char_password) 218 | random_chars = "".join([random.choice(char_password) for x in range(len_password)]) 219 | 220 | if type_password == "onechar": 221 | random_chars = f"{random.choice('abcdefghigklmnopqrstuvyxwzABCDEFGHIGKLMNOPQRSTUVYXWZ')}{random_chars[1:]}" 222 | 223 | return random_chars 224 | 225 | 226 | # Дополнение к числу корректного времени (1 -> 1 день, 3 -> 3 дня) 227 | def convert_times(get_time: int, get_type: str = "day") -> str: 228 | get_time = int(get_time) 229 | if get_time < 0: get_time = 0 230 | 231 | if get_type == "second": 232 | get_list = ['секунда', 'секунды', 'секунд'] 233 | elif get_type == "minute": 234 | get_list = ['минута', 'минуты', 'минут'] 235 | elif get_type == "hour": 236 | get_list = ['час', 'часа', 'часов'] 237 | elif get_type == "day": 238 | get_list = ['день', 'дня', 'дней'] 239 | elif get_type == "month": 240 | get_list = ['месяц', 'месяца', 'месяцев'] 241 | else: 242 | get_list = ['год', 'года', 'лет'] 243 | 244 | if get_time % 10 == 1 and get_time % 100 != 11: 245 | count = 0 246 | elif 2 <= get_time % 10 <= 4 and (get_time % 100 < 10 or get_time % 100 >= 20): 247 | count = 1 248 | else: 249 | count = 2 250 | 251 | return f"{get_time} {get_list[count]}" 252 | 253 | 254 | # Проверка на булевый тип 255 | def is_bool(value: Union[bool, str, int]) -> bool: 256 | value = str(value).lower() 257 | 258 | if value in ('y', 'yes', 't', 'true', 'on', '1'): 259 | return True 260 | elif value in ('n', 'no', 'f', 'false', 'off', '0'): 261 | return False 262 | else: 263 | raise ValueError(f"invalid truth value {value}") 264 | 265 | 266 | ######################################## ЧИСЛА ######################################## 267 | # Преобразование экспоненциальных чисел в читаемый вид (1e-06 -> 0.000001) 268 | def snum(amount: Union[int, float], remains: int = 2) -> str: 269 | format_str = "{:." + str(remains) + "f}" 270 | str_amount = format_str.format(float(amount)) 271 | 272 | if remains != 0: 273 | if "." in str_amount: 274 | remains_find = str_amount.find(".") 275 | remains_save = remains_find + 8 - (8 - remains) + 1 276 | 277 | str_amount = str_amount[:remains_save] 278 | 279 | if "." in str(str_amount): 280 | while str(str_amount).endswith('0'): str_amount = str(str_amount)[:-1] 281 | 282 | if str(str_amount).endswith('.'): str_amount = str(str_amount)[:-1] 283 | 284 | return str(str_amount) 285 | 286 | 287 | # Конвертация любого числа в вещественное, с удалением нулей в конце (remains - округление) 288 | def to_float(get_number, remains: int = 2) -> Union[int, float]: 289 | if "," in str(get_number): 290 | get_number = str(get_number).replace(",", ".") 291 | 292 | if "." in str(get_number): 293 | get_last = str(get_number).split(".") 294 | 295 | if str(get_last[1]).endswith("0"): 296 | while True: 297 | if str(get_number).endswith("0"): 298 | get_number = str(get_number)[:-1] 299 | else: 300 | break 301 | 302 | get_number = round(float(get_number), remains) 303 | 304 | str_number = snum(get_number) 305 | if "." in str_number: 306 | if str_number.split(".")[1] == "0": 307 | get_number = int(get_number) 308 | else: 309 | get_number = float(get_number) 310 | else: 311 | get_number = int(get_number) 312 | 313 | return get_number 314 | 315 | 316 | # Конвертация вещественного числа в целочисленное 317 | def to_int(get_number: float) -> int: 318 | if "," in get_number: 319 | get_number = str(get_number).replace(",", ".") 320 | 321 | get_number = int(round(float(get_number))) 322 | 323 | return get_number 324 | 325 | 326 | # Проверка ввода на число 327 | def is_number(get_number: Union[str, int, float]) -> bool: 328 | if str(get_number).isdigit(): 329 | return True 330 | else: 331 | if "," in str(get_number): get_number = str(get_number).replace(",", ".") 332 | 333 | try: 334 | float(get_number) 335 | return True 336 | except ValueError: 337 | return False 338 | 339 | 340 | # Преобразование числа в читаемый вид (123456789 -> 123 456 789) 341 | def format_rate(amount: Union[float, int], around: int = 2) -> str: 342 | if "," in str(amount): amount = float(str(amount).replace(",", ".")) 343 | if " " in str(amount): amount = float(str(amount).replace(" ", "")) 344 | amount = str(round(amount, around)) 345 | 346 | out_amount, save_remains = [], "" 347 | 348 | if "." in amount: save_remains = amount.split(".")[1] 349 | save_amount = [char for char in str(int(float(amount)))] 350 | 351 | if len(save_amount) % 3 != 0: 352 | if (len(save_amount) - 1) % 3 == 0: 353 | out_amount.extend([save_amount[0]]) 354 | save_amount.pop(0) 355 | elif (len(save_amount) - 2) % 3 == 0: 356 | out_amount.extend([save_amount[0], save_amount[1]]) 357 | save_amount.pop(1) 358 | save_amount.pop(0) 359 | else: 360 | print("Error 4388326") 361 | 362 | for x, char in enumerate(save_amount): 363 | if x % 3 == 0: out_amount.append(" ") 364 | out_amount.append(char) 365 | 366 | response = "".join(out_amount).strip() + "." + save_remains 367 | 368 | if response.endswith("."): 369 | response = response[:-1] 370 | 371 | return response 372 | -------------------------------------------------------------------------------- /tgbot/utils/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djimboy/djimbo_template_aio3/9a48d6f60f7be596087ae24a154f9f8807b1f188/tgbot/utils/misc/__init__.py -------------------------------------------------------------------------------- /tgbot/utils/misc/bot_commands.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Bot 3 | from aiogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeDefault 4 | 5 | from tgbot.data.config import get_admins 6 | 7 | # Команды для юзеров 8 | user_commands = [ 9 | BotCommand(command="start", description="♻️ Restart bot"), 10 | BotCommand(command="inline", description="🌀 Get Inline keyboard"), 11 | ] 12 | 13 | # Команды для админов 14 | admin_commands = [ 15 | BotCommand(command="start", description="♻️ Restart bot"), 16 | BotCommand(command="inline", description="🌀 Get Inline keyboard"), 17 | BotCommand(command="log", description="🖨 Get Logs"), 18 | BotCommand(command="db", description="📦 Get Database"), 19 | ] 20 | 21 | 22 | # Установка команд 23 | async def set_commands(bot: Bot): 24 | await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault()) 25 | 26 | for admin in get_admins(): 27 | try: 28 | await bot.set_my_commands(admin_commands, scope=BotCommandScopeChat(chat_id=admin)) 29 | except: 30 | ... 31 | -------------------------------------------------------------------------------- /tgbot/utils/misc/bot_filters.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.filters import BaseFilter 3 | from aiogram.types import Message 4 | 5 | from tgbot.data.config import get_admins 6 | 7 | 8 | # Проверка на админа 9 | class IsAdmin(BaseFilter): 10 | async def __call__(self, message: Message) -> bool: 11 | if message.from_user.id in get_admins(): 12 | return True 13 | else: 14 | return False 15 | -------------------------------------------------------------------------------- /tgbot/utils/misc/bot_logging.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | import logging as bot_logger 3 | 4 | import colorlog 5 | 6 | from tgbot.data.config import PATH_LOGS 7 | 8 | # Формат логгирования 9 | log_formatter_file = bot_logger.Formatter("%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s") 10 | log_formatter_console = colorlog.ColoredFormatter( 11 | "%(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", 12 | datefmt="%d-%m-%Y %H:%M:%S", 13 | ) 14 | 15 | # Логгирование в файл logs.log 16 | file_handler = bot_logger.FileHandler(PATH_LOGS, "w", "utf-8") 17 | file_handler.setFormatter(log_formatter_file) 18 | file_handler.setLevel(bot_logger.INFO) 19 | 20 | # Логгирование в консоль 21 | console_handler = bot_logger.StreamHandler() 22 | console_handler.setFormatter(log_formatter_console) 23 | console_handler.setLevel(bot_logger.CRITICAL) 24 | 25 | # Подключение настроек логгирования 26 | bot_logger.basicConfig( 27 | format="%(levelname)s | %(asctime)s | %(filename)s:%(lineno)d | %(message)s", 28 | handlers=[ 29 | file_handler, 30 | console_handler 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /tgbot/utils/misc/bot_models.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram.fsm.context import FSMContext 3 | 4 | from tgbot.services.api_session import AsyncRequestSession 5 | 6 | FSM = FSMContext 7 | ARS = AsyncRequestSession 8 | -------------------------------------------------------------------------------- /tgbot/utils/misc_functions.py: -------------------------------------------------------------------------------- 1 | # - *- coding: utf- 8 - *- 2 | from aiogram import Bot 3 | from aiogram.types import FSInputFile 4 | 5 | from tgbot.data.config import get_admins, PATH_DATABASE, BOT_STATUS_NOTIFICATION 6 | from tgbot.utils.const_functions import get_date, send_admins 7 | 8 | 9 | # Выполнение функции после запуска бота (рассылка админам о запуске бота) 10 | async def startup_notify(bot: Bot): 11 | if len(get_admins()) >= 1 and BOT_STATUS_NOTIFICATION: 12 | await send_admins(bot, "✅ Bot was started") 13 | 14 | 15 | # Автоматические бэкапы БД 16 | async def autobackup_admin(bot: Bot): 17 | for admin in get_admins(): 18 | try: 19 | await bot.send_document( 20 | admin, 21 | FSInputFile(PATH_DATABASE), 22 | caption=f"📦 #AUTOBACKUP | {get_date()}", 23 | ) 24 | except: 25 | ... 26 | --------------------------------------------------------------------------------