├── .env.example ├── .gitignore ├── .idea ├── .gitignore ├── TestProject.iml ├── dataSources.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── BotCore ├── __init__.py ├── handlers │ ├── __init__.py │ ├── main_router.py │ ├── questioning.py │ └── start_menu.py ├── keyboards │ ├── __init__.py │ └── ikb.py ├── middlewares │ ├── __init__.py │ └── update_users.py ├── states │ ├── __init__.py │ └── chat_with_user.py └── utils │ ├── __init__.py │ └── photos_manager.py ├── General ├── __init__.py ├── assets │ └── main.jpg ├── cfg.py └── database.py ├── README.md ├── app.py ├── loader.py └── requirements.txt /.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN= 2 | 3 | DB_HOST=127.0.0.1 4 | DB_PORT=5432 5 | DB_USER= 6 | DB_PASSWORD= 7 | DB_NAME=TestProject -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### venv template 2 | # Virtualenv 3 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 4 | .Python 5 | [Bb]in 6 | [Ii]nclude 7 | [Ll]ib 8 | [Ll]ib64 9 | [Ll]ocal 10 | [Ss]cripts 11 | pyvenv.cfg 12 | .venv 13 | pip-selfcheck.json 14 | 15 | ### PyCharm+iml template 16 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 17 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 18 | 19 | # User-specific stuff 20 | .idea/**/workspace.xml 21 | .idea/**/tasks.xml 22 | .idea/**/usage.statistics.xml 23 | .idea/**/dictionaries 24 | .idea/**/shelf 25 | 26 | # AWS User-specific 27 | .idea/**/aws.xml 28 | 29 | # Generated files 30 | .idea/**/contentModel.xml 31 | 32 | # Sensitive or high-churn files 33 | .idea/**/dataSources/ 34 | .idea/**/dataSources.ids 35 | .idea/**/dataSources.local.xml 36 | .idea/**/sqlDataSources.xml 37 | .idea/**/dynamic.xml 38 | .idea/**/uiDesigner.xml 39 | .idea/**/dbnavigator.xml 40 | 41 | # Gradle 42 | .idea/**/gradle.xml 43 | .idea/**/libraries 44 | 45 | # Gradle and Maven with auto-import 46 | # When using Gradle or Maven with auto-import, you should exclude module files, 47 | # since they will be recreated, and may cause churn. Uncomment if using 48 | # auto-import. 49 | # .idea/artifacts 50 | # .idea/compiler.xml 51 | # .idea/jarRepositories.xml 52 | # .idea/modules.xml 53 | # .idea/*.iml 54 | # .idea/modules 55 | # *.iml 56 | # *.ipr 57 | 58 | # CMake 59 | cmake-build-*/ 60 | 61 | # Mongo Explorer plugin 62 | .idea/**/mongoSettings.xml 63 | 64 | # File-based project format 65 | *.iws 66 | 67 | # IntelliJ 68 | out/ 69 | 70 | # mpeltonen/sbt-idea plugin 71 | .idea_modules/ 72 | 73 | # JIRA plugin 74 | atlassian-ide-plugin.xml 75 | 76 | # Cursive Clojure plugin 77 | .idea/replstate.xml 78 | 79 | # SonarLint plugin 80 | .idea/sonarlint/ 81 | 82 | # Crashlytics plugin (for Android Studio and IntelliJ) 83 | com_crashlytics_export_strings.xml 84 | crashlytics.properties 85 | crashlytics-build.properties 86 | fabric.properties 87 | 88 | # Editor-based Rest Client 89 | .idea/httpRequests 90 | 91 | # Android studio 3.1+ serialized cache file 92 | .idea/caches/build_file_checksums.ser 93 | 94 | ### JetBrains template 95 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 96 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 97 | 98 | # User-specific stuff 99 | 100 | # AWS User-specific 101 | 102 | # Generated files 103 | 104 | # Sensitive or high-churn files 105 | 106 | # Gradle 107 | 108 | # Gradle and Maven with auto-import 109 | # When using Gradle or Maven with auto-import, you should exclude module files, 110 | # since they will be recreated, and may cause churn. Uncomment if using 111 | # auto-import. 112 | # .idea/artifacts 113 | # .idea/compiler.xml 114 | # .idea/jarRepositories.xml 115 | # .idea/modules.xml 116 | # .idea/*.iml 117 | # .idea/modules 118 | # *.iml 119 | # *.ipr 120 | 121 | # CMake 122 | 123 | # Mongo Explorer plugin 124 | 125 | # File-based project format 126 | 127 | # IntelliJ 128 | 129 | # mpeltonen/sbt-idea plugin 130 | 131 | # JIRA plugin 132 | 133 | # Cursive Clojure plugin 134 | 135 | # SonarLint plugin 136 | 137 | # Crashlytics plugin (for Android Studio and IntelliJ) 138 | 139 | # Editor-based Rest Client 140 | 141 | # Android studio 3.1+ serialized cache file 142 | 143 | ### PyCharm template 144 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 145 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 146 | 147 | # User-specific stuff 148 | 149 | # AWS User-specific 150 | 151 | # Generated files 152 | 153 | # Sensitive or high-churn files 154 | 155 | # Gradle 156 | 157 | # Gradle and Maven with auto-import 158 | # When using Gradle or Maven with auto-import, you should exclude module files, 159 | # since they will be recreated, and may cause churn. Uncomment if using 160 | # auto-import. 161 | # .idea/artifacts 162 | # .idea/compiler.xml 163 | # .idea/jarRepositories.xml 164 | # .idea/modules.xml 165 | # .idea/*.iml 166 | # .idea/modules 167 | # *.iml 168 | # *.ipr 169 | 170 | # CMake 171 | 172 | # Mongo Explorer plugin 173 | 174 | # File-based project format 175 | 176 | # IntelliJ 177 | 178 | # mpeltonen/sbt-idea plugin 179 | 180 | # JIRA plugin 181 | 182 | # Cursive Clojure plugin 183 | 184 | # SonarLint plugin 185 | 186 | # Crashlytics plugin (for Android Studio and IntelliJ) 187 | 188 | # Editor-based Rest Client 189 | 190 | # Android studio 3.1+ serialized cache file 191 | 192 | ### PythonVanilla template 193 | # Byte-compiled / optimized / DLL files 194 | __pycache__/ 195 | *.py[cod] 196 | *$py.class 197 | 198 | # C extensions 199 | *.so 200 | 201 | # Distribution / packaging 202 | build/ 203 | develop-eggs/ 204 | dist/ 205 | downloads/ 206 | eggs/ 207 | .eggs/ 208 | lib/ 209 | lib64/ 210 | parts/ 211 | sdist/ 212 | var/ 213 | wheels/ 214 | share/python-wheels/ 215 | *.egg-info/ 216 | .installed.cfg 217 | *.egg 218 | MANIFEST 219 | 220 | # Installer logs 221 | pip-log.txt 222 | pip-delete-this-directory.txt 223 | 224 | # Unit test / coverage reports 225 | htmlcov/ 226 | .tox/ 227 | .nox/ 228 | .coverage 229 | .coverage.* 230 | .cache 231 | nosetests.xml 232 | coverage.xml 233 | *.cover 234 | *.py,cover 235 | .hypothesis/ 236 | .pytest_cache/ 237 | cover/ 238 | 239 | # Translations 240 | *.mo 241 | *.pot 242 | 243 | # pyenv 244 | # For a library or package, you might want to ignore these files since the code is 245 | # intended to run in multiple environments; otherwise, check them in: 246 | # .python-version 247 | 248 | # pipenv 249 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 250 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 251 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 252 | # install all needed dependencies. 253 | #Pipfile.lock 254 | 255 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 256 | __pypackages__/ 257 | 258 | 259 | ### VirtualEnv template 260 | # Virtualenv 261 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 262 | 263 | ### Python template 264 | # Byte-compiled / optimized / DLL files 265 | 266 | # C extensions 267 | 268 | # Distribution / packaging 269 | 270 | # PyInstaller 271 | # Usually these files are written by a python script from a template 272 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 273 | *.manifest 274 | *.spec 275 | 276 | # Installer logs 277 | 278 | # Unit test / coverage reports 279 | 280 | # Translations 281 | 282 | # Django stuff: 283 | *.log 284 | local_settings.py 285 | db.sqlite3 286 | db.sqlite3-journal 287 | 288 | # Flask stuff: 289 | instance/ 290 | .webassets-cache 291 | 292 | # Scrapy stuff: 293 | .scrapy 294 | 295 | # Sphinx documentation 296 | docs/_build/ 297 | 298 | # PyBuilder 299 | .pybuilder/ 300 | target/ 301 | 302 | # Jupyter Notebook 303 | .ipynb_checkpoints 304 | 305 | # IPython 306 | profile_default/ 307 | ipython_config.py 308 | 309 | # pyenv 310 | # For a library or package, you might want to ignore these files since the code is 311 | # intended to run in multiple environments; otherwise, check them in: 312 | # .python-version 313 | 314 | # pipenv 315 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 316 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 317 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 318 | # install all needed dependencies. 319 | #Pipfile.lock 320 | 321 | # poetry 322 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 323 | # This is especially recommended for binary packages to ensure reproducibility, and is more 324 | # commonly ignored for libraries. 325 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 326 | #poetry.lock 327 | 328 | # pdm 329 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 330 | #pdm.lock 331 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 332 | # in version control. 333 | # https://pdm.fming.dev/#use-with-ide 334 | .pdm.toml 335 | 336 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 337 | 338 | # Celery stuff 339 | celerybeat-schedule 340 | celerybeat.pid 341 | 342 | # SageMath parsed files 343 | *.sage.py 344 | 345 | # Environments 346 | .env 347 | env/ 348 | venv/ 349 | ENV/ 350 | env.bak/ 351 | venv.bak/ 352 | 353 | # Spyder project settings 354 | .spyderproject 355 | .spyproject 356 | 357 | # Rope project settings 358 | .ropeproject 359 | 360 | # mkdocs documentation 361 | /site 362 | 363 | # mypy 364 | .mypy_cache/ 365 | .dmypy.json 366 | dmypy.json 367 | 368 | # Pyre type checker 369 | .pyre/ 370 | 371 | # pytype static type analyzer 372 | .pytype/ 373 | 374 | # Cython debug symbols 375 | cython_debug/ 376 | 377 | # PyCharm 378 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 379 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 380 | # and can be added to the global gitignore or merged into this file. For a more nuclear 381 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 382 | #.idea/ 383 | 384 | ### PyCharm+all template 385 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 386 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 387 | 388 | # User-specific stuff 389 | 390 | # AWS User-specific 391 | 392 | # Generated files 393 | 394 | # Sensitive or high-churn files 395 | 396 | # Gradle 397 | 398 | # Gradle and Maven with auto-import 399 | # When using Gradle or Maven with auto-import, you should exclude module files, 400 | # since they will be recreated, and may cause churn. Uncomment if using 401 | # auto-import. 402 | # .idea/artifacts 403 | # .idea/compiler.xml 404 | # .idea/jarRepositories.xml 405 | # .idea/modules.xml 406 | # .idea/*.iml 407 | # .idea/modules 408 | # *.iml 409 | # *.ipr 410 | 411 | # CMake 412 | 413 | # Mongo Explorer plugin 414 | 415 | # File-based project format 416 | 417 | # IntelliJ 418 | 419 | # mpeltonen/sbt-idea plugin 420 | 421 | # JIRA plugin 422 | 423 | # Cursive Clojure plugin 424 | 425 | # SonarLint plugin 426 | 427 | # Crashlytics plugin (for Android Studio and IntelliJ) 428 | 429 | # Editor-based Rest Client 430 | 431 | # Android studio 3.1+ serialized cache file 432 | 433 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/TestProject.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://localhost:5432/TestProject 9 | $ProjectFileDir$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BotCore/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | handlers, 3 | keyboards, 4 | utils 5 | ) 6 | -------------------------------------------------------------------------------- /BotCore/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | main_router, 3 | start_menu, 4 | questioning 5 | ) 6 | -------------------------------------------------------------------------------- /BotCore/handlers/main_router.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | 3 | router = Router() 4 | -------------------------------------------------------------------------------- /BotCore/handlers/questioning.py: -------------------------------------------------------------------------------- 1 | from aiogram import F 2 | from aiogram.fsm.context import FSMContext 3 | from aiogram.types import InlineQueryResultArticle, InputTextMessageContent, InlineQuery, Message 4 | 5 | from loader import bot, db 6 | from .main_router import router 7 | from BotCore.states.chat_with_user import ChatWithUser 8 | 9 | 10 | @router.inline_query(F.query == "select_user") 11 | async def select_user(inline_query: InlineQuery) -> None: 12 | users = await db.get_all_users() 13 | 14 | result = [ 15 | InlineQueryResultArticle( 16 | id=str(i["_id"]), 17 | title=i["full_name"], 18 | description=f"Username: \"{i['username']}\"", 19 | input_message_content=InputTextMessageContent(message_text=f"Отправить сообщение пользователю {i['_id']}"), 20 | thumb_width=100, 21 | thumb_height=100, 22 | ) for i in users 23 | ] 24 | 25 | result.reverse() 26 | await bot.answer_inline_query(inline_query.id, results=result, cache_time=0) 27 | 28 | 29 | @router.message(F.text, F.via_bot) 30 | async def send_me_text(message: Message, state: FSMContext) -> None: 31 | user_id = int(message.text.split(" ")[-1]) 32 | await message.answer("Пришлите мне текст для отправки пользователю") 33 | await state.set_state(ChatWithUser.text) 34 | await state.update_data({"user_id": user_id}) 35 | 36 | 37 | @router.message(F.text, ChatWithUser.text) 38 | async def send_message_to_user(message: Message, state: FSMContext) -> None: 39 | state_data = await state.get_data() 40 | text = message.text 41 | user_id = state_data["user_id"] 42 | await bot.send_message(user_id, text + "\n\nОт бота: проверено!") 43 | await message.answer("Сообщение успешно отправлено пользователю!") 44 | await state.clear() 45 | -------------------------------------------------------------------------------- /BotCore/handlers/start_menu.py: -------------------------------------------------------------------------------- 1 | from aiogram.filters import CommandStart 2 | from aiogram.fsm.context import FSMContext 3 | from aiogram.types import Message 4 | 5 | from loader import bot, db 6 | from .main_router import router 7 | from BotCore.keyboards import ikb 8 | from BotCore.utils.photos_manager import Photo 9 | 10 | 11 | @router.message(CommandStart()) 12 | async def start_command(message: Message, state: FSMContext) -> None: 13 | await state.clear() 14 | 15 | await db.add_user( 16 | user_id=message.from_user.id, 17 | username=message.from_user.username, 18 | full_name=message.from_user.full_name 19 | ) 20 | 21 | await bot.send_photo( 22 | chat_id=message.from_user.id, 23 | caption="Привет. Здесь ты можешь переписываться с друзьями!", 24 | photo=(await Photo.file()), 25 | reply_markup=ikb.start_menu 26 | ) 27 | 28 | await bot.delete_message(message.from_user.id, message.message_id) 29 | -------------------------------------------------------------------------------- /BotCore/keyboards/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ikb 2 | -------------------------------------------------------------------------------- /BotCore/keyboards/ikb.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton 2 | 3 | start_menu = InlineKeyboardMarkup(inline_keyboard=[ 4 | [InlineKeyboardButton(text="📝 Написать", switch_inline_query_current_chat="select_user")] 5 | ]) 6 | -------------------------------------------------------------------------------- /BotCore/middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | from . import update_users 2 | -------------------------------------------------------------------------------- /BotCore/middlewares/update_users.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, Awaitable 2 | from aiogram import BaseMiddleware 3 | from aiogram.types import TelegramObject 4 | 5 | from loader import db 6 | 7 | 8 | class UpdateUsersMiddleware(BaseMiddleware): 9 | async def __call__( 10 | self, 11 | handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], 12 | event: TelegramObject, 13 | data: Dict[str, Any] 14 | ) -> Any: 15 | await db.update_user_activity(event.from_user.id) 16 | return await handler(event, data) 17 | -------------------------------------------------------------------------------- /BotCore/states/__init__.py: -------------------------------------------------------------------------------- 1 | from . import chat_with_user 2 | -------------------------------------------------------------------------------- /BotCore/states/chat_with_user.py: -------------------------------------------------------------------------------- 1 | from aiogram.fsm.state import StatesGroup, State 2 | 3 | 4 | class ChatWithUser(StatesGroup): 5 | text = State() 6 | -------------------------------------------------------------------------------- /BotCore/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import photos_manager 2 | -------------------------------------------------------------------------------- /BotCore/utils/photos_manager.py: -------------------------------------------------------------------------------- 1 | import aiofiles 2 | from aiogram.types import BufferedInputFile 3 | 4 | 5 | class Photo: 6 | @classmethod 7 | async def file(cls, filename: str = "main.jpg"): 8 | async with aiofiles.open("General/assets/" + filename, 'rb') as f: 9 | photo = await f.read() 10 | 11 | return BufferedInputFile(photo, "photo") 12 | -------------------------------------------------------------------------------- /General/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | cfg, 3 | database 4 | ) 5 | -------------------------------------------------------------------------------- /General/assets/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DIMFLIX/ModMessageBot/4a8f706aa169b4e9982eb64540e9e7cb05ed0c48/General/assets/main.jpg -------------------------------------------------------------------------------- /General/cfg.py: -------------------------------------------------------------------------------- 1 | from environs import Env 2 | 3 | env = Env() 4 | env.read_env() 5 | 6 | BOT_TOKEN = env.str("BOT_TOKEN") 7 | 8 | DB_HOST = env.str("DB_HOST") 9 | DB_PORT = env.int("DB_PORT") 10 | DB_USER = env.str("DB_USER") 11 | DB_PASSWORD = env.str("DB_PASSWORD") 12 | DB_NAME = env.str("DB_NAME") 13 | -------------------------------------------------------------------------------- /General/database.py: -------------------------------------------------------------------------------- 1 | import asyncpg 2 | from typing import List 3 | from datetime import datetime 4 | from asyncpg import Pool, Record 5 | from loguru import logger 6 | 7 | 8 | class DictRecord(Record): 9 | def __getitem__(self, key): 10 | value = super().__getitem__(key) 11 | if isinstance(value, Record): 12 | return DictRecord(value) 13 | return value 14 | 15 | def to_dict(self): 16 | return self._convert_records_to_dicts(dict(super().items())) 17 | 18 | def _convert_records_to_dicts(self, obj): 19 | if isinstance(obj, dict): 20 | return {k: self._convert_records_to_dicts(v) for k, v in obj.items()} 21 | elif isinstance(obj, list): 22 | return [self._convert_records_to_dicts(item) for item in obj] 23 | elif isinstance(obj, Record): 24 | return dict(obj) 25 | else: 26 | return obj 27 | 28 | def __repr__(self): 29 | return str(self.to_dict()) 30 | 31 | 32 | class DB: 33 | db: Pool 34 | 35 | def __init__(self, host: str, port: int, user: str, password: str, db_name: str) -> None: 36 | self._host = host 37 | self._port = port 38 | self._user = user 39 | self._password = password 40 | self._db_name = db_name 41 | 42 | async def close(self) -> None: 43 | await self.db.close() 44 | logger.warning("Соединение с базой данных завершено!") 45 | 46 | async def setup(self) -> None: 47 | self.db = await asyncpg.create_pool( 48 | host=self._host, port=self._port, user=self._user, 49 | password=self._password, database=self._db_name, 50 | record_class=DictRecord, init=self._init_database 51 | ) 52 | logger.success("Соединение с базой данных успешно установлено!") 53 | 54 | @staticmethod 55 | async def _init_database(db: asyncpg.Connection) -> None: 56 | await db.execute(""" 57 | CREATE TABLE IF NOT EXISTS \"users\"( 58 | _id BIGINT NOT NULL PRIMARY KEY, 59 | username TEXT, 60 | full_name TEXT, 61 | created_at TIMESTAMP DEFAULT now(), 62 | updated_at TIMESTAMP DEFAULT now() 63 | )""") 64 | 65 | await db.execute("SET TIME ZONE 'Europe/Moscow'") 66 | 67 | async def get_user_info(self, user_id: int) -> dict: 68 | response = await self.db.fetchrow("SELECT * FROM users WHERE _id = $1", user_id) 69 | return response.to_dict() 70 | 71 | async def add_user(self, user_id: int, username: str, full_name: str) -> dict: 72 | if not await self.user_existence(user_id): 73 | response = ( 74 | await self.db.fetchrow( 75 | "INSERT INTO users(_id, username, full_name) VALUES($1, $2, $3) RETURNING *", 76 | user_id, username, full_name 77 | ) 78 | ).to_dict() 79 | else: 80 | response = await self.get_user_info(user_id) 81 | 82 | return response 83 | 84 | async def update_user_activity(self, user_id: int): 85 | if await self.user_existence(user_id): 86 | await self.db.execute("UPDATE users SET updated_at=$1", datetime.now()) 87 | 88 | async def user_existence(self, user_id: int) -> bool: 89 | response = await self.db.fetchval("SELECT EXISTS(SELECT 1 FROM users WHERE _id=$1)", int(user_id)) 90 | return response 91 | 92 | async def get_all_users(self) -> List[dict]: 93 | response = await self.db.fetch("SELECT * FROM users") 94 | return response 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TestProject 2 | Реализация функции отправки сообщений в Telegram с 3 | модификацией текста. \ 4 | Версия: 1.0 \ 5 | Разработчик TG: @dimflix_official 6 | 7 | ### Тех. Задание 8 | Разработать функцию в Telegram-боте, которая позволит пользователям 9 | отправлять сообщения друг другу через бота. Каждое сообщение должно 10 | модифицироваться добавлением текста "проверено" к исходному тексту, и 11 | перед его отправкой пользователю должен быть предложен запрос на 12 | подтверждение. 13 | 14 | # Запуск 15 | 1. Устанавливем Python 3.11+ 16 | 2. Создаем виртуальное окружение `python -m venv venv` 17 | 3. Активируем виртуальное окружение `venv\Scripts\activate.bat` 18 | 4. Устанавливаем зависимости `pip install -r requirements.txt` 19 | 5. Создаем файл с настройками `copy .env.example .env` 20 | 6. Открываем файл конфигурации `.env` в любом текстовом редакторе и заполняем поля.\ 21 | `BOT_TOKEN` - токен бота \ 22 | `DB_HOST` - IP, на котором установлена база данных PostgreSQL \ 23 | `DB_PORT` - Порт, на котором установлена база данных PostgreSQL \ 24 | `DB_USER` - имя пользователя в базе данных PostgreSQL \ 25 | `DB_PASSWORD` - Пароль от учетной записи пользователя в базе данных PostgreSQL 26 | 7. Запускаем бота: `python app.py` -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from loguru import logger 3 | 4 | from loader import bot, dp, db 5 | from BotCore.handlers.main_router import router 6 | from BotCore.middlewares.update_users import UpdateUsersMiddleware 7 | 8 | 9 | async def on_startup() -> None: 10 | await db.setup() 11 | logger.success("Бот успешно запущен!") 12 | 13 | 14 | async def on_shutdown() -> None: 15 | await db.close() 16 | logger.warning("Бот выключен!") 17 | 18 | 19 | async def register_middlewares() -> None: 20 | dp.message.middleware(UpdateUsersMiddleware()) 21 | dp.callback_query.middleware(UpdateUsersMiddleware()) 22 | dp.inline_query.middleware(UpdateUsersMiddleware()) 23 | 24 | 25 | async def main() -> None: 26 | dp.include_router(router) 27 | await register_middlewares() 28 | dp.startup.register(on_startup) 29 | dp.shutdown.register(on_shutdown) 30 | await dp.start_polling(bot) 31 | 32 | 33 | if __name__ == "__main__": 34 | try: 35 | asyncio.run(main()) 36 | except KeyboardInterrupt: 37 | ... 38 | -------------------------------------------------------------------------------- /loader.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Dispatcher 2 | 3 | from General import cfg 4 | from General.database import DB 5 | 6 | 7 | bot: Bot = Bot(token=cfg.BOT_TOKEN, parse_mode="HTML") 8 | dp: Dispatcher = Dispatcher() 9 | db: DB = DB( 10 | cfg.DB_HOST, cfg.DB_PORT, cfg.DB_USER, 11 | cfg.DB_PASSWORD, cfg.DB_NAME 12 | ) 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | loguru~=0.7.2 2 | aiogram~=3.4.1 3 | aiofiles~=23.2.1 4 | asyncpg 5 | environs --------------------------------------------------------------------------------