0
0
mirror of https://gitflic.ru/project/maks1ms/ocab.git synced 2024-12-23 16:23:01 +03:00

Merged with chore/code-quality-tools

This commit is contained in:
Maxim Slipenko 2024-07-08 14:20:21 +03:00
commit 2a2b9e15e8
58 changed files with 1004 additions and 299 deletions

1
.bandit Normal file
View File

@ -0,0 +1 @@
[bandit]

6
.flake8 Normal file
View File

@ -0,0 +1,6 @@
[flake8]
per-file-ignores =
__init__.py:F401
max-line-length = 88
count = true
extend-ignore = E203,E701

30
.pre-commit-config.yaml Normal file
View File

@ -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

View File

@ -55,4 +55,4 @@ OCAB - это бот для Telegram, который призван помочь
* SQLite 3 - база данных для хранения информации о чате и пользователях. * SQLite 3 - база данных для хранения информации о чате и пользователях.
* [Poetry](https://gitflic.ru/project/armatik/ocab/blob?file=how-to%20install%20deps.md&branch=OCAB-V2) - менеджер зависимостей. * [Poetry](https://gitflic.ru/project/armatik/ocab/blob?file=how-to%20install%20deps.md&branch=OCAB-V2) - менеджер зависимостей.
* aiogram 3 - библиотека для работы с Telegram API. * aiogram 3 - библиотека для работы с Telegram API.
* peewee - ORM для работы с базой данных. * peewee - ORM для работы с базой данных.

View File

@ -44,4 +44,4 @@ poetry shell
Хранить окружение внутри проекта Хранить окружение внутри проекта
```shell ```shell
poetry config virtualenvs.in-project true poetry config virtualenvs.in-project true
``` ```

View File

@ -1,5 +1,5 @@
from pathlib import Path
from json import dumps from json import dumps
from pathlib import Path
pwd = Path().cwd() pwd = Path().cwd()
dir_core = pwd / "src" / "core" dir_core = pwd / "src" / "core"
@ -7,9 +7,9 @@ dir_modules_standard = pwd / "src" / "modules" / "standard"
dir_modules_custom = pwd / "src" / "modules" / "custom" dir_modules_custom = pwd / "src" / "modules" / "custom"
json = { json = {
'core': str(dir_core), "core": str(dir_core),
'modules standard': str(dir_modules_standard), "modules standard": str(dir_modules_standard),
'modules custom': str(dir_modules_custom), "modules custom": str(dir_modules_custom),
} }
with open("src/paths.json", "w", encoding="utf8") as f: with open("src/paths.json", "w", encoding="utf8") as f:
f.write(dumps(json, indent=4)) f.write(dumps(json, indent=4))

402
poetry.lock generated
View File

@ -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]] [[package]]
name = "aiofiles" name = "aiofiles"
@ -179,6 +179,74 @@ tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] 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]] [[package]]
name = "certifi" name = "certifi"
version = "2024.2.2" version = "2024.2.2"
@ -190,6 +258,17 @@ files = [
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, {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]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.3.2" version = "3.3.2"
@ -289,6 +368,74 @@ files = [
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, {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]] [[package]]
name = "frozenlist" name = "frozenlist"
version = "1.4.1" version = "1.4.1"
@ -375,6 +522,20 @@ files = [
{file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, {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]] [[package]]
name = "idna" name = "idna"
version = "3.7" version = "3.7"
@ -386,6 +547,20 @@ files = [
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, {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]] [[package]]
name = "magic-filter" name = "magic-filter"
version = "1.0.12" version = "1.0.12"
@ -400,6 +575,52 @@ files = [
[package.extras] [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)"] 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]] [[package]]
name = "multidict" name = "multidict"
version = "6.0.5" version = "6.0.5"
@ -499,6 +720,61 @@ files = [
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, {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]] [[package]]
name = "peewee" name = "peewee"
version = "3.17.3" version = "3.17.3"
@ -509,6 +785,51 @@ files = [
{file = "peewee-3.17.3.tar.gz", hash = "sha256:ef15f90b628e41a584be8306cdc3243c51f73ce88b06154d9572f6d0284a0169"}, {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]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.7.1" version = "2.7.1"
@ -619,6 +940,31 @@ files = [
[package.dependencies] [package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 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]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.1"
@ -700,6 +1046,38 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.11.0" version = "4.11.0"
@ -728,6 +1106,26 @@ h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.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]] [[package]]
name = "yarl" name = "yarl"
version = "1.9.4" version = "1.9.4"
@ -834,4 +1232,4 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11.6" python-versions = "^3.11.6"
content-hash = "001611942f2ccb553fc80099924dce739c3cd5febb1438e9bad3e865d1aa8f8b" content-hash = "5b43fa850045f857f8e7b84baa8a9d9b6c9e64758bf8525f4eb772574aafa5ca"

View File

@ -20,6 +20,22 @@ pyyaml = "^6.0.1"
requests = "^2.31.0" 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] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"

View File

@ -1,4 +1,4 @@
#! /bin/sh #! /bin/sh
cd src cd src
python -m unittest discover -v python -m unittest discover -v
cd .. cd ..

View File

@ -1,2 +1,2 @@
import src.core
import src.service import src.service
import src.core

View File

@ -18,4 +18,4 @@ ROLES:
ADMIN: 2 ADMIN: 2
MODERATOR: 1 MODERATOR: 1
USER: 0 USER: 0
BOT: 3 BOT: 3

View File

@ -16,7 +16,7 @@ def setup_logger():
filename=log_file, filename=log_file,
level=logging.INFO, level=logging.INFO,
format="%(asctime)s %(message)s", format="%(asctime)s %(message)s",
datefmt="%H:%M:%S" datefmt="%H:%M:%S",
) )

View File

@ -1,9 +1,11 @@
import asyncio
from aiogram import Bot, Dispatcher
from routers import include_routers from routers import include_routers
from src.core.logger import log, setup_logger from src.core.logger import log, setup_logger
from src.modules.standard.config.config import get_telegram_token from src.modules.standard.config.config import get_telegram_token
from src.modules.standard.database.db_api import connect_database, create_tables from src.modules.standard.database.db_api import connect_database, create_tables
import asyncio
from aiogram import Bot, Dispatcher
async def main(): async def main():

View File

@ -1,8 +1,10 @@
from aiogram import Dispatcher 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.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): async def include_routers(dp: Dispatcher):
@ -13,4 +15,3 @@ async def include_routers(dp: Dispatcher):
dp.include_router(info_router) dp.include_router(info_router)
dp.include_router(admin_router) dp.include_router(admin_router)
dp.include_router(process_message) dp.include_router(process_message)

View File

@ -1 +0,0 @@

View File

@ -1 +1 @@
from . import yandexgpt from . import yandexgpt

