diff --git a/.bandit b/.bandit new file mode 100644 index 0000000..51ff587 --- /dev/null +++ b/.bandit @@ -0,0 +1 @@ +[bandit] diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4e2b181 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +per-file-ignores = + __init__.py:F401 +max-line-length = 88 +count = true +extend-ignore = E203,E701 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4f277a5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/crashappsec/pre-commit-sync + rev: 04b0e02eefa7c41bedca7456ad542e60b67c16c6 + hooks: + - id: pre-commit-sync + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 # sync:isort:poetry.lock + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 24.4.2 # sync:black:poetry.lock + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.0 # sync:flake8:poetry.lock + hooks: + - id: flake8 + - repo: https://github.com/PyCQA/bandit + rev: 1.7.9 # sync:bandit:poetry.lock + hooks: + - id: bandit diff --git a/README.md b/README.md index 69be112..2072957 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,4 @@ OCAB - это бот для Telegram, который призван помочь * SQLite 3 - база данных для хранения информации о чате и пользователях. * [Poetry](https://gitflic.ru/project/armatik/ocab/blob?file=how-to%20install%20deps.md&branch=OCAB-V2) - менеджер зависимостей. * aiogram 3 - библиотека для работы с Telegram API. -* peewee - ORM для работы с базой данных. \ No newline at end of file +* peewee - ORM для работы с базой данных. diff --git a/how-to install deps.md b/how-to install deps.md index 9353363..39b723c 100644 --- a/how-to install deps.md +++ b/how-to install deps.md @@ -44,4 +44,4 @@ poetry shell Хранить окружение внутри проекта ```shell poetry config virtualenvs.in-project true -``` \ No newline at end of file +``` diff --git a/init.py b/init.py index 404ae78..e4d52fa 100644 --- a/init.py +++ b/init.py @@ -1,5 +1,5 @@ -from pathlib import Path from json import dumps +from pathlib import Path pwd = Path().cwd() dir_core = pwd / "src" / "core" @@ -7,9 +7,9 @@ dir_modules_standard = pwd / "src" / "modules" / "standard" dir_modules_custom = pwd / "src" / "modules" / "custom" json = { - 'core': str(dir_core), - 'modules standard': str(dir_modules_standard), - 'modules custom': str(dir_modules_custom), + "core": str(dir_core), + "modules standard": str(dir_modules_standard), + "modules custom": str(dir_modules_custom), } with open("src/paths.json", "w", encoding="utf8") as f: f.write(dumps(json, indent=4)) diff --git a/poetry.lock b/poetry.lock index a58b989..8b77791 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiofiles" @@ -179,6 +179,74 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "bandit" +version = "1.7.9" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2024.2.2" @@ -190,6 +258,17 @@ files = [ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -289,6 +368,74 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "flake8" +version = "7.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" + [[package]] name = "frozenlist" version = "1.4.1" @@ -375,6 +522,20 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "identify" +version = "2.5.36" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.7" @@ -386,6 +547,20 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + [[package]] name = "magic-filter" version = "1.0.12" @@ -400,6 +575,52 @@ files = [ [package.extras] dev = ["black (>=22.8.0,<22.9.0)", "flake8 (>=5.0.4,<5.1.0)", "isort (>=5.11.5,<5.12.0)", "mypy (>=1.4.1,<1.5.0)", "pre-commit (>=2.20.0,<2.21.0)", "pytest (>=7.1.3,<7.2.0)", "pytest-cov (>=3.0.0,<3.1.0)", "pytest-html (>=3.1.1,<3.2.0)", "types-setuptools (>=65.3.0,<65.4.0)"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "multidict" version = "6.0.5" @@ -499,6 +720,61 @@ files = [ {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pbr" +version = "6.0.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, + {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, +] + [[package]] name = "peewee" version = "3.17.3" @@ -509,6 +785,51 @@ files = [ {file = "peewee-3.17.3.tar.gz", hash = "sha256:ef15f90b628e41a584be8306cdc3243c51f73ce88b06154d9572f6d0284a0169"}, ] +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pycodestyle" +version = "2.12.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, +] + [[package]] name = "pydantic" version = "2.7.1" @@ -619,6 +940,31 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyyaml" version = "6.0.1" @@ -700,6 +1046,38 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "stevedore" +version = "5.2.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.2.0-py3-none-any.whl", hash = "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9"}, + {file = "stevedore-5.2.0.tar.gz", hash = "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + [[package]] name = "typing-extensions" version = "4.11.0" @@ -728,6 +1106,26 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "yarl" version = "1.9.4" @@ -834,4 +1232,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11.6" -content-hash = "001611942f2ccb553fc80099924dce739c3cd5febb1438e9bad3e865d1aa8f8b" +content-hash = "5b43fa850045f857f8e7b84baa8a9d9b6c9e64758bf8525f4eb772574aafa5ca" diff --git a/pyproject.toml b/pyproject.toml index a8ccac3..c9d3e5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,22 @@ pyyaml = "^6.0.1" requests = "^2.31.0" +[tool.poetry.group.dev.dependencies] +flake8 = "^7.1.0" +black = "^24.4.2" +isort = "^5.13.2" +bandit = "^1.7.9" +pre-commit = "^3.7.1" + +[tool.black] +line-length = 88 + +[tool.isort] +profile = "black" +line_length = 88 +multi_line_output = 3 +skip_gitignore = true + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/run_tests b/run_tests index 1fcea0e..6009a83 100644 --- a/run_tests +++ b/run_tests @@ -1,4 +1,4 @@ #! /bin/sh cd src python -m unittest discover -v -cd .. \ No newline at end of file +cd .. diff --git a/src/__init__.py b/src/__init__.py index 2c9e6c4..7da3d9b 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,2 +1,2 @@ +import src.core import src.service -import src.core \ No newline at end of file diff --git a/src/core/example_config.yaml b/src/core/example_config.yaml index 927ef62..4ed979a 100644 --- a/src/core/example_config.yaml +++ b/src/core/example_config.yaml @@ -18,4 +18,4 @@ ROLES: ADMIN: 2 MODERATOR: 1 USER: 0 - BOT: 3 \ No newline at end of file + BOT: 3 diff --git a/src/core/logger.py b/src/core/logger.py index 3fef59c..4180a12 100644 --- a/src/core/logger.py +++ b/src/core/logger.py @@ -16,7 +16,7 @@ def setup_logger(): filename=log_file, level=logging.INFO, format="%(asctime)s %(message)s", - datefmt="%H:%M:%S" + datefmt="%H:%M:%S", ) diff --git a/src/core/main.py b/src/core/main.py index 5de2aa0..da38dd2 100644 --- a/src/core/main.py +++ b/src/core/main.py @@ -1,9 +1,11 @@ +import asyncio + +from aiogram import Bot, Dispatcher from routers import include_routers + from src.core.logger import log, setup_logger from src.modules.standard.config.config import get_telegram_token from src.modules.standard.database.db_api import connect_database, create_tables -import asyncio -from aiogram import Bot, Dispatcher async def main(): diff --git a/src/core/routers.py b/src/core/routers.py index 4c10732..f0c235b 100644 --- a/src/core/routers.py +++ b/src/core/routers.py @@ -1,8 +1,10 @@ from aiogram import Dispatcher -from src.modules.standard.info.routers import router as info_router from src.modules.standard.admin.routers import router as admin_router -from src.modules.standard.message_processing.message_api import router as process_message +from src.modules.standard.info.routers import router as info_router +from src.modules.standard.message_processing.message_api import ( + router as process_message, +) async def include_routers(dp: Dispatcher): @@ -13,4 +15,3 @@ async def include_routers(dp: Dispatcher): dp.include_router(info_router) dp.include_router(admin_router) dp.include_router(process_message) - \ No newline at end of file diff --git a/src/modules/__init__.py b/src/modules/__init__.py index 8b13789..e69de29 100644 --- a/src/modules/__init__.py +++ b/src/modules/__init__.py @@ -1 +0,0 @@ - diff --git a/src/modules/external/__init__.py b/src/modules/external/__init__.py index 04e9162..846ebe6 100644 --- a/src/modules/external/__init__.py +++ b/src/modules/external/__init__.py @@ -1 +1 @@ -from . import yandexgpt \ No newline at end of file +from . import yandexgpt diff --git a/src/modules/external/yandexgpt/handlers.py b/src/modules/external/yandexgpt/handlers.py index 7369be9..f8c8e4a 100644 --- a/src/modules/external/yandexgpt/handlers.py +++ b/src/modules/external/yandexgpt/handlers.py @@ -1,10 +1,17 @@ +# flake8: noqa +import asyncio + from aiogram import Bot from aiogram.types import Message -from src.modules.external.yandexgpt.yandexgpt import * -from src.modules.standard.config.config import get_yandexgpt_token, get_yandexgpt_catalog_id, get_yandexgpt_prompt -from src.modules.standard.database.db_api import add_message + from src.core.logger import log -import asyncio +from src.modules.external.yandexgpt.yandexgpt import * +from src.modules.standard.config.config import ( + get_yandexgpt_catalog_id, + get_yandexgpt_prompt, + get_yandexgpt_token, +) +from src.modules.standard.database.db_api import add_message async def answer_to_message(message: Message, bot: Bot): @@ -14,6 +21,8 @@ async def answer_to_message(message: Message, bot: Bot): text = message.text prompt = get_yandexgpt_prompt() # response = await yagpt.async_yandexgpt(system_prompt=prompt, input_messages=text) - response = await yagpt.yandexgpt_request(chat_id = message.chat.id, message_id = message.message_id, type = "yandexgpt") + response = await yagpt.yandexgpt_request( + chat_id=message.chat.id, message_id=message.message_id, type="yandexgpt" + ) reply = await message.reply(response, parse_mode="Markdown") add_message(reply, message_ai_model="yandexgpt") diff --git a/src/modules/external/yandexgpt/info.json b/src/modules/external/yandexgpt/info.json index 7f4bc15..2c331b1 100644 --- a/src/modules/external/yandexgpt/info.json +++ b/src/modules/external/yandexgpt/info.json @@ -3,4 +3,4 @@ "description": "Модуль для работы с Yandex GPT", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/external/yandexgpt/routers.py b/src/modules/external/yandexgpt/routers.py index 881aab9..6e2e0be 100644 --- a/src/modules/external/yandexgpt/routers.py +++ b/src/modules/external/yandexgpt/routers.py @@ -1,7 +1,10 @@ -from aiogram import Router, F +# flake8: noqa +from aiogram import F, Router from src.modules.external.yandexgpt.handlers import answer_to_message router = Router() # Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message -router.message.register(answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик")) \ No newline at end of file +router.message.register( + answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик") +) diff --git a/src/modules/external/yandexgpt/yandexgpt.py b/src/modules/external/yandexgpt/yandexgpt.py index 3ded6dc..1d726d6 100644 --- a/src/modules/external/yandexgpt/yandexgpt.py +++ b/src/modules/external/yandexgpt/yandexgpt.py @@ -1,24 +1,27 @@ -import requests -import json +# flake8: noqa import asyncio +import json + import aiohttp +import requests + from src.core.logger import log -from ...standard.database import * from ...standard.config.config import * +from ...standard.database import * class YandexGPT: token = None catalog_id = None languages = { - "ru": "русский язык", - "en": "английский язык", - "de": "немецкий язык", - "uk": "украинский язык", - "es": "испанский язык", - "be": "белорусский язык", - } + "ru": "русский язык", + "en": "английский язык", + "de": "немецкий язык", + "uk": "украинский язык", + "es": "испанский язык", + "be": "белорусский язык", + } def __init__(self, token, catalog_id): self.token = token @@ -29,11 +32,13 @@ class YandexGPT: async with session.post(url, headers=headers, json=prompt) as response: return await response.json() - async def async_token_check(self, messages, gpt, max_tokens, stream, temperature, del_msg_id=1): + async def async_token_check( + self, messages, gpt, max_tokens, stream, temperature, del_msg_id=1 + ): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/tokenizeCompletion" headers = { "Content-Type": "application/json", - "Authorization": f"Api-Key {self.token}" + "Authorization": f"Api-Key {self.token}", } answer_token = get_yandexgpt_token_for_answer() while True: @@ -43,12 +48,14 @@ class YandexGPT: "completionOptions": { "stream": stream, "temperature": temperature, - "maxTokens": max_tokens + "maxTokens": max_tokens, }, - "messages": messages + "messages": messages, } - response = await self.async_request(url=url, headers=headers, prompt=request) - except Exception as e: # TODO: Переделать обработку ошибок + response = await self.async_request( + url=url, headers=headers, prompt=request + ) + except Exception as e: # TODO: Переделать обработку ошибок # print(e) await log(f"Error: {e}") @@ -62,12 +69,19 @@ class YandexGPT: Exception("IndexError: list index out of range") return messages - async def async_yandexgpt_lite(self, system_prompt, input_messages, stream=False, temperature=0.6, max_tokens=8000): + async def async_yandexgpt_lite( + self, + system_prompt, + input_messages, + stream=False, + temperature=0.6, + max_tokens=8000, + ): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" gpt = f"gpt://{self.catalog_id}/yandexgpt-lite/latest" headers = { "Content-Type": "application/json", - "Authorization": f"Api-Key {self.token}" + "Authorization": f"Api-Key {self.token}", } messages = [{"role": "system", "text": system_prompt}] @@ -80,27 +94,27 @@ class YandexGPT: "completionOptions": { "stream": stream, "temperature": temperature, - "maxTokens": max_tokens + "maxTokens": max_tokens, }, - "messages": messages + "messages": messages, } - response = requests.post(url, headers=headers, json=prompt).text + response = requests.post(url, headers=headers, json=prompt).text # nosec return json.loads(response)["result"]["alternatives"][0]["message"]["text"] async def async_yandexgpt( - self, - system_prompt, - input_messages, - stream=False, - temperature=0.6, - max_tokens=get_yandexgpt_token_for_request() + self, + system_prompt, + input_messages, + stream=False, + temperature=0.6, + max_tokens=get_yandexgpt_token_for_request(), ): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" gpt = f"gpt://{self.catalog_id}/yandexgpt/latest" headers = { "Content-Type": "application/json", - "Authorization": f"Api-Key {self.token}" + "Authorization": f"Api-Key {self.token}", } messages = [] @@ -108,21 +122,24 @@ class YandexGPT: for message in input_messages: messages.append(message) - messages = await self.async_token_check(messages, gpt, max_tokens, stream, temperature) + messages = await self.async_token_check( + messages, gpt, max_tokens, stream, temperature + ) request = { "modelUri": gpt, "completionOptions": { "stream": stream, "temperature": temperature, - "maxTokens": max_tokens + "maxTokens": max_tokens, }, - "messages": messages + "messages": messages, } - response = await self.async_request(url=url, headers=headers, prompt=request) + response = await self.async_request( + url=url, headers=headers, prompt=request + ) # nosec return response["result"]["alternatives"][0]["message"]["text"] - async def async_yandexgpt_translate(self, input_language, output_language, text): input_language = self.languages[input_language] output_language = self.languages[output_language] @@ -130,7 +147,9 @@ class YandexGPT: return await self.async_yandexgpt( f"Переведи на {output_language} сохранив оригинальный смысл текста. Верни только результат:", [{"role": "user", "text": text}], - stream=False, temperature=0.6, max_tokens=8000 + stream=False, + temperature=0.6, + max_tokens=8000, ) async def async_yandexgpt_spelling_check(self, input_language, text): @@ -140,15 +159,19 @@ class YandexGPT: f"Проверьте орфографию и пунктуацию текста на {input_language}. Верни исправленный текст " f"без смысловых искажений:", [{"role": "user", "text": text}], - stream=False, temperature=0.6, max_tokens=8000 + stream=False, + temperature=0.6, + max_tokens=8000, ) - async def async_yandexgpt_text_history(self, input_messages, stream=False, temperature=0.6, max_tokens=8000): + async def async_yandexgpt_text_history( + self, input_messages, stream=False, temperature=0.6, max_tokens=8000 + ): url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" gpt = f"gpt://{self.catalog_id}/summarization/latest" headers = { "Content-Type": "application/json", - "Authorization": f"Api-Key {self.token}" + "Authorization": f"Api-Key {self.token}", } messages = [] @@ -161,15 +184,17 @@ class YandexGPT: "completionOptions": { "stream": stream, "temperature": temperature, - "maxTokens": max_tokens + "maxTokens": max_tokens, }, - "messages": messages + "messages": messages, } - response = requests.post(url, headers=headers, json=prompt).text + response = requests.post(url, headers=headers, json=prompt).text # nosec return json.loads(response)["result"]["alternatives"][0]["message"]["text"] - async def async_yandex_cloud_text_to_speech(self, text, voice, emotion, speed, format, quality): + async def async_yandex_cloud_text_to_speech( + self, text, voice, emotion, speed, format, quality + ): tts = "tts.api.cloud.yandex.net/speech/v1/tts:synthesize" # TODO: Сделать функцию TTS return 0 @@ -187,14 +212,18 @@ class YandexGPT: if db_api.get_message_ai_model(chat_id, message_id) != None: messages.append({"role": "assistant", "text": message}) else: - sender_name = db_api.get_user_name(db_api.get_message_sender_id(chat_id, message_id)) + sender_name = db_api.get_user_name( + db_api.get_message_sender_id(chat_id, message_id) + ) messages.append({"role": "user", "text": sender_name + ": " + message}) message_id = db_api.get_answer_to_message_id(chat_id, message_id) if message_id is None: break return list(reversed(messages)) - async def collecting_messages_for_history(self, start_message_id, end_message_id, chat_id): + async def collecting_messages_for_history( + self, start_message_id, end_message_id, chat_id + ): messages = [] # Собираем цепочку сообщений в формате: [{"role": "user", "text": "<Имя_пользователя>: Привет!"}, # {"role": "assistant", "text": "Привет!"}] @@ -203,21 +232,33 @@ class YandexGPT: if db_api.get_message_ai_model(chat_id, start_message_id) != None: messages.append({"role": "assistant", "text": message}) else: - sender_name = db_api.get_user_name(db_api.get_message_sender_id(chat_id, start_message_id)) + sender_name = db_api.get_user_name( + db_api.get_message_sender_id(chat_id, start_message_id) + ) messages.append({"role": "user", "text": sender_name + ": " + message}) start_message_id -= 1 if start_message_id <= end_message_id: break return messages.reverse() - async def yandexgpt_request(self, message_id = None, type = "yandexgpt-lite", chat_id = None, - message_id_end = None, input_language = None, output_language = None, text = None): + async def yandexgpt_request( + self, + message_id=None, + type="yandexgpt-lite", + chat_id=None, + message_id_end=None, + input_language=None, + output_language=None, + text=None, + ): if type == "yandexgpt-lite": messages = await self.collect_messages(message_id, chat_id) return await self.async_yandexgpt_lite( system_prompt=get_yandexgpt_prompt(), input_messages=messages, - stream=False, temperature=0.6, max_tokens=8000 + stream=False, + temperature=0.6, + max_tokens=8000, ) elif type == "yandexgpt": # print("yandexgpt_request") @@ -226,24 +267,26 @@ class YandexGPT: return await self.async_yandexgpt( system_prompt=get_yandexgpt_prompt(), input_messages=messages, - stream=False, temperature=0.6, max_tokens=get_yandexgpt_token_for_request() + stream=False, + temperature=0.6, + max_tokens=get_yandexgpt_token_for_request(), ) elif type == "yandexgpt-translate": return await self.async_yandexgpt_translate( input_language, output_language, - text=db_api.get_message_text(chat_id, message_id) + text=db_api.get_message_text(chat_id, message_id), ) elif type == "yandexgpt-spelling-check": return await self.async_yandexgpt_spelling_check( - input_language, - text=db_api.get_message_text(chat_id, message_id) + input_language, text=db_api.get_message_text(chat_id, message_id) ) elif type == "yandexgpt-text-history": - messages = await self.collect_messages_for_history(message_id, message_id_end, chat_id) + messages = await self.collect_messages_for_history( + message_id, message_id_end, chat_id + ) return await self.async_yandexgpt_text_history( - messages=messages, - stream=False, temperature=0.6, max_tokens=8000 + messages=messages, stream=False, temperature=0.6, max_tokens=8000 ) else: return "Ошибка: Неизвестный тип запроса | Error: Unknown request type" diff --git a/src/modules/standard/__init__.py b/src/modules/standard/__init__.py index 686a317..2cf9668 100644 --- a/src/modules/standard/__init__.py +++ b/src/modules/standard/__init__.py @@ -1 +1 @@ -from . import config, database, exceptions, roles \ No newline at end of file +from . import config, database, exceptions, roles diff --git a/src/modules/standard/admin/handlers.py b/src/modules/standard/admin/handlers.py index 4c197f8..bed369e 100644 --- a/src/modules/standard/admin/handlers.py +++ b/src/modules/standard/admin/handlers.py @@ -1,18 +1,26 @@ +# flake8: noqa +import time + from aiogram import Bot from aiogram.types import Message + from src.modules.standard.config.config import get_default_chat_tag -import time async def delete_message(message: Message, bot: Bot): reply_message_id = message.reply_to_message.message_id await bot.delete_message(message.chat.id, reply_message_id) + async def error_access(message: Message, bot: Bot): await message.reply("Вы не админ/модератор") + async def get_chat_id(message: Message, bot: Bot): - await message.reply(f"ID данного чата: `{message.chat.id}`", parse_mode="MarkdownV2") + await message.reply( + f"ID данного чата: `{message.chat.id}`", parse_mode="MarkdownV2" + ) + async def chat_not_in_approve_list(message: Message, bot: Bot): await message.reply( @@ -22,6 +30,7 @@ async def chat_not_in_approve_list(message: Message, bot: Bot): ) await get_chat_id(message, bot) + async def mute_user(chat_id: int, user_id: int, time: int, bot: Bot): # *, can_send_messages: bool | None = None, can_send_audios: bool | None = None, can_send_documents: bool | None = None, can_send_photos: bool | None = None, can_send_videos: bool | None = None, can_send_video_notes: bool | None = None, can_send_voice_notes: bool | None = None, can_send_polls: bool | None = None, can_send_other_messages: bool | None = None, can_add_web_page_previews: bool | None = None, can_change_info: bool | None = None, can_invite_users: bool | None = None, can_pin_messages: bool | None = None, can_manage_topics: bool | None = None, **extra_data: Any) @@ -39,8 +48,9 @@ async def mute_user(chat_id: int, user_id: int, time: int, bot: Bot): "can_change_info": False, "can_invite_users": False, "can_pin_messages": False, - "can_manage_topics": False + "can_manage_topics": False, } end_time = time + int(time.time()) - await bot.restrict_chat_member(chat_id, user_id, until_date=end_time, **mutePermissions) - + await bot.restrict_chat_member( + chat_id, user_id, until_date=end_time, **mutePermissions + ) diff --git a/src/modules/standard/admin/info.json b/src/modules/standard/admin/info.json index bd45d98..08c2581 100644 --- a/src/modules/standard/admin/info.json +++ b/src/modules/standard/admin/info.json @@ -3,4 +3,4 @@ "description": "Модуль для работы с админкой", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/admin/routers.py b/src/modules/standard/admin/routers.py index 4d8c8d0..3d9fcd8 100644 --- a/src/modules/standard/admin/routers.py +++ b/src/modules/standard/admin/routers.py @@ -1,15 +1,24 @@ -from aiogram import Router, F +# flake8: noqa +from aiogram import F, Router -from src.modules.standard.admin.handlers import delete_message, error_access, get_chat_id, chat_not_in_approve_list -from src.modules.standard.filters.filters import ChatModerOrAdminFilter, ChatNotInApproveFilter +from src.modules.standard.admin.handlers import ( + chat_not_in_approve_list, + delete_message, + error_access, + get_chat_id, +) +from src.modules.standard.filters.filters import ( + ChatModerOrAdminFilter, + ChatNotInApproveFilter, +) router = Router() # Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text) -router.message.register(get_chat_id, ChatModerOrAdminFilter(), F.text == '/chatID') +router.message.register(get_chat_id, ChatModerOrAdminFilter(), F.text == "/chatID") -router.message.register(delete_message, ChatModerOrAdminFilter(), F.text == '/rm') -router.message.register(error_access, F.text == '/rm') -router.message.register(error_access, F.text == '/chatID') \ No newline at end of file +router.message.register(delete_message, ChatModerOrAdminFilter(), F.text == "/rm") +router.message.register(error_access, F.text == "/rm") +router.message.register(error_access, F.text == "/chatID") diff --git a/src/modules/standard/config/config.py b/src/modules/standard/config/config.py index c851975..b47b307 100644 --- a/src/modules/standard/config/config.py +++ b/src/modules/standard/config/config.py @@ -1,4 +1,7 @@ +# flake8: noqa + import yaml + from ....service import paths @@ -9,52 +12,64 @@ def get_config(is_test: bool = False) -> dict: path = paths.core path = f"{path}/config.yaml" - with open(path, 'r') as file: + with open(path, "r") as file: return yaml.full_load(file) + config = get_config() + def get_telegram_token() -> str: return config["TELEGRAM"]["TOKEN"] + def get_telegram_check_bot() -> bool: return config["TELEGRAM"]["CHECK_BOT"] + def get_aproved_chat_id() -> list: # Возваращем сплитованный список id чатов в формате int - return [int(chat_id) for chat_id in config["TELEGRAM"]["APPROVED_CHAT_ID"].split(" | ")] + return [ + int(chat_id) for chat_id in config["TELEGRAM"]["APPROVED_CHAT_ID"].split(" | ") + ] + def get_user_role_name(role_number) -> dict: # Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно return config["ROLES"].get(role_number, "Неизвестно") + def get_default_chat_tag() -> str: return config["TELEGRAM"]["DEFAULT_CHAT_TAG"] + def get_yandexgpt_token() -> str: return config["YANDEXGPT"]["TOKEN"] + def get_yandexgpt_catalog_id() -> str: return config["YANDEXGPT"]["CATALOGID"] + def get_yandexgpt_prompt() -> str: return config["YANDEXGPT"]["PROMPT"] + def get_yandexgpt_start_words() -> list: return config["YANDEXGPT"]["STARTWORD"].split(" | ") + def get_yandexgpt_in_words() -> list: return config["YANDEXGPT"]["INWORD"].split(" | ") + def get_yandexgpt_token_for_request() -> int: return config["YANDEXGPT"]["TOKEN_FOR_REQUEST"] + def get_yandexgpt_token_for_answer() -> int: return config["YANDEXGPT"]["TOKEN_FOR_ANSWER"] + def get_access_rights() -> dict: return get_config()["ACCESS_RIGHTS"] - - - - diff --git a/src/modules/standard/config/info.json b/src/modules/standard/config/info.json index b190cc1..c615aa5 100644 --- a/src/modules/standard/config/info.json +++ b/src/modules/standard/config/info.json @@ -3,4 +3,4 @@ "description": "Модуль для работы с конфигурационным файлом бота (YAML)", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/config/tests/config.yaml b/src/modules/standard/config/tests/config.yaml index 65a4d10..338d945 100644 --- a/src/modules/standard/config/tests/config.yaml +++ b/src/modules/standard/config/tests/config.yaml @@ -4,4 +4,4 @@ ROLES: ADMIN: 0 MODERATOR: 1 USER: 2 - BOT: 3 \ No newline at end of file + BOT: 3 diff --git a/src/modules/standard/config/tests/test_config.py b/src/modules/standard/config/tests/test_config.py index d60777e..011d5c1 100644 --- a/src/modules/standard/config/tests/test_config.py +++ b/src/modules/standard/config/tests/test_config.py @@ -1,6 +1,7 @@ -from src.modules.standard.config.config import get_config import unittest +from src.modules.standard.config.config import get_config + yaml_load = get_config(is_test=True) @@ -18,20 +19,27 @@ class TestConfig(unittest.TestCase): def test_yaml_keys_existence(self): self.assertTrue(all(key in yaml_load for key in ["TELEGRAM", "ROLES"])) self.assertIn("TOKEN", yaml_load["TELEGRAM"]) - self.assertTrue(all(role in yaml_load["ROLES"] for role in ["ADMIN", "MODERATOR", "USER"])) + self.assertTrue( + all(role in yaml_load["ROLES"] for role in ["ADMIN", "MODERATOR", "USER"]) + ) def test_yaml_yaml_load_types(self): self.assertIsInstance(yaml_load["TELEGRAM"]["TOKEN"], str) - self.assertTrue(all(isinstance(yaml_load["ROLES"][role], int) for role in ["ADMIN", "MODERATOR", "USER"])) + self.assertTrue( + all( + isinstance(yaml_load["ROLES"][role], int) + for role in ["ADMIN", "MODERATOR", "USER"] + ) + ) def test_yaml_values(self): - expected_token = 'xxxxxxxxxxxxxxxxxxxx' - expected_role_values = {'ADMIN': 0, 'MODERATOR': 1, 'USER': 2, 'BOT': 3} + expected_token = "xxxxxxxxxxxxxxxxxxxx" # nosec + expected_role_values = {"ADMIN": 0, "MODERATOR": 1, "USER": 2, "BOT": 3} self.assertEqual(yaml_load["TELEGRAM"]["TOKEN"], expected_token) for role, value in expected_role_values.items(): self.assertEqual(yaml_load["ROLES"][role], value) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/src/modules/standard/database/README.md b/src/modules/standard/database/README.md index 9f0b979..769e793 100644 --- a/src/modules/standard/database/README.md +++ b/src/modules/standard/database/README.md @@ -4,7 +4,7 @@ Модуль содержит в себе следующие таблицы: -* `Chats` - таблица для хранения информации о чатах. +* `Chats` - таблица для хранения информации о чатах. * `Users` - таблица для хранения информации о пользователях. * `Messages` - таблица для хранения информации о сообщениях. * `ChatStats` - таблица для хранения статистики чатов по дням. @@ -28,9 +28,9 @@ Cтруктура таблицы `Users`: Cтруктура таблицы `Messages`: * `message_chat_id` - идентификатор чата в котором отправлено сообщение. * `message_id` - идентификатор сообщения. -* `messag_sender_id` - идентификатор пользователя отправившего сообщение. Если сообщение отправил бот, то +* `messag_sender_id` - идентификатор пользователя отправившего сообщение. Если сообщение отправил бот, то `messag_sender_id` = 0. -* `answer_to_message_id` - идентификатор сообщения на которое дан ответ. Если ответа нет или ответ на служебное +* `answer_to_message_id` - идентификатор сообщения на которое дан ответ. Если ответа нет или ответ на служебное сообщение о создании топика в чатах с форумным типом, то `answer_to_message_id` = 0. * `message_ai_model` - идентификатор модели нейросети, которая использовалась для генерации ответа. Если ответ' сгенерирован не был, то `message_ai_model` = null. @@ -45,4 +45,4 @@ Cтруктура таблицы `UserStats`: * `chat_id` - идентификатор чата для которого собрана статистика. * `user_id` - идентификатор пользователя для которого собрана статистика. * `date` - дата на которую собрана статистика. -* `messages_count` - количество сообщений отправленных пользователем в чат за день. \ No newline at end of file +* `messages_count` - количество сообщений отправленных пользователем в чат за день. diff --git a/src/modules/standard/database/__init__.py b/src/modules/standard/database/__init__.py index f336d57..fa2df97 100644 --- a/src/modules/standard/database/__init__.py +++ b/src/modules/standard/database/__init__.py @@ -1 +1 @@ -from . import db_api, models \ No newline at end of file +from . import db_api, models diff --git a/src/modules/standard/database/db_api.py b/src/modules/standard/database/db_api.py index 92f4527..e96d4ff 100644 --- a/src/modules/standard/database/db_api.py +++ b/src/modules/standard/database/db_api.py @@ -1,13 +1,14 @@ -from .models.chats import Chats -from .models.messages import Messages -from .models.users import Users -from .models.user_stats import UserStats -from .models.chat_stats import ChatStats -from ....service import paths -from ..exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName import peewee as pw from aiogram.types import Message +from ....service import paths +from ..exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName +from .models.chat_stats import ChatStats +from .models.chats import Chats +from .models.messages import Messages +from .models.user_stats import UserStats +from .models.users import Users + def connect_database(is_test: bool = False, module: str | None = None): if is_test: @@ -37,11 +38,14 @@ def create_tables(db: pw.SqliteDatabase): def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0): - chat, created = Chats.get_or_create(id=chat_id, defaults={ - 'chat_name': chat_name, - 'chat_type': chat_type, - 'chat_all_stat': chat_stats, - }) + chat, created = Chats.get_or_create( + id=chat_id, + defaults={ + "chat_name": chat_name, + "chat_type": chat_type, + "chat_all_stat": chat_stats, + }, + ) if not created: # Обновить существующий чат, если он уже существует chat.chat_name = chat_name @@ -50,19 +54,30 @@ def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0): chat.save() -def add_user(user_id, user_first_name, user_last_name=None, user_tag=None, user_role=0, user_stats=0, user_rep=0): +def add_user( + user_id, + user_first_name, + user_last_name=None, + user_tag=None, + user_role=0, + user_stats=0, + user_rep=0, +): if user_last_name is None: user_name = user_first_name else: user_name = user_first_name + " " + user_last_name - user, created = Users.get_or_create(id=user_id, defaults={ - 'user_tag': user_tag, - 'user_name': user_name, - 'user_role': user_role, - 'user_stats': user_stats, - 'user_rep': user_rep - }) + user, created = Users.get_or_create( + id=user_id, + defaults={ + "user_tag": user_tag, + "user_name": user_name, + "user_role": user_role, + "user_stats": user_stats, + "user_rep": user_rep, + }, + ) if not created: # Обновить существующего пользователя, если он уже существует user.user_tag = user_tag @@ -84,26 +99,20 @@ def add_message(message: Message, message_ai_model=None): message_sender_id=message.from_user.id, answer_to_message_id=answer_to_message_id, message_ai_model=message_ai_model, - message_text=message.text + message_text=message.text, ) def add_chat_stats(chat_id, date, messages_count): - ChatStats.create( - chat_id=chat_id, - date=date, - messages_count=messages_count - ) + ChatStats.create(chat_id=chat_id, date=date, messages_count=messages_count) def add_user_stats(chat_id, user_id, date, messages_count): UserStats.create( - chat_id=chat_id, - user_id=user_id, - date=date, - messages_count=messages_count + chat_id=chat_id, user_id=user_id, date=date, messages_count=messages_count ) + # Работа с таблицей чатов @@ -125,6 +134,7 @@ def get_chat_all_stat(chat_id): chat = Chats.get_or_none(Chats.id == chat_id) return chat.chat_all_stat if chat else None + # Работа с таблицей пользователей @@ -136,6 +146,7 @@ def get_user_tag(user_id): user = Users.get_or_none(Users.id == user_id) return user.user_tag if user else None + def get_user_id(user_tag): user = Users.get_or_none(Users.user_tag == user_tag) return user.id if user else None @@ -179,32 +190,44 @@ def change_user_role(user_id, new_user_role): query = Users.update(user_role=new_user_role).where(Users.id == user_id) query.execute() + # Работа с таблицей сообщений def get_message(message_chat_id, message_id): - return Messages.get_or_none(Messages.message_chat_id == message_chat_id, Messages.message_id == message_id) + return Messages.get_or_none( + Messages.message_chat_id == message_chat_id, Messages.message_id == message_id + ) def get_message_sender_id(message_chat_id, message_id): - message = Messages.get_or_none(Messages.message_chat_id == message_chat_id, Messages.message_id == message_id) + message = Messages.get_or_none( + Messages.message_chat_id == message_chat_id, Messages.message_id == message_id + ) return message.message_sender_id if message else None def get_message_text(message_chat_id, message_id): - message = Messages.get_or_none(Messages.message_chat_id == message_chat_id, Messages.message_id == message_id) + message = Messages.get_or_none( + Messages.message_chat_id == message_chat_id, Messages.message_id == message_id + ) return message.message_text if message else None def get_message_ai_model(message_chat_id, message_id): - message = Messages.get_or_none(Messages.message_chat_id == message_chat_id, Messages.message_id == message_id) + message = Messages.get_or_none( + Messages.message_chat_id == message_chat_id, Messages.message_id == message_id + ) return message.message_ai_model if message else None def get_answer_to_message_id(message_chat_id, message_id): - message = Messages.get_or_none(Messages.message_chat_id == message_chat_id, Messages.message_id == message_id) + message = Messages.get_or_none( + Messages.message_chat_id == message_chat_id, Messages.message_id == message_id + ) return message.answer_to_message_id if message else None + # Работа с таблицей статистики чатов @@ -214,6 +237,7 @@ def get_chat_stats(chat_id): chat_stats[chat_stat.date] = chat_stat.messages_count return chat_stats + # Работа с таблицей статистики пользователей @@ -223,26 +247,28 @@ def get_user_stats(user_id): user_stats[user_stat.date] = user_stat.messages_count return user_stats + # Функции обновления def update_chat_all_stat(chat_id): - query = Chats.update(chat_all_stat=Chats.chat_all_stat + 1).where(Chats.id == chat_id) + query = Chats.update(chat_all_stat=Chats.chat_all_stat + 1).where( + Chats.id == chat_id + ) query.execute() def update_chat_stats(chat_id, date): - chat_stats = ChatStats.get_or_none(ChatStats.chat_id == chat_id, ChatStats.date == date) + chat_stats = ChatStats.get_or_none( + ChatStats.chat_id == chat_id, ChatStats.date == date + ) if chat_stats: - query = ChatStats.update(messages_count=ChatStats.messages_count + 1).where(ChatStats.chat_id == chat_id, - ChatStats.date == date) + query = ChatStats.update(messages_count=ChatStats.messages_count + 1).where( + ChatStats.chat_id == chat_id, ChatStats.date == date + ) query.execute() else: - ChatStats.create( - chat_id=chat_id, - date=date, - messages_count=1 - ) + ChatStats.create(chat_id=chat_id, date=date, messages_count=1) def update_user_all_stat(user_id): @@ -251,10 +277,7 @@ def update_user_all_stat(user_id): query = Users.update(user_stats=Users.user_stats + 1).where(Users.id == user_id) query.execute() else: - Users.create( - id=user_id, - user_stats=1 - ) + Users.create(id=user_id, user_stats=1) def update_user_rep(user_id): @@ -263,24 +286,21 @@ def update_user_rep(user_id): query = Users.update(user_rep=Users.user_rep + 1).where(Users.id == user_id) query.execute() else: - Users.create( - id=user_id, - user_rep=1 - ) + Users.create(id=user_id, user_rep=1) def update_user_stats(chat_id, user_id, date): - user_stats = UserStats.get_or_none(UserStats.chat_id == chat_id, UserStats.user_id == user_id, - UserStats.date == date) + user_stats = UserStats.get_or_none( + UserStats.chat_id == chat_id, + UserStats.user_id == user_id, + UserStats.date == date, + ) if user_stats: - query = UserStats.update(messages_count=UserStats.messages_count + 1).where(UserStats.chat_id == chat_id, - UserStats.user_id == user_id, - UserStats.date == date) + query = UserStats.update(messages_count=UserStats.messages_count + 1).where( + UserStats.chat_id == chat_id, + UserStats.user_id == user_id, + UserStats.date == date, + ) query.execute() else: - UserStats.create( - chat_id=chat_id, - user_id=user_id, - date=date, - messages_count=1 - ) + UserStats.create(chat_id=chat_id, user_id=user_id, date=date, messages_count=1) diff --git a/src/modules/standard/database/info.json b/src/modules/standard/database/info.json index db9c2c7..bfa88d4 100644 --- a/src/modules/standard/database/info.json +++ b/src/modules/standard/database/info.json @@ -3,4 +3,4 @@ "description": "Модуль для работы с БД", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/database/models/__init__.py b/src/modules/standard/database/models/__init__.py index 8b13789..e69de29 100644 --- a/src/modules/standard/database/models/__init__.py +++ b/src/modules/standard/database/models/__init__.py @@ -1 +0,0 @@ - diff --git a/src/modules/standard/database/models/chat_stats.py b/src/modules/standard/database/models/chat_stats.py index 97bb28e..1f62601 100644 --- a/src/modules/standard/database/models/chat_stats.py +++ b/src/modules/standard/database/models/chat_stats.py @@ -2,8 +2,8 @@ import peewee as pw class ChatStats(pw.Model): - class Meta: - ... + class Meta: ... + chat_id = pw.IntegerField(null=False) date = pw.DateField(null=False) messages_count = pw.IntegerField(null=False, default=0) diff --git a/src/modules/standard/database/models/chats.py b/src/modules/standard/database/models/chats.py index e2d0d8f..8e2dd24 100644 --- a/src/modules/standard/database/models/chats.py +++ b/src/modules/standard/database/models/chats.py @@ -2,8 +2,8 @@ import peewee as pw class Chats(pw.Model): - class Meta: - ... + class Meta: ... + chat_name = pw.CharField(null=False) chat_type = pw.IntegerField(null=False, default=10) chat_all_stat = pw.IntegerField(null=False) diff --git a/src/modules/standard/database/models/messages.py b/src/modules/standard/database/models/messages.py index 81a70e2..926b58d 100644 --- a/src/modules/standard/database/models/messages.py +++ b/src/modules/standard/database/models/messages.py @@ -2,12 +2,11 @@ import peewee as pw class Messages(pw.Model): - class Meta: - ... + class Meta: ... + message_chat_id = pw.IntegerField(null=False) message_id = pw.IntegerField(null=False) message_sender_id = pw.IntegerField(null=False) answer_to_message_id = pw.IntegerField(null=True) message_ai_model = pw.TextField(null=True) message_text = pw.TextField(null=False) - diff --git a/src/modules/standard/database/models/user_stats.py b/src/modules/standard/database/models/user_stats.py index 6db2c1c..e656da4 100644 --- a/src/modules/standard/database/models/user_stats.py +++ b/src/modules/standard/database/models/user_stats.py @@ -2,8 +2,8 @@ import peewee as pw class UserStats(pw.Model): - class Meta: - ... + class Meta: ... + chat_id = pw.IntegerField(null=False) user_id = pw.IntegerField(null=False) date = pw.DateField(null=False) diff --git a/src/modules/standard/database/models/users.py b/src/modules/standard/database/models/users.py index 7214099..27a7fe1 100644 --- a/src/modules/standard/database/models/users.py +++ b/src/modules/standard/database/models/users.py @@ -2,8 +2,8 @@ import peewee as pw class Users(pw.Model): - class Meta: - ... + class Meta: ... + user_tag = pw.CharField(null=True) user_name = pw.CharField(null=False) # до 255 символов user_role = pw.IntegerField(null=True, default=3) diff --git a/src/modules/standard/database/tests/database/file b/src/modules/standard/database/tests/database/file index ef0b553..e29d9d5 100644 --- a/src/modules/standard/database/tests/database/file +++ b/src/modules/standard/database/tests/database/file @@ -1 +1 @@ -Эта директория для тестовой БД \ No newline at end of file +Эта директория для тестовой БД diff --git a/src/modules/standard/database/tests/test_db.py b/src/modules/standard/database/tests/test_db.py index e858981..0052a7e 100644 --- a/src/modules/standard/database/tests/test_db.py +++ b/src/modules/standard/database/tests/test_db.py @@ -1,8 +1,10 @@ -import unittest -import os +# flake8: noqa + +import os +import unittest -from ..db_api import * from ...exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName +from ..db_api import * class TestDatabaseAPI(unittest.TestCase): @@ -13,6 +15,7 @@ class TestDatabaseAPI(unittest.TestCase): def setUpClass(cls): cls.database, cls.path = connect_database(is_test=True, module="database") create_tables(cls.database) + def test_fail_connect(cls): with cls.assertRaises(MissingModuleName): cls.database, cls.path = connect_database(is_test=True) @@ -35,8 +38,12 @@ class TestDatabaseAPI(unittest.TestCase): self.assertEqual(chat2.chat_role, 1) def test_add_and_get_message(self): - add_message(message_id=1, message_text="Test Message 1", message_sender=1, answer_id=2) - add_message(message_id=2, message_text="Test Message 2", message_sender=2, answer_id=1) + add_message( + message_id=1, message_text="Test Message 1", message_sender=1, answer_id=2 + ) + add_message( + message_id=2, message_text="Test Message 2", message_sender=2, answer_id=1 + ) message1 = get_message(1) self.assertIsNotNone(message1) @@ -49,8 +56,22 @@ class TestDatabaseAPI(unittest.TestCase): self.assertEqual(message2.message_text, "Test Message 2") def test_add_and_get_user(self): - add_user(user_id=100, user_name="TestUser1", user_tag="TestTag1", user_role=0, user_stats=10, user_rep=5) - add_user(user_id=101, user_name="TestUser2", user_tag="TestTag2", user_role=1, user_stats=20, user_rep=10) + add_user( + user_id=100, + user_name="TestUser1", + user_tag="TestTag1", + user_role=0, + user_stats=10, + user_rep=5, + ) + add_user( + user_id=101, + user_name="TestUser2", + user_tag="TestTag2", + user_role=1, + user_stats=20, + user_rep=10, + ) user1 = get_user(100) self.assertIsNotNone(user1) @@ -63,8 +84,22 @@ class TestDatabaseAPI(unittest.TestCase): self.assertEqual(user2.user_name, "TestUser2") def test_get_user_role(self): - add_user(user_id=102, user_name="TestUser3", user_tag="TestTag3", user_role=0, user_stats=30, user_rep=15) - add_user(user_id=103, user_name="TestUser4", user_tag="TestTag4", user_role=1, user_stats=40, user_rep=20) + add_user( + user_id=102, + user_name="TestUser3", + user_tag="TestTag3", + user_role=0, + user_stats=30, + user_rep=15, + ) + add_user( + user_id=103, + user_name="TestUser4", + user_tag="TestTag4", + user_role=1, + user_stats=40, + user_rep=20, + ) user_role1 = get_user_role(102) self.assertEqual(user_role1, 0) @@ -73,12 +108,26 @@ class TestDatabaseAPI(unittest.TestCase): self.assertEqual(user_role2, 1) def test_change_user_name(self): - add_user(user_id=104, user_name="OldName1", user_tag="TestTag5", user_role=0, user_stats=50, user_rep=25) + add_user( + user_id=104, + user_name="OldName1", + user_tag="TestTag5", + user_role=0, + user_stats=50, + user_rep=25, + ) change_user_name(104, "NewName1") updated_user1 = get_user(104) self.assertEqual(updated_user1.user_name, "NewName1") - add_user(user_id=105, user_name="OldName2", user_tag="TestTag6", user_role=1, user_stats=60, user_rep=30) + add_user( + user_id=105, + user_name="OldName2", + user_tag="TestTag6", + user_role=1, + user_stats=60, + user_rep=30, + ) change_user_name(105, "NewName2") updated_user2 = get_user(105) self.assertEqual(updated_user2.user_name, "NewName2") @@ -86,8 +135,8 @@ class TestDatabaseAPI(unittest.TestCase): @classmethod def tearDownClass(cls): cls.database.close() - os.system(f"rm {cls.path}") + os.system(f"rm {cls.path}") # nosec -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/src/modules/standard/exceptions/__init__.py b/src/modules/standard/exceptions/__init__.py index 0fe936f..6ceaccd 100644 --- a/src/modules/standard/exceptions/__init__.py +++ b/src/modules/standard/exceptions/__init__.py @@ -1 +1 @@ -from . import module_exceptions \ No newline at end of file +from . import module_exceptions diff --git a/src/modules/standard/exceptions/info.json b/src/modules/standard/exceptions/info.json index 28d6bdd..79618c6 100644 --- a/src/modules/standard/exceptions/info.json +++ b/src/modules/standard/exceptions/info.json @@ -3,4 +3,4 @@ "description": "Модуль с исключениями", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/exceptions/module_exceptions.py b/src/modules/standard/exceptions/module_exceptions.py index 89f3718..d315424 100644 --- a/src/modules/standard/exceptions/module_exceptions.py +++ b/src/modules/standard/exceptions/module_exceptions.py @@ -10,4 +10,3 @@ class NotExpectedModuleName(BaseException): self.message = "Не ожидалось название директории модуля" super().__init__(self.message) - diff --git a/src/modules/standard/filters/filters.py b/src/modules/standard/filters/filters.py index 7bccb50..ecdaef7 100644 --- a/src/modules/standard/filters/filters.py +++ b/src/modules/standard/filters/filters.py @@ -1,18 +1,23 @@ +from aiogram import Bot from aiogram.filters import BaseFilter from aiogram.types import Message -from aiogram import Bot -from src.modules.standard.roles.roles import Roles -from src.modules.standard.config.config import get_aproved_chat_id from src.core.logger import log +from src.modules.standard.config.config import get_aproved_chat_id +from src.modules.standard.roles.roles import Roles + class ChatModerOrAdminFilter(BaseFilter): async def __call__(self, message: Message, bot: Bot) -> bool: user_id = message.from_user.id roles = Roles() admins = await bot.get_chat_administrators(message.chat.id) - return await roles.check_admin_permission(user_id) or \ - await roles.check_moderator_permission(user_id) or any(user_id == admin.user.id for admin in admins) + return ( + await roles.check_admin_permission(user_id) + or await roles.check_moderator_permission(user_id) + or any(user_id == admin.user.id for admin in admins) + ) + class ChatNotInApproveFilter(BaseFilter): async def __call__(self, message: Message, bot: Bot) -> bool: diff --git a/src/modules/standard/filters/info.json b/src/modules/standard/filters/info.json index 59f6d44..4fc97ec 100644 --- a/src/modules/standard/filters/info.json +++ b/src/modules/standard/filters/info.json @@ -3,4 +3,4 @@ "description": "Модуль с фильтрами", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/info/handlers.py b/src/modules/standard/info/handlers.py index 6a658b0..d1b60ba 100644 --- a/src/modules/standard/info/handlers.py +++ b/src/modules/standard/info/handlers.py @@ -1,13 +1,20 @@ +# flake8: noqa + from aiogram import Bot from aiogram.types import Message -from src.modules.standard.config.config import get_user_role_name -from src.modules.standard.roles.roles import Roles -from src.modules.standard.database.db_api import * + from src.core.logger import log +from src.modules.standard.config.config import get_user_role_name +from src.modules.standard.database.db_api import * +from src.modules.standard.roles.roles import Roles + async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int): if get_message_ai_model(message.chat.id, message.message_id) is not None: - await message.reply("Это сообщение было сгенерировано ботом используя модель: " + get_message_ai_model(message.chat.id, message.message_id)) + await message.reply( + "Это сообщение было сгенерировано ботом используя модель: " + + get_message_ai_model(message.chat.id, message.message_id) + ) elif user_id == bot.id: await message.reply("Это сообщение было отправлено ботом") elif get_user(user_id) is None: @@ -44,11 +51,14 @@ async def get_user_info(message: Message, bot: Bot): else: await get_info_answer_by_id(message, bot, message.from_user.id) except Exception as e: - await message.reply("В вашем запросе что-то пошло не так," - " попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение") + await message.reply( + "В вашем запросе что-то пошло не так," + " попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение" + ) # print(e) await log(e) + async def get_chat_info(message: Message, bot: Bot): answer = ( f"*Название чата:* {message.chat.title}\n" @@ -57,4 +67,4 @@ async def get_chat_info(message: Message, bot: Bot): f"*Количество пользователей в чате:* {await bot.get_chat_member_count(message.chat.id)}\n" f"*Количество администраторов в чате:* {len(await bot.get_chat_administrators(message.chat.id))}" ) - await message.reply(answer, parse_mode="MarkdownV2") \ No newline at end of file + await message.reply(answer, parse_mode="MarkdownV2") diff --git a/src/modules/standard/info/routers.py b/src/modules/standard/info/routers.py index 957930f..9594b91 100644 --- a/src/modules/standard/info/routers.py +++ b/src/modules/standard/info/routers.py @@ -1,8 +1,10 @@ -from aiogram import Router, F +# flake8: noqa -from src.modules.standard.info.handlers import get_user_info, get_chat_info +from aiogram import F, Router + +from src.modules.standard.info.handlers import get_chat_info, get_user_info router = Router() router.message.register(get_user_info, F.text.startswith("/info") == True) -router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True) \ No newline at end of file +router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True) diff --git a/src/modules/standard/message_processing/message_api.py b/src/modules/standard/message_processing/message_api.py index 028a2cf..a1ef218 100644 --- a/src/modules/standard/message_processing/message_api.py +++ b/src/modules/standard/message_processing/message_api.py @@ -1,9 +1,15 @@ -from aiogram import Router, F, Bot, types +# flake8: noqa + +from aiogram import Bot, F, Router, types -from src.modules.external.yandexgpt.handlers import answer_to_message -from src.modules.standard.database.db_api import * -from src.modules.standard.config.config import get_yandexgpt_start_words, get_yandexgpt_in_words, get_aproved_chat_id from src.core.logger import log +from src.modules.external.yandexgpt.handlers import answer_to_message +from src.modules.standard.config.config import ( + get_aproved_chat_id, + get_yandexgpt_in_words, + get_yandexgpt_start_words, +) +from src.modules.standard.database.db_api import * async def chat_check(message: types.Message): @@ -19,7 +25,9 @@ async def chat_check(message: types.Message): await log(f"Chat added: {message.chat.id} {message.chat.title}") else: # print(f"Chat not in approve list: {message.chat.id} {message.chat.title}") - await log(f"Chat not in approve list: {message.chat.id} {message.chat.title}") + await log( + f"Chat not in approve list: {message.chat.id} {message.chat.title}" + ) pass else: # Проверяем обновление названия чата @@ -34,6 +42,7 @@ async def chat_check(message: types.Message): await log(f"Chat already exists: {message.chat.id} {message.chat.title}") pass + async def user_check(message: types.Message): # Проверка наличия id пользователя в базе данных пользователей # Если пользователя нет в базе данных, то добавляем его @@ -42,17 +51,24 @@ async def user_check(message: types.Message): if message.from_user.last_name is None: current_user_name = message.from_user.first_name else: - current_user_name = message.from_user.first_name + " " + message.from_user.last_name + current_user_name = ( + message.from_user.first_name + " " + message.from_user.last_name + ) if get_user(message.from_user.id) is None: - add_user(message.from_user.id, - message.from_user.first_name, message.from_user.last_name, - message.from_user.username) + add_user( + message.from_user.id, + message.from_user.first_name, + message.from_user.last_name, + message.from_user.username, + ) # print(f"User added: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name}") await log(f"User added: {message.from_user.id} {current_user_name}") else: # print(f"User already exists: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name} {message.from_user.username}") - await log(f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}") + await log( + f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}" + ) # Проверяем обновление имени пользователя if get_user_name(message.from_user.id) != current_user_name: change_user_name(message.from_user.id, current_user_name) @@ -62,9 +78,12 @@ async def user_check(message: types.Message): if get_user_tag(message.from_user.id) != message.from_user.username: change_user_tag(message.from_user.id, message.from_user.username) # print(f"User updated: {message.from_user.id} {message.from_user.username}") - await log(f"User tag updated: {message.from_user.id} {message.from_user.username}") + await log( + f"User tag updated: {message.from_user.id} {message.from_user.username}" + ) pass + async def add_stats(message: types.Message): # Добавляем пользователю и чату статистику update_chat_all_stat(message.chat.id) @@ -79,8 +98,9 @@ async def message_processing(message: types.Message, bot: Bot): add_message(message) # Если сообщение в начале содержит слово из списка или внутри сообщения содержится слово из списка или сообщение отвечает на сообщение бота - if ((message.text.split(" ")[0] in get_yandexgpt_start_words()) - or (any(word in message.text for word in get_yandexgpt_in_words()))): + if (message.text.split(" ")[0] in get_yandexgpt_start_words()) or ( + any(word in message.text for word in get_yandexgpt_in_words()) + ): # print("message_processing") await log("message_processing") await answer_to_message(message, bot) @@ -92,8 +112,6 @@ async def message_processing(message: types.Message, bot: Bot): await answer_to_message(message, bot) - - router = Router() # Если сообщение содержит текст то вызывается функция message_processing -router.message.register(message_processing, F.text) \ No newline at end of file +router.message.register(message_processing, F.text) diff --git a/src/modules/standard/moderation/info.json b/src/modules/standard/moderation/info.json index ae7e52a..619113e 100644 --- a/src/modules/standard/moderation/info.json +++ b/src/modules/standard/moderation/info.json @@ -3,4 +3,4 @@ "description": "Moderation commands for OCAB", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/moderation/moderation.py b/src/modules/standard/moderation/moderation.py index da78c74..0d97abe 100644 --- a/src/modules/standard/moderation/moderation.py +++ b/src/modules/standard/moderation/moderation.py @@ -1,12 +1,15 @@ +# flake8: noqa + import asyncio -import aiohttp -import aiogram import time + +import aiogram +import aiohttp + from ...standard.config.config import * from ...standard.roles.roles import * - class Moderation: def __init__(self): access_rights = get_access_rights() @@ -19,24 +22,24 @@ class Moderation: async def time_to_seconds(time): # Конвертация текстового указания времени по типу 3h, 5m, 10s в минуты - if time[-1] == 'd': + if time[-1] == "d": return int(time[:-1]) * 86400 - elif time[-1] == 'h': + elif time[-1] == "h": return int(time[:-1]) * 3600 - elif time[-1] == 'm': + elif time[-1] == "m": return int(time[:-1]) * 60 - elif time[-1] == 's': + elif time[-1] == "s": return int(time[:-1]) async def short_time_to_time(self, time): # Конвертация времени в длинное название - if time[-1] == 'd': + if time[-1] == "d": return str(f"{time[0:-1]} дней") - elif time[-1] == 'h': + elif time[-1] == "h": return str(f"{time[0:-1]} часов") - elif time[-1] == 'm': + elif time[-1] == "m": return str(f"{time[0:-1]} минут") - elif time[-1] == 's': + elif time[-1] == "s": return str(f"{time[0:-1]} секунд") async def delete_message(self, chat_id, message_id, bot: aiogram.Bot): @@ -45,6 +48,7 @@ class Moderation: async def ban_user(self, chat_id, user_id, bot: aiogram.Bot): await bot.ban_chat_member(chat_id, user_id) + async def mute_user(chat_id, user_id, time, bot: aiogram.Bot): mutePermissions = { "can_send_messages": False, @@ -60,16 +64,19 @@ async def mute_user(chat_id, user_id, time, bot: aiogram.Bot): "can_change_info": False, "can_invite_users": False, "can_pin_messages": False, - "can_manage_topics": False + "can_manage_topics": False, } end_time = time + int(time.time()) - await bot.restrict_chat_member(chat_id, user_id, until_date=end_time, **mutePermissions) + await bot.restrict_chat_member( + chat_id, user_id, until_date=end_time, **mutePermissions + ) async def unmute_user(chat_id, user_id, bot: aiogram.Bot): - await bot.restrict_chat_member(chat_id, user_id, use_independent_chat_permissions=True) + await bot.restrict_chat_member( + chat_id, user_id, use_independent_chat_permissions=True + ) + async def ban_user(chat_id, user_id, bot: aiogram.Bot): await bot.ban_chat_member(chat_id, user_id) - - diff --git a/src/modules/standard/roles/info.json b/src/modules/standard/roles/info.json index 80d3b3d..73dc7e6 100644 --- a/src/modules/standard/roles/info.json +++ b/src/modules/standard/roles/info.json @@ -3,4 +3,4 @@ "description": "Модуль для работы с ролями", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/roles/roles.py b/src/modules/standard/roles/roles.py index 0421787..2bf3bb8 100644 --- a/src/modules/standard/roles/roles.py +++ b/src/modules/standard/roles/roles.py @@ -1,7 +1,9 @@ -from ..database.db_api import get_user_role from ..config.config import get_config +from ..database.db_api import get_user_role yaml_load = get_config() + + class Roles: user = "USER" moderator = "MODERATOR" diff --git a/src/modules/standard/roles/tests/config.yaml b/src/modules/standard/roles/tests/config.yaml index 65a4d10..338d945 100644 --- a/src/modules/standard/roles/tests/config.yaml +++ b/src/modules/standard/roles/tests/config.yaml @@ -4,4 +4,4 @@ ROLES: ADMIN: 0 MODERATOR: 1 USER: 2 - BOT: 3 \ No newline at end of file + BOT: 3 diff --git a/src/modules/standard/roles/tests/test_roles.py b/src/modules/standard/roles/tests/test_roles.py index 2131a3b..4828306 100644 --- a/src/modules/standard/roles/tests/test_roles.py +++ b/src/modules/standard/roles/tests/test_roles.py @@ -1,6 +1,7 @@ import os import unittest -from ...database.db_api import create_tables, connect_database, add_user + +from ...database.db_api import add_user, connect_database, create_tables from ..roles import Roles @@ -14,10 +15,39 @@ class TestRoles(unittest.IsolatedAsyncioTestCase): cls.database, cls.path = connect_database(is_test=True, module="roles") create_tables(cls.database) - add_user(user_id=1, user_name="TestUser1", user_tag="TestTag1", user_role=0, user_stats=30, user_rep=15) - add_user(user_id=2, user_name="TestUser3", user_tag="TestTag3", user_role=1, user_stats=30, user_rep=15) - add_user(user_id=3, user_name="TestUser4", user_tag="TestTag4", user_role=2, user_stats=30, user_rep=15) - add_user(user_id=4, user_name="TestUser2", user_tag="TestTag2", user_role=3, user_stats=30, user_rep=15) + add_user( + user_id=1, + user_name="TestUser1", + user_tag="TestTag1", + user_role=0, + user_stats=30, + user_rep=15, + ) + add_user( + user_id=2, + user_name="TestUser3", + user_tag="TestTag3", + user_role=1, + user_stats=30, + user_rep=15, + ) + add_user( + user_id=3, + user_name="TestUser4", + user_tag="TestTag4", + user_role=2, + user_stats=30, + user_rep=15, + ) + add_user( + user_id=4, + user_name="TestUser2", + user_tag="TestTag2", + user_role=3, + user_stats=30, + user_rep=15, + ) + async def test_check_admin_permission(cls): cls.assertTrue(await cls.roles.check_admin_permission(1)) cls.assertFalse(await cls.roles.check_admin_permission(2)) @@ -32,7 +62,9 @@ class TestRoles(unittest.IsolatedAsyncioTestCase): async def test_get_role_name(cls): cls.assertEqual(await cls.roles.get_role_name(cls.roles.admin_role_id), "ADMIN") - cls.assertEqual(await cls.roles.get_role_name(cls.roles.moderator_role_id), "MODERATOR") + cls.assertEqual( + await cls.roles.get_role_name(cls.roles.moderator_role_id), "MODERATOR" + ) cls.assertEqual(await cls.roles.get_role_name(cls.roles.user_role_id), "USER") cls.assertEqual(await cls.roles.get_role_name(cls.roles.bot_role_id), "BOT") with cls.assertRaises(ValueError): @@ -41,10 +73,8 @@ class TestRoles(unittest.IsolatedAsyncioTestCase): @classmethod def tearDownClass(cls): cls.database.close() - os.system(f"rm {cls.path}") + os.system(f"rm {cls.path}") # nosec - - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/src/modules/standard/welcome/handlers.py b/src/modules/standard/welcome/handlers.py index 397a107..16102b9 100644 --- a/src/modules/standard/welcome/handlers.py +++ b/src/modules/standard/welcome/handlers.py @@ -1,27 +1,34 @@ -from aiogram import Bot -from aiogram.types import Message -from src.modules.standard.config.config import get_telegram_check_bot -from src.modules.standard.roles.roles import Roles -from src.modules.standard.database.db_api import * -from src.modules.standard.moderation.moderation import mute_user, unmute_user, ban_user -from aiogram.utils.keyboard import InlineKeyboardBuilder -from aiogram.types import inline_keyboard_button as types -import random, asyncio +# flake8: noqa + +import asyncio +import random from threading import Thread +from aiogram import Bot +from aiogram.types import Message +from aiogram.types import inline_keyboard_button as types +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from src.modules.standard.config.config import get_telegram_check_bot +from src.modules.standard.database.db_api import * +from src.modules.standard.moderation.moderation import ban_user, mute_user, unmute_user +from src.modules.standard.roles.roles import Roles + + async def create_math_task(): - first_number = random.randint(1, 100) - second_number = random.randint(1, 100) + first_number = random.randint(1, 100) # nosec + second_number = random.randint(1, 100) # nosec answer = first_number + second_number fake_answers = [] for i in range(3): - diff = random.randint(1, 10) - diff_sign = random.choice(["+", "-"]) + diff = random.randint(1, 10) # nosec + diff_sign = random.choice(["+", "-"]) # nosec fake_answers.append(answer + diff if diff_sign == "+" else answer - diff) fake_answers.append(answer) random.shuffle(fake_answers) return [answer, first_number, second_number, fake_answers] + async def ban_user_timer(chat_id: int, user_id: int, time: int, bot: Bot): await asyncio.sleep(time) if get_user(user_id) is not None: @@ -30,8 +37,6 @@ async def ban_user_timer(chat_id: int, user_id: int, time: int, bot: Bot): await ban_user() - - async def check_new_user(message: Message, bot: Bot): print("check_new_user") if get_telegram_check_bot(): @@ -39,7 +44,10 @@ async def check_new_user(message: Message, bot: Bot): if get_user(message.from_user.id) is None: # Выдаём пользователю ограничение на отправку сообщений на 3 минуты - ban_task = Thread(target=ban_user_timer, args=(message.chat.id, message.from_user.id, 180, bot)) + ban_task = Thread( + target=ban_user_timer, + args=(message.chat.id, message.from_user.id, 180, bot), + ) ban_task.start() # Создаём задачу с отложенным выполнением на 3 минуты @@ -48,27 +56,31 @@ async def check_new_user(message: Message, bot: Bot): builder = InlineKeyboardBuilder() for answer in math_task[3]: if answer == math_task[0]: - builder.add(types.InlineKeyboardButton( - text=answer, - callback_data=f"check_math_task_true") + builder.add( + types.InlineKeyboardButton( + text=answer, callback_data=f"check_math_task_true" + ) ) else: - builder.add(types.InlineKeyboardButton( - text=answer, - callback_data=f"check_math_task_false") + builder.add( + types.InlineKeyboardButton( + text=answer, callback_data=f"check_math_task_false" + ) ) await message.reply( f"Приветствую, {message.from_user.first_name}!\n" f"Для продолжения работы с ботом, пожалуйста, решите математический пример в течении 3х минут:\n" f"*{text}*", - reply_markup=builder.as_markup() + reply_markup=builder.as_markup(), ) async def math_task_true(message: Message, bot: Bot): await message.reply(f"Верно! Добро пожаловать в чат {message.from_user.first_name}") await unmute_user(message.chat.id, message.from_user.id, bot) - add_user(message.from_user.id, - message.from_user.first_name + ' ' + message.from_user.last_name, - message.from_user.username) + add_user( + message.from_user.id, + message.from_user.first_name + " " + message.from_user.last_name, + message.from_user.username, + ) pass diff --git a/src/modules/standard/welcome/info.json b/src/modules/standard/welcome/info.json index cc6c2ae..fde1be0 100644 --- a/src/modules/standard/welcome/info.json +++ b/src/modules/standard/welcome/info.json @@ -3,4 +3,4 @@ "description": "Мо", "author": "OCAB Team", "version": "1.0" -} \ No newline at end of file +} diff --git a/src/modules/standard/welcome/routers.py b/src/modules/standard/welcome/routers.py index 4ddfa6f..2afc09f 100644 --- a/src/modules/standard/welcome/routers.py +++ b/src/modules/standard/welcome/routers.py @@ -1,4 +1,4 @@ -from aiogram import Router, F +from aiogram import F, Router from src.modules.standard.welcome.handlers import check_new_user @@ -7,4 +7,6 @@ router = Router() # Если в чат пришел новый пользователь router.message.register(check_new_user, F.new_chat_members.exists()) # Ловин колбеки от кнопок с callback_data=f"check_math_task_true" -router.callback_query.register(check_new_user, F.callback_data == "check_math_task_true") \ No newline at end of file +router.callback_query.register( + check_new_user, F.callback_data == "check_math_task_true" +) diff --git a/src/service.py b/src/service.py index 4bd226e..64a0293 100644 --- a/src/service.py +++ b/src/service.py @@ -9,13 +9,14 @@ class Path: modules_standard: str modules_custom: str + def _get_paths(path_to_json: str): with open(path_to_json, encoding="utf8") as f: paths = loads(f.read()) return Path( core=paths["core"], modules_standard=paths["modules standard"], - modules_custom=paths["modules custom"] + modules_custom=paths["modules custom"], )