├── .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 |
12 |
13 |
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 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
26 |
27 |
28 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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
--------------------------------------------------------------------------------