View File

@ -1,10 +1,17 @@
# flake8: noqa
import asyncio
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message 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 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): 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 text = message.text
prompt = get_yandexgpt_prompt() prompt = get_yandexgpt_prompt()
# response = await yagpt.async_yandexgpt(system_prompt=prompt, input_messages=text) # 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") reply = await message.reply(response, parse_mode="Markdown")
add_message(reply, message_ai_model="yandexgpt") add_message(reply, message_ai_model="yandexgpt")

View File

@ -3,4 +3,4 @@
"description": "Модуль для работы с Yandex GPT", "description": "Модуль для работы с Yandex GPT",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -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 from src.modules.external.yandexgpt.handlers import answer_to_message
router = Router() router = Router()
# Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message # Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message
router.message.register(answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик")) router.message.register(
answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик")
)

View File

@ -1,24 +1,27 @@
import requests # flake8: noqa
import json
import asyncio import asyncio
import json
import aiohttp import aiohttp
import requests
from src.core.logger import log from src.core.logger import log
from ...standard.database import *
from ...standard.config.config import * from ...standard.config.config import *
from ...standard.database import *
class YandexGPT: class YandexGPT:
token = None token = None
catalog_id = None catalog_id = None
languages = { languages = {
"ru": "русский язык", "ru": "русский язык",
"en": "английский язык", "en": "английский язык",
"de": "немецкий язык", "de": "немецкий язык",
"uk": "украинский язык", "uk": "украинский язык",
"es": "испанский язык", "es": "испанский язык",
"be": "белорусский язык", "be": "белорусский язык",
} }
def __init__(self, token, catalog_id): def __init__(self, token, catalog_id):
self.token = token self.token = token
@ -29,11 +32,13 @@ class YandexGPT:
async with session.post(url, headers=headers, json=prompt) as response: async with session.post(url, headers=headers, json=prompt) as response:
return await response.json() 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" url = "https://llm.api.cloud.yandex.net/foundationModels/v1/tokenizeCompletion"
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Api-Key {self.token}" "Authorization": f"Api-Key {self.token}",
} }
answer_token = get_yandexgpt_token_for_answer() answer_token = get_yandexgpt_token_for_answer()
while True: while True:
@ -43,12 +48,14 @@ class YandexGPT:
"completionOptions": { "completionOptions": {
"stream": stream, "stream": stream,
"temperature": temperature, "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(
except Exception as e: # TODO: Переделать обработку ошибок url=url, headers=headers, prompt=request
)
except Exception as e: # TODO: Переделать обработку ошибок
# print(e) # print(e)
await log(f"Error: {e}") await log(f"Error: {e}")
@ -62,12 +69,19 @@ class YandexGPT:
Exception("IndexError: list index out of range") Exception("IndexError: list index out of range")
return messages 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" url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
gpt = f"gpt://{self.catalog_id}/yandexgpt-lite/latest" gpt = f"gpt://{self.catalog_id}/yandexgpt-lite/latest"
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Api-Key {self.token}" "Authorization": f"Api-Key {self.token}",
} }
messages = [{"role": "system", "text": system_prompt}] messages = [{"role": "system", "text": system_prompt}]
@ -80,27 +94,27 @@ class YandexGPT:
"completionOptions": { "completionOptions": {
"stream": stream, "stream": stream,
"temperature": temperature, "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"] return json.loads(response)["result"]["alternatives"][0]["message"]["text"]
async def async_yandexgpt( async def async_yandexgpt(
self, self,
system_prompt, system_prompt,
input_messages, input_messages,
stream=False, stream=False,
temperature=0.6, temperature=0.6,
max_tokens=get_yandexgpt_token_for_request() max_tokens=get_yandexgpt_token_for_request(),
): ):
url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
gpt = f"gpt://{self.catalog_id}/yandexgpt/latest" gpt = f"gpt://{self.catalog_id}/yandexgpt/latest"
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Api-Key {self.token}" "Authorization": f"Api-Key {self.token}",
} }
messages = [] messages = []
@ -108,21 +122,24 @@ class YandexGPT:
for message in input_messages: for message in input_messages:
messages.append(message) 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 = { request = {
"modelUri": gpt, "modelUri": gpt,
"completionOptions": { "completionOptions": {
"stream": stream, "stream": stream,
"temperature": temperature, "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"] return response["result"]["alternatives"][0]["message"]["text"]
async def async_yandexgpt_translate(self, input_language, output_language, text): async def async_yandexgpt_translate(self, input_language, output_language, text):
input_language = self.languages[input_language] input_language = self.languages[input_language]
output_language = self.languages[output_language] output_language = self.languages[output_language]
@ -130,7 +147,9 @@ class YandexGPT:
return await self.async_yandexgpt( return await self.async_yandexgpt(
f"Переведи на {output_language} сохранив оригинальный смысл текста. Верни только результат:", f"Переведи на {output_language} сохранив оригинальный смысл текста. Верни только результат:",
[{"role": "user", "text": text}], [{"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): async def async_yandexgpt_spelling_check(self, input_language, text):
@ -140,15 +159,19 @@ class YandexGPT:
f"Проверьте орфографию и пунктуацию текста на {input_language}. Верни исправленный текст " f"Проверьте орфографию и пунктуацию текста на {input_language}. Верни исправленный текст "
f"без смысловых искажений:", f"без смысловых искажений:",
[{"role": "user", "text": text}], [{"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" url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
gpt = f"gpt://{self.catalog_id}/summarization/latest" gpt = f"gpt://{self.catalog_id}/summarization/latest"
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": f"Api-Key {self.token}" "Authorization": f"Api-Key {self.token}",
} }
messages = [] messages = []
@ -161,15 +184,17 @@ class YandexGPT:
"completionOptions": { "completionOptions": {
"stream": stream, "stream": stream,
"temperature": temperature, "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"] 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" tts = "tts.api.cloud.yandex.net/speech/v1/tts:synthesize"
# TODO: Сделать функцию TTS # TODO: Сделать функцию TTS
return 0 return 0
@ -187,14 +212,18 @@ class YandexGPT:
if db_api.get_message_ai_model(chat_id, message_id) != None: if db_api.get_message_ai_model(chat_id, message_id) != None:
messages.append({"role": "assistant", "text": message}) messages.append({"role": "assistant", "text": message})
else: 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}) messages.append({"role": "user", "text": sender_name + ": " + message})
message_id = db_api.get_answer_to_message_id(chat_id, message_id) message_id = db_api.get_answer_to_message_id(chat_id, message_id)
if message_id is None: if message_id is None:
break break
return list(reversed(messages)) 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 = [] messages = []
# Собираем цепочку сообщений в формате: [{"role": "user", "text": "<Имя_пользователя>: Привет!"}, # Собираем цепочку сообщений в формате: [{"role": "user", "text": "<Имя_пользователя>: Привет!"},
# {"role": "assistant", "text": "Привет!"}] # {"role": "assistant", "text": "Привет!"}]
@ -203,21 +232,33 @@ class YandexGPT:
if db_api.get_message_ai_model(chat_id, start_message_id) != None: if db_api.get_message_ai_model(chat_id, start_message_id) != None:
messages.append({"role": "assistant", "text": message}) messages.append({"role": "assistant", "text": message})
else: 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}) messages.append({"role": "user", "text": sender_name + ": " + message})
start_message_id -= 1 start_message_id -= 1
if start_message_id <= end_message_id: if start_message_id <= end_message_id:
break break
return messages.reverse() return messages.reverse()
async def yandexgpt_request(self, message_id = None, type = "yandexgpt-lite", chat_id = None, async def yandexgpt_request(
message_id_end = None, input_language = None, output_language = None, text = None): 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": if type == "yandexgpt-lite":
messages = await self.collect_messages(message_id, chat_id) messages = await self.collect_messages(message_id, chat_id)
return await self.async_yandexgpt_lite( return await self.async_yandexgpt_lite(
system_prompt=get_yandexgpt_prompt(), system_prompt=get_yandexgpt_prompt(),
input_messages=messages, input_messages=messages,
stream=False, temperature=0.6, max_tokens=8000 stream=False,
temperature=0.6,
max_tokens=8000,
) )
elif type == "yandexgpt": elif type == "yandexgpt":
# print("yandexgpt_request") # print("yandexgpt_request")
@ -226,24 +267,26 @@ class YandexGPT:
return await self.async_yandexgpt( return await self.async_yandexgpt(
system_prompt=get_yandexgpt_prompt(), system_prompt=get_yandexgpt_prompt(),
input_messages=messages, 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": elif type == "yandexgpt-translate":
return await self.async_yandexgpt_translate( return await self.async_yandexgpt_translate(
input_language, input_language,
output_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": elif type == "yandexgpt-spelling-check":
return await self.async_yandexgpt_spelling_check( return await self.async_yandexgpt_spelling_check(
input_language, input_language, text=db_api.get_message_text(chat_id, message_id)
text=db_api.get_message_text(chat_id, message_id)
) )
elif type == "yandexgpt-text-history": 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( return await self.async_yandexgpt_text_history(
messages=messages, messages=messages, stream=False, temperature=0.6, max_tokens=8000
stream=False, temperature=0.6, max_tokens=8000
) )
else: else:
return "Ошибка: Неизвестный тип запроса | Error: Unknown request type" return "Ошибка: Неизвестный тип запроса | Error: Unknown request type"

View File

@ -1 +1 @@
from . import config, database, exceptions, roles from . import config, database, exceptions, roles

View File

@ -1,18 +1,26 @@
# flake8: noqa
import time
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message from aiogram.types import Message
from src.modules.standard.config.config import get_default_chat_tag from src.modules.standard.config.config import get_default_chat_tag
import time
async def delete_message(message: Message, bot: Bot): async def delete_message(message: Message, bot: Bot):
reply_message_id = message.reply_to_message.message_id reply_message_id = message.reply_to_message.message_id
await bot.delete_message(message.chat.id, reply_message_id) await bot.delete_message(message.chat.id, reply_message_id)
async def error_access(message: Message, bot: Bot): async def error_access(message: Message, bot: Bot):
await message.reply("Вы не админ/модератор") await message.reply("Вы не админ/модератор")
async def get_chat_id(message: Message, bot: Bot): 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): async def chat_not_in_approve_list(message: Message, bot: Bot):
await message.reply( await message.reply(
@ -22,6 +30,7 @@ async def chat_not_in_approve_list(message: Message, bot: Bot):
) )
await get_chat_id(message, bot) await get_chat_id(message, bot)
async def mute_user(chat_id: int, user_id: int, time: int, bot: 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) # *, 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_change_info": False,
"can_invite_users": False, "can_invite_users": False,
"can_pin_messages": False, "can_pin_messages": False,
"can_manage_topics": False "can_manage_topics": False,
} }
end_time = time + int(time.time()) 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
)

View File

@ -3,4 +3,4 @@
"description": "Модуль для работы с админкой", "description": "Модуль для работы с админкой",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -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.admin.handlers import (
from src.modules.standard.filters.filters import ChatModerOrAdminFilter, ChatNotInApproveFilter chat_not_in_approve_list,
delete_message,
error_access,
get_chat_id,
)
from src.modules.standard.filters.filters import (
ChatModerOrAdminFilter,
ChatNotInApproveFilter,
)
router = Router() router = Router()
# Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list # Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list
router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text) 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(delete_message, ChatModerOrAdminFilter(), F.text == "/rm")
router.message.register(error_access, F.text == '/rm') router.message.register(error_access, F.text == "/rm")
router.message.register(error_access, F.text == '/chatID') router.message.register(error_access, F.text == "/chatID")

View File

@ -1,4 +1,7 @@
# flake8: noqa
import yaml import yaml
from ....service import paths from ....service import paths
@ -9,52 +12,64 @@ def get_config(is_test: bool = False) -> dict:
path = paths.core path = paths.core
path = f"{path}/config.yaml" path = f"{path}/config.yaml"
with open(path, 'r') as file: with open(path, "r") as file:
return yaml.full_load(file) return yaml.full_load(file)
config = get_config() config = get_config()
def get_telegram_token() -> str: def get_telegram_token() -> str:
return config["TELEGRAM"]["TOKEN"] return config["TELEGRAM"]["TOKEN"]
def get_telegram_check_bot() -> bool: def get_telegram_check_bot() -> bool:
return config["TELEGRAM"]["CHECK_BOT"] return config["TELEGRAM"]["CHECK_BOT"]
def get_aproved_chat_id() -> list: def get_aproved_chat_id() -> list:
# Возваращем сплитованный список id чатов в формате int # Возваращем сплитованный список 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: def get_user_role_name(role_number) -> dict:
# Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно # Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно
return config["ROLES"].get(role_number, "Неизвестно") return config["ROLES"].get(role_number, "Неизвестно")
def get_default_chat_tag() -> str: def get_default_chat_tag() -> str:
return config["TELEGRAM"]["DEFAULT_CHAT_TAG"] return config["TELEGRAM"]["DEFAULT_CHAT_TAG"]
def get_yandexgpt_token() -> str: def get_yandexgpt_token() -> str:
return config["YANDEXGPT"]["TOKEN"] return config["YANDEXGPT"]["TOKEN"]
def get_yandexgpt_catalog_id() -> str: def get_yandexgpt_catalog_id() -> str:
return config["YANDEXGPT"]["CATALOGID"] return config["YANDEXGPT"]["CATALOGID"]
def get_yandexgpt_prompt() -> str: def get_yandexgpt_prompt() -> str:
return config["YANDEXGPT"]["PROMPT"] return config["YANDEXGPT"]["PROMPT"]
def get_yandexgpt_start_words() -> list: def get_yandexgpt_start_words() -> list:
return config["YANDEXGPT"]["STARTWORD"].split(" | ") return config["YANDEXGPT"]["STARTWORD"].split(" | ")
def get_yandexgpt_in_words() -> list: def get_yandexgpt_in_words() -> list:
return config["YANDEXGPT"]["INWORD"].split(" | ") return config["YANDEXGPT"]["INWORD"].split(" | ")
def get_yandexgpt_token_for_request() -> int: def get_yandexgpt_token_for_request() -> int:
return config["YANDEXGPT"]["TOKEN_FOR_REQUEST"] return config["YANDEXGPT"]["TOKEN_FOR_REQUEST"]
def get_yandexgpt_token_for_answer() -> int: def get_yandexgpt_token_for_answer() -> int:
return config["YANDEXGPT"]["TOKEN_FOR_ANSWER"] return config["YANDEXGPT"]["TOKEN_FOR_ANSWER"]
def get_access_rights() -> dict: def get_access_rights() -> dict:
return get_config()["ACCESS_RIGHTS"] return get_config()["ACCESS_RIGHTS"]

View File

@ -3,4 +3,4 @@
"description": "Модуль для работы с конфигурационным файлом бота (YAML)", "description": "Модуль для работы с конфигурационным файлом бота (YAML)",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -4,4 +4,4 @@ ROLES:
ADMIN: 0 ADMIN: 0
MODERATOR: 1 MODERATOR: 1
USER: 2 USER: 2
BOT: 3 BOT: 3

View File

@ -1,6 +1,7 @@
from src.modules.standard.config.config import get_config
import unittest import unittest
from src.modules.standard.config.config import get_config
yaml_load = get_config(is_test=True) yaml_load = get_config(is_test=True)
@ -18,20 +19,27 @@ class TestConfig(unittest.TestCase):
def test_yaml_keys_existence(self): def test_yaml_keys_existence(self):
self.assertTrue(all(key in yaml_load for key in ["TELEGRAM", "ROLES"])) self.assertTrue(all(key in yaml_load for key in ["TELEGRAM", "ROLES"]))
self.assertIn("TOKEN", yaml_load["TELEGRAM"]) 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): def test_yaml_yaml_load_types(self):
self.assertIsInstance(yaml_load["TELEGRAM"]["TOKEN"], str) 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): def test_yaml_values(self):
expected_token = 'xxxxxxxxxxxxxxxxxxxx' expected_token = "xxxxxxxxxxxxxxxxxxxx" # nosec
expected_role_values = {'ADMIN': 0, 'MODERATOR': 1, 'USER': 2, 'BOT': 3} expected_role_values = {"ADMIN": 0, "MODERATOR": 1, "USER": 2, "BOT": 3}
self.assertEqual(yaml_load["TELEGRAM"]["TOKEN"], expected_token) self.assertEqual(yaml_load["TELEGRAM"]["TOKEN"], expected_token)
for role, value in expected_role_values.items(): for role, value in expected_role_values.items():
self.assertEqual(yaml_load["ROLES"][role], value) self.assertEqual(yaml_load["ROLES"][role], value)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -4,7 +4,7 @@
Модуль содержит в себе следующие таблицы: Модуль содержит в себе следующие таблицы:
* `Chats` - таблица для хранения информации о чатах. * `Chats` - таблица для хранения информации о чатах.
* `Users` - таблица для хранения информации о пользователях. * `Users` - таблица для хранения информации о пользователях.
* `Messages` - таблица для хранения информации о сообщениях. * `Messages` - таблица для хранения информации о сообщениях.
* `ChatStats` - таблица для хранения статистики чатов по дням. * `ChatStats` - таблица для хранения статистики чатов по дням.
@ -28,9 +28,9 @@ Cтруктура таблицы `Users`:
руктура таблицы `Messages`: руктура таблицы `Messages`:
* `message_chat_id` - идентификатор чата в котором отправлено сообщение. * `message_chat_id` - идентификатор чата в котором отправлено сообщение.
* `message_id` - идентификатор сообщения. * `message_id` - идентификатор сообщения.
* `messag_sender_id` - идентификатор пользователя отправившего сообщение. Если сообщение отправил бот, то * `messag_sender_id` - идентификатор пользователя отправившего сообщение. Если сообщение отправил бот, то
`messag_sender_id` = 0. `messag_sender_id` = 0.
* `answer_to_message_id` - идентификатор сообщения на которое дан ответ. Если ответа нет или ответ на служебное * `answer_to_message_id` - идентификатор сообщения на которое дан ответ. Если ответа нет или ответ на служебное
сообщение о создании топика в чатах с форумным типом, то `answer_to_message_id` = 0. сообщение о создании топика в чатах с форумным типом, то `answer_to_message_id` = 0.
* `message_ai_model` - идентификатор модели нейросети, которая использовалась для генерации ответа. Если ответ' * `message_ai_model` - идентификатор модели нейросети, которая использовалась для генерации ответа. Если ответ'
сгенерирован не был, то `message_ai_model` = null. сгенерирован не был, то `message_ai_model` = null.
@ -45,4 +45,4 @@ Cтруктура таблицы `UserStats`:
* `chat_id` - идентификатор чата для которого собрана статистика. * `chat_id` - идентификатор чата для которого собрана статистика.
* `user_id` - идентификатор пользователя для которого собрана статистика. * `user_id` - идентификатор пользователя для которого собрана статистика.
* `date` - дата на которую собрана статистика. * `date` - дата на которую собрана статистика.
* `messages_count` - количество сообщений отправленных пользователем в чат за день. * `messages_count` - количество сообщений отправленных пользователем в чат за день.

View File

@ -1 +1 @@
from . import db_api, models from . import db_api, models

View File

@ -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 import peewee as pw
from aiogram.types import Message 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): def connect_database(is_test: bool = False, module: str | None = None):
if is_test: 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): def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0):
chat, created = Chats.get_or_create(id=chat_id, defaults={ chat, created = Chats.get_or_create(
'chat_name': chat_name, id=chat_id,
'chat_type': chat_type, defaults={
'chat_all_stat': chat_stats, "chat_name": chat_name,
}) "chat_type": chat_type,
"chat_all_stat": chat_stats,
},
)
if not created: if not created:
# Обновить существующий чат, если он уже существует # Обновить существующий чат, если он уже существует
chat.chat_name = chat_name chat.chat_name = chat_name
@ -50,19 +54,30 @@ def add_chat(chat_id, chat_name, chat_type=10, chat_stats=0):
chat.save() 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: if user_last_name is None:
user_name = user_first_name user_name = user_first_name
else: else:
user_name = user_first_name + " " + user_last_name user_name = user_first_name + " " + user_last_name
user, created = Users.get_or_create(id=user_id, defaults={ user, created = Users.get_or_create(
'user_tag': user_tag, id=user_id,
'user_name': user_name, defaults={
'user_role': user_role, "user_tag": user_tag,
'user_stats': user_stats, "user_name": user_name,
'user_rep': user_rep "user_role": user_role,
}) "user_stats": user_stats,
"user_rep": user_rep,
},
)
if not created: if not created:
# Обновить существующего пользователя, если он уже существует # Обновить существующего пользователя, если он уже существует
user.user_tag = user_tag 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, message_sender_id=message.from_user.id,
answer_to_message_id=answer_to_message_id, answer_to_message_id=answer_to_message_id,
message_ai_model=message_ai_model, message_ai_model=message_ai_model,
message_text=message.text message_text=message.text,
) )
def add_chat_stats(chat_id, date, messages_count): def add_chat_stats(chat_id, date, messages_count):
ChatStats.create( ChatStats.create(chat_id=chat_id, date=date, messages_count=messages_count)
chat_id=chat_id,
date=date,
messages_count=messages_count
)
def add_user_stats(chat_id, user_id, date, messages_count): def add_user_stats(chat_id, user_id, date, messages_count):
UserStats.create( UserStats.create(
chat_id=chat_id, chat_id=chat_id, user_id=user_id, date=date, messages_count=messages_count
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) chat = Chats.get_or_none(Chats.id == chat_id)
return chat.chat_all_stat if chat else None 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) user = Users.get_or_none(Users.id == user_id)
return user.user_tag if user else None return user.user_tag if user else None
def get_user_id(user_tag): def get_user_id(user_tag):
user = Users.get_or_none(Users.user_tag == user_tag) user = Users.get_or_none(Users.user_tag == user_tag)
return user.id if user else None 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 = Users.update(user_role=new_user_role).where(Users.id == user_id)
query.execute() query.execute()
# Работа с таблицей сообщений # Работа с таблицей сообщений
def get_message(message_chat_id, message_id): 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): 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 return message.message_sender_id if message else None
def get_message_text(message_chat_id, message_id): 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 return message.message_text if message else None
def get_message_ai_model(message_chat_id, message_id): 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 return message.message_ai_model if message else None
def get_answer_to_message_id(message_chat_id, message_id): 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 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 chat_stats[chat_stat.date] = chat_stat.messages_count
return chat_stats return chat_stats
# Работа с таблицей статистики пользователей # Работа с таблицей статистики пользователей
@ -223,26 +247,28 @@ def get_user_stats(user_id):
user_stats[user_stat.date] = user_stat.messages_count user_stats[user_stat.date] = user_stat.messages_count
return user_stats return user_stats
# Функции обновления # Функции обновления
def update_chat_all_stat(chat_id): 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() query.execute()
def update_chat_stats(chat_id, date): 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: if chat_stats:
query = ChatStats.update(messages_count=ChatStats.messages_count + 1).where(ChatStats.chat_id == chat_id, query = ChatStats.update(messages_count=ChatStats.messages_count + 1).where(
ChatStats.date == date) ChatStats.chat_id == chat_id, ChatStats.date == date
)
query.execute() query.execute()
else: else:
ChatStats.create( ChatStats.create(chat_id=chat_id, date=date, messages_count=1)
chat_id=chat_id,
date=date,
messages_count=1
)
def update_user_all_stat(user_id): 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 = Users.update(user_stats=Users.user_stats + 1).where(Users.id == user_id)
query.execute() query.execute()
else: else:
Users.create( Users.create(id=user_id, user_stats=1)
id=user_id,
user_stats=1
)
def update_user_rep(user_id): 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 = Users.update(user_rep=Users.user_rep + 1).where(Users.id == user_id)
query.execute() query.execute()
else: else:
Users.create( Users.create(id=user_id, user_rep=1)
id=user_id,
user_rep=1
)
def update_user_stats(chat_id, user_id, date): 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, user_stats = UserStats.get_or_none(
UserStats.date == date) UserStats.chat_id == chat_id,
UserStats.user_id == user_id,
UserStats.date == date,
)
if user_stats: if user_stats:
query = UserStats.update(messages_count=UserStats.messages_count + 1).where(UserStats.chat_id == chat_id, query = UserStats.update(messages_count=UserStats.messages_count + 1).where(
UserStats.user_id == user_id, UserStats.chat_id == chat_id,
UserStats.date == date) UserStats.user_id == user_id,
UserStats.date == date,
)
query.execute() query.execute()
else: else:
UserStats.create( UserStats.create(chat_id=chat_id, user_id=user_id, date=date, messages_count=1)
chat_id=chat_id,
user_id=user_id,
date=date,
messages_count=1
)

View File

@ -3,4 +3,4 @@
"description": "Модуль для работы с БД", "description": "Модуль для работы с БД",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -2,8 +2,8 @@ import peewee as pw
class ChatStats(pw.Model): class ChatStats(pw.Model):
class Meta: class Meta: ...
...
chat_id = pw.IntegerField(null=False) chat_id = pw.IntegerField(null=False)
date = pw.DateField(null=False) date = pw.DateField(null=False)
messages_count = pw.IntegerField(null=False, default=0) messages_count = pw.IntegerField(null=False, default=0)

View File

@ -2,8 +2,8 @@ import peewee as pw
class Chats(pw.Model): class Chats(pw.Model):
class Meta: class Meta: ...
...
chat_name = pw.CharField(null=False) chat_name = pw.CharField(null=False)
chat_type = pw.IntegerField(null=False, default=10) chat_type = pw.IntegerField(null=False, default=10)
chat_all_stat = pw.IntegerField(null=False) chat_all_stat = pw.IntegerField(null=False)

View File

@ -2,12 +2,11 @@ import peewee as pw
class Messages(pw.Model): class Messages(pw.Model):
class Meta: class Meta: ...
...
message_chat_id = pw.IntegerField(null=False) message_chat_id = pw.IntegerField(null=False)
message_id = pw.IntegerField(null=False) message_id = pw.IntegerField(null=False)
message_sender_id = pw.IntegerField(null=False) message_sender_id = pw.IntegerField(null=False)
answer_to_message_id = pw.IntegerField(null=True) answer_to_message_id = pw.IntegerField(null=True)
message_ai_model = pw.TextField(null=True) message_ai_model = pw.TextField(null=True)
message_text = pw.TextField(null=False) message_text = pw.TextField(null=False)

View File

@ -2,8 +2,8 @@ import peewee as pw
class UserStats(pw.Model): class UserStats(pw.Model):
class Meta: class Meta: ...
...
chat_id = pw.IntegerField(null=False) chat_id = pw.IntegerField(null=False)
user_id = pw.IntegerField(null=False) user_id = pw.IntegerField(null=False)
date = pw.DateField(null=False) date = pw.DateField(null=False)

View File

@ -2,8 +2,8 @@ import peewee as pw
class Users(pw.Model): class Users(pw.Model):
class Meta: class Meta: ...
...
user_tag = pw.CharField(null=True) user_tag = pw.CharField(null=True)
user_name = pw.CharField(null=False) # до 255 символов user_name = pw.CharField(null=False) # до 255 символов
user_role = pw.IntegerField(null=True, default=3) user_role = pw.IntegerField(null=True, default=3)

View File

@ -1 +1 @@
Эта директория для тестовой БД Эта директория для тестовой БД

View File

@ -1,8 +1,10 @@
import unittest # flake8: noqa
import os
import os
import unittest
from ..db_api import *
from ...exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName from ...exceptions.module_exceptions import MissingModuleName, NotExpectedModuleName
from ..db_api import *
class TestDatabaseAPI(unittest.TestCase): class TestDatabaseAPI(unittest.TestCase):
@ -13,6 +15,7 @@ class TestDatabaseAPI(unittest.TestCase):
def setUpClass(cls): def setUpClass(cls):
cls.database, cls.path = connect_database(is_test=True, module="database") cls.database, cls.path = connect_database(is_test=True, module="database")
create_tables(cls.database) create_tables(cls.database)
def test_fail_connect(cls): def test_fail_connect(cls):
with cls.assertRaises(MissingModuleName): with cls.assertRaises(MissingModuleName):
cls.database, cls.path = connect_database(is_test=True) cls.database, cls.path = connect_database(is_test=True)
@ -35,8 +38,12 @@ class TestDatabaseAPI(unittest.TestCase):
self.assertEqual(chat2.chat_role, 1) self.assertEqual(chat2.chat_role, 1)
def test_add_and_get_message(self): 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(
add_message(message_id=2, message_text="Test Message 2", message_sender=2, answer_id=1) 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) message1 = get_message(1)
self.assertIsNotNone(message1) self.assertIsNotNone(message1)
@ -49,8 +56,22 @@ class TestDatabaseAPI(unittest.TestCase):
self.assertEqual(message2.message_text, "Test Message 2") self.assertEqual(message2.message_text, "Test Message 2")
def test_add_and_get_user(self): 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(
add_user(user_id=101, user_name="TestUser2", user_tag="TestTag2", user_role=1, user_stats=20, user_rep=10) 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) user1 = get_user(100)
self.assertIsNotNone(user1) self.assertIsNotNone(user1)
@ -63,8 +84,22 @@ class TestDatabaseAPI(unittest.TestCase):
self.assertEqual(user2.user_name, "TestUser2") self.assertEqual(user2.user_name, "TestUser2")
def test_get_user_role(self): 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(
add_user(user_id=103, user_name="TestUser4", user_tag="TestTag4", user_role=1, user_stats=40, user_rep=20) 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) user_role1 = get_user_role(102)
self.assertEqual(user_role1, 0) self.assertEqual(user_role1, 0)
@ -73,12 +108,26 @@ class TestDatabaseAPI(unittest.TestCase):
self.assertEqual(user_role2, 1) self.assertEqual(user_role2, 1)
def test_change_user_name(self): 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") change_user_name(104, "NewName1")
updated_user1 = get_user(104) updated_user1 = get_user(104)
self.assertEqual(updated_user1.user_name, "NewName1") 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") change_user_name(105, "NewName2")
updated_user2 = get_user(105) updated_user2 = get_user(105)
self.assertEqual(updated_user2.user_name, "NewName2") self.assertEqual(updated_user2.user_name, "NewName2")
@ -86,8 +135,8 @@ class TestDatabaseAPI(unittest.TestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.database.close() cls.database.close()
os.system(f"rm {cls.path}") os.system(f"rm {cls.path}") # nosec
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -1 +1 @@
from . import module_exceptions from . import module_exceptions

View File

@ -3,4 +3,4 @@
"description": "Модуль с исключениями", "description": "Модуль с исключениями",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -10,4 +10,3 @@ class NotExpectedModuleName(BaseException):
self.message = "Не ожидалось название директории модуля" self.message = "Не ожидалось название директории модуля"
super().__init__(self.message) super().__init__(self.message)

View File

@ -1,18 +1,23 @@
from aiogram import Bot
from aiogram.filters import BaseFilter from aiogram.filters import BaseFilter
from aiogram.types import Message 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.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): class ChatModerOrAdminFilter(BaseFilter):
async def __call__(self, message: Message, bot: Bot) -> bool: async def __call__(self, message: Message, bot: Bot) -> bool:
user_id = message.from_user.id user_id = message.from_user.id
roles = Roles() roles = Roles()
admins = await bot.get_chat_administrators(message.chat.id) admins = await bot.get_chat_administrators(message.chat.id)
return await roles.check_admin_permission(user_id) or \ return (
await roles.check_moderator_permission(user_id) or any(user_id == admin.user.id for admin in admins) 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): class ChatNotInApproveFilter(BaseFilter):
async def __call__(self, message: Message, bot: Bot) -> bool: async def __call__(self, message: Message, bot: Bot) -> bool:

View File

@ -3,4 +3,4 @@
"description": "Модуль с фильтрами", "description": "Модуль с фильтрами",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -1,13 +1,20 @@
# flake8: noqa
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message 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.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): 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: 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: elif user_id == bot.id:
await message.reply("Это сообщение было отправлено ботом") await message.reply("Это сообщение было отправлено ботом")
elif get_user(user_id) is None: elif get_user(user_id) is None:
@ -44,11 +51,14 @@ async def get_user_info(message: Message, bot: Bot):
else: else:
await get_info_answer_by_id(message, bot, message.from_user.id) await get_info_answer_by_id(message, bot, message.from_user.id)
except Exception as e: except Exception as e:
await message.reply("В вашем запросе что-то пошло не так," await message.reply(
" попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение") "В вашем запросе что-то пошло не так,"
" попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение"
)
# print(e) # print(e)
await log(e) await log(e)
async def get_chat_info(message: Message, bot: Bot): async def get_chat_info(message: Message, bot: Bot):
answer = ( answer = (
f"*Название чата:* {message.chat.title}\n" 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"*Количество пользователей в чате:* {await bot.get_chat_member_count(message.chat.id)}\n"
f"*Количество администраторов в чате:* {len(await bot.get_chat_administrators(message.chat.id))}" f"*Количество администраторов в чате:* {len(await bot.get_chat_administrators(message.chat.id))}"
) )
await message.reply(answer, parse_mode="MarkdownV2") await message.reply(answer, parse_mode="MarkdownV2")

View File

@ -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 = Router()
router.message.register(get_user_info, F.text.startswith("/info") == True) router.message.register(get_user_info, F.text.startswith("/info") == True)
router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True) router.message.register(get_chat_info, F.text.startswith("/chatinfo") == True)

View File

@ -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.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): 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}") await log(f"Chat added: {message.chat.id} {message.chat.title}")
else: else:
# print(f"Chat not in approve list: {message.chat.id} {message.chat.title}") # 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 pass
else: else:
# Проверяем обновление названия чата # Проверяем обновление названия чата
@ -34,6 +42,7 @@ async def chat_check(message: types.Message):
await log(f"Chat already exists: {message.chat.id} {message.chat.title}") await log(f"Chat already exists: {message.chat.id} {message.chat.title}")
pass pass
async def user_check(message: types.Message): async def user_check(message: types.Message):
# Проверка наличия id пользователя в базе данных пользователей # Проверка наличия id пользователя в базе данных пользователей
# Если пользователя нет в базе данных, то добавляем его # Если пользователя нет в базе данных, то добавляем его
@ -42,17 +51,24 @@ async def user_check(message: types.Message):
if message.from_user.last_name is None: if message.from_user.last_name is None:
current_user_name = message.from_user.first_name current_user_name = message.from_user.first_name
else: 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: if get_user(message.from_user.id) is None:
add_user(message.from_user.id, add_user(
message.from_user.first_name, message.from_user.last_name, message.from_user.id,
message.from_user.username) 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}") # 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}") await log(f"User added: {message.from_user.id} {current_user_name}")
else: else:
# print(f"User already exists: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name} {message.from_user.username}") # 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: if get_user_name(message.from_user.id) != current_user_name:
change_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: if get_user_tag(message.from_user.id) != message.from_user.username:
change_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}") # 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 pass
async def add_stats(message: types.Message): async def add_stats(message: types.Message):
# Добавляем пользователю и чату статистику # Добавляем пользователю и чату статистику
update_chat_all_stat(message.chat.id) update_chat_all_stat(message.chat.id)
@ -79,8 +98,9 @@ async def message_processing(message: types.Message, bot: Bot):
add_message(message) add_message(message)
# Если сообщение в начале содержит слово из списка или внутри сообщения содержится слово из списка или сообщение отвечает на сообщение бота # Если сообщение в начале содержит слово из списка или внутри сообщения содержится слово из списка или сообщение отвечает на сообщение бота
if ((message.text.split(" ")[0] in get_yandexgpt_start_words()) if (message.text.split(" ")[0] in get_yandexgpt_start_words()) or (
or (any(word in message.text for word in get_yandexgpt_in_words()))): any(word in message.text for word in get_yandexgpt_in_words())
):
# print("message_processing") # print("message_processing")
await log("message_processing") await log("message_processing")
await answer_to_message(message, bot) 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) await answer_to_message(message, bot)
router = Router() router = Router()
# Если сообщение содержит текст то вызывается функция message_processing # Если сообщение содержит текст то вызывается функция message_processing
router.message.register(message_processing, F.text) router.message.register(message_processing, F.text)

View File

@ -3,4 +3,4 @@
"description": "Moderation commands for OCAB", "description": "Moderation commands for OCAB",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -1,12 +1,15 @@
# flake8: noqa
import asyncio import asyncio
import aiohttp
import aiogram
import time import time
import aiogram
import aiohttp
from ...standard.config.config import * from ...standard.config.config import *
from ...standard.roles.roles import * from ...standard.roles.roles import *
class Moderation: class Moderation:
def __init__(self): def __init__(self):
access_rights = get_access_rights() access_rights = get_access_rights()
@ -19,24 +22,24 @@ class Moderation:
async def time_to_seconds(time): async def time_to_seconds(time):
# Конвертация текстового указания времени по типу 3h, 5m, 10s в минуты # Конвертация текстового указания времени по типу 3h, 5m, 10s в минуты
if time[-1] == 'd': if time[-1] == "d":
return int(time[:-1]) * 86400 return int(time[:-1]) * 86400
elif time[-1] == 'h': elif time[-1] == "h":
return int(time[:-1]) * 3600 return int(time[:-1]) * 3600
elif time[-1] == 'm': elif time[-1] == "m":
return int(time[:-1]) * 60 return int(time[:-1]) * 60
elif time[-1] == 's': elif time[-1] == "s":
return int(time[:-1]) return int(time[:-1])
async def short_time_to_time(self, time): async def short_time_to_time(self, time):
# Конвертация времени в длинное название # Конвертация времени в длинное название
if time[-1] == 'd': if time[-1] == "d":
return str(f"{time[0:-1]} дней") return str(f"{time[0:-1]} дней")
elif time[-1] == 'h': elif time[-1] == "h":
return str(f"{time[0:-1]} часов") return str(f"{time[0:-1]} часов")
elif time[-1] == 'm': elif time[-1] == "m":
return str(f"{time[0:-1]} минут") return str(f"{time[0:-1]} минут")
elif time[-1] == 's': elif time[-1] == "s":
return str(f"{time[0:-1]} секунд") return str(f"{time[0:-1]} секунд")
async def delete_message(self, chat_id, message_id, bot: aiogram.Bot): 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): async def ban_user(self, chat_id, user_id, bot: aiogram.Bot):
await bot.ban_chat_member(chat_id, user_id) await bot.ban_chat_member(chat_id, user_id)
async def mute_user(chat_id, user_id, time, bot: aiogram.Bot): async def mute_user(chat_id, user_id, time, bot: aiogram.Bot):
mutePermissions = { mutePermissions = {
"can_send_messages": False, "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_change_info": False,
"can_invite_users": False, "can_invite_users": False,
"can_pin_messages": False, "can_pin_messages": False,
"can_manage_topics": False "can_manage_topics": False,
} }
end_time = time + int(time.time()) 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): 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): async def ban_user(chat_id, user_id, bot: aiogram.Bot):
await bot.ban_chat_member(chat_id, user_id) await bot.ban_chat_member(chat_id, user_id)

View File

@ -3,4 +3,4 @@
"description": "Модуль для работы с ролями", "description": "Модуль для работы с ролями",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -1,7 +1,9 @@
from ..database.db_api import get_user_role
from ..config.config import get_config from ..config.config import get_config
from ..database.db_api import get_user_role
yaml_load = get_config() yaml_load = get_config()
class Roles: class Roles:
user = "USER" user = "USER"
moderator = "MODERATOR" moderator = "MODERATOR"

View File

@ -4,4 +4,4 @@ ROLES:
ADMIN: 0 ADMIN: 0
MODERATOR: 1 MODERATOR: 1
USER: 2 USER: 2
BOT: 3 BOT: 3

View File

@ -1,6 +1,7 @@
import os import os
import unittest 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 from ..roles import Roles
@ -14,10 +15,39 @@ class TestRoles(unittest.IsolatedAsyncioTestCase):
cls.database, cls.path = connect_database(is_test=True, module="roles") cls.database, cls.path = connect_database(is_test=True, module="roles")
create_tables(cls.database) 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(
add_user(user_id=2, user_name="TestUser3", user_tag="TestTag3", user_role=1, user_stats=30, user_rep=15) user_id=1,
add_user(user_id=3, user_name="TestUser4", user_tag="TestTag4", user_role=2, user_stats=30, user_rep=15) user_name="TestUser1",
add_user(user_id=4, user_name="TestUser2", user_tag="TestTag2", user_role=3, user_stats=30, user_rep=15) 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): async def test_check_admin_permission(cls):
cls.assertTrue(await cls.roles.check_admin_permission(1)) cls.assertTrue(await cls.roles.check_admin_permission(1))
cls.assertFalse(await cls.roles.check_admin_permission(2)) cls.assertFalse(await cls.roles.check_admin_permission(2))
@ -32,7 +62,9 @@ class TestRoles(unittest.IsolatedAsyncioTestCase):
async def test_get_role_name(cls): 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.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.user_role_id), "USER")
cls.assertEqual(await cls.roles.get_role_name(cls.roles.bot_role_id), "BOT") cls.assertEqual(await cls.roles.get_role_name(cls.roles.bot_role_id), "BOT")
with cls.assertRaises(ValueError): with cls.assertRaises(ValueError):
@ -41,10 +73,8 @@ class TestRoles(unittest.IsolatedAsyncioTestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.database.close() cls.database.close()
os.system(f"rm {cls.path}") os.system(f"rm {cls.path}") # nosec
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,27 +1,34 @@
from aiogram import Bot # flake8: noqa
from aiogram.types import Message
from src.modules.standard.config.config import get_telegram_check_bot import asyncio
from src.modules.standard.roles.roles import Roles import random
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
from threading import Thread 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(): async def create_math_task():
first_number = random.randint(1, 100) first_number = random.randint(1, 100) # nosec
second_number = random.randint(1, 100) second_number = random.randint(1, 100) # nosec
answer = first_number + second_number answer = first_number + second_number
fake_answers = [] fake_answers = []
for i in range(3): for i in range(3):
diff = random.randint(1, 10) diff = random.randint(1, 10) # nosec
diff_sign = random.choice(["+", "-"]) diff_sign = random.choice(["+", "-"]) # nosec
fake_answers.append(answer + diff if diff_sign == "+" else answer - diff) fake_answers.append(answer + diff if diff_sign == "+" else answer - diff)
fake_answers.append(answer) fake_answers.append(answer)
random.shuffle(fake_answers) random.shuffle(fake_answers)
return [answer, first_number, second_number, 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): async def ban_user_timer(chat_id: int, user_id: int, time: int, bot: Bot):
await asyncio.sleep(time) await asyncio.sleep(time)
if get_user(user_id) is not None: 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() await ban_user()
async def check_new_user(message: Message, bot: Bot): async def check_new_user(message: Message, bot: Bot):
print("check_new_user") print("check_new_user")
if get_telegram_check_bot(): 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: if get_user(message.from_user.id) is None:
# Выдаём пользователю ограничение на отправку сообщений на 3 минуты # Выдаём пользователю ограничение на отправку сообщений на 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() ban_task.start()
# Создаём задачу с отложенным выполнением на 3 минуты # Создаём задачу с отложенным выполнением на 3 минуты
@ -48,27 +56,31 @@ async def check_new_user(message: Message, bot: Bot):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for answer in math_task[3]: for answer in math_task[3]:
if answer == math_task[0]: if answer == math_task[0]:
builder.add(types.InlineKeyboardButton( builder.add(
text=answer, types.InlineKeyboardButton(
callback_data=f"check_math_task_true") text=answer, callback_data=f"check_math_task_true"
)
) )
else: else:
builder.add(types.InlineKeyboardButton( builder.add(
text=answer, types.InlineKeyboardButton(
callback_data=f"check_math_task_false") text=answer, callback_data=f"check_math_task_false"
)
) )
await message.reply( await message.reply(
f"Приветствую, {message.from_user.first_name}!\n" f"Приветствую, {message.from_user.first_name}!\n"
f"Для продолжения работы с ботом, пожалуйста, решите математический пример в течении 3х минут:\n" f"Для продолжения работы с ботом, пожалуйста, решите математический пример в течении 3х минут:\n"
f"*{text}*", f"*{text}*",
reply_markup=builder.as_markup() reply_markup=builder.as_markup(),
) )
async def math_task_true(message: Message, bot: Bot): async def math_task_true(message: Message, bot: Bot):
await message.reply(f"Верно! Добро пожаловать в чат {message.from_user.first_name}") await message.reply(f"Верно! Добро пожаловать в чат {message.from_user.first_name}")
await unmute_user(message.chat.id, message.from_user.id, bot) await unmute_user(message.chat.id, message.from_user.id, bot)
add_user(message.from_user.id, add_user(
message.from_user.first_name + ' ' + message.from_user.last_name, message.from_user.id,
message.from_user.username) message.from_user.first_name + " " + message.from_user.last_name,
message.from_user.username,
)
pass pass

View File

@ -3,4 +3,4 @@
"description": "Мо", "description": "Мо",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0"
} }

View File

@ -1,4 +1,4 @@
from aiogram import Router, F from aiogram import F, Router
from src.modules.standard.welcome.handlers import check_new_user 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()) router.message.register(check_new_user, F.new_chat_members.exists())
# Ловин колбеки от кнопок с callback_data=f"check_math_task_true" # Ловин колбеки от кнопок с callback_data=f"check_math_task_true"
router.callback_query.register(check_new_user, F.callback_data == "check_math_task_true") router.callback_query.register(
check_new_user, F.callback_data == "check_math_task_true"
)

View File

@ -9,13 +9,14 @@ class Path:
modules_standard: str modules_standard: str
modules_custom: str modules_custom: str
def _get_paths(path_to_json: str): def _get_paths(path_to_json: str):
with open(path_to_json, encoding="utf8") as f: with open(path_to_json, encoding="utf8") as f:
paths = loads(f.read()) paths = loads(f.read())
return Path( return Path(
core=paths["core"], core=paths["core"],
modules_standard=paths["modules standard"], modules_standard=paths["modules standard"],
modules_custom=paths["modules custom"] modules_custom=paths["modules custom"],
) )