mirror of
https://gitflic.ru/project/maks1ms/ocab.git
synced 2025-01-12 01:31:05 +03:00
wip
This commit is contained in:
parent
e8b5f79d99
commit
d52864a231
377
poetry.lock
generated
377
poetry.lock
generated
@ -301,6 +301,28 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
|||||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blinker"
|
||||||
|
version = "1.8.2"
|
||||||
|
description = "Fast, simple object-to-object and broadcast signaling"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
|
||||||
|
{file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachelib"
|
||||||
|
version = "0.9.0"
|
||||||
|
description = "A collection of cache libraries in the same API interface."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "cachelib-0.9.0-py3-none-any.whl", hash = "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3"},
|
||||||
|
{file = "cachelib-0.9.0.tar.gz", hash = "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.7.4"
|
version = "2024.7.4"
|
||||||
@ -479,6 +501,128 @@ pyyaml = ">=5.3.1"
|
|||||||
requests = ">=2.23.0"
|
requests = ">=2.23.0"
|
||||||
rich = "*"
|
rich = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash"
|
||||||
|
version = "2.17.1"
|
||||||
|
description = "A Python framework for building reactive web-apps. Developed by Plotly."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "dash-2.17.1-py3-none-any.whl", hash = "sha256:3eefc9ac67003f93a06bc3e500cae0a6787c48e6c81f6f61514239ae2da414e4"},
|
||||||
|
{file = "dash-2.17.1.tar.gz", hash = "sha256:ee2d9c319de5dcc1314085710b72cd5fa63ff994d913bf72979b7130daeea28e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
dash-core-components = "2.0.0"
|
||||||
|
dash-html-components = "2.0.0"
|
||||||
|
dash-table = "5.0.0"
|
||||||
|
Flask = ">=1.0.4,<3.1"
|
||||||
|
importlib-metadata = "*"
|
||||||
|
nest-asyncio = "*"
|
||||||
|
plotly = ">=5.0.0"
|
||||||
|
requests = "*"
|
||||||
|
retrying = "*"
|
||||||
|
setuptools = "*"
|
||||||
|
typing-extensions = ">=4.1.1"
|
||||||
|
Werkzeug = "<3.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
celery = ["celery[redis] (>=5.1.2)", "redis (>=3.5.3)"]
|
||||||
|
ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.8.1)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.10.3)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"]
|
||||||
|
compress = ["flask-compress"]
|
||||||
|
dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"]
|
||||||
|
diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"]
|
||||||
|
testing = ["beautifulsoup4 (>=4.8.2)", "cryptography", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash-bootstrap-components"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = "Bootstrap themed components for use in Plotly Dash"
|
||||||
|
optional = false
|
||||||
|
python-versions = "<4,>=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "dash_bootstrap_components-1.6.0-py3-none-any.whl", hash = "sha256:97f0f47b38363f18863e1b247462229266ce12e1e171cfb34d3c9898e6e5cd1e"},
|
||||||
|
{file = "dash_bootstrap_components-1.6.0.tar.gz", hash = "sha256:960a1ec9397574792f49a8241024fa3cecde0f5930c971a3fc81f016cbeb1095"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
dash = ">=2.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
pandas = ["numpy", "pandas"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash-core-components"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "Core component suite for Dash"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"},
|
||||||
|
{file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash-extensions"
|
||||||
|
version = "1.0.18"
|
||||||
|
description = "Extensions for Plotly Dash."
|
||||||
|
optional = false
|
||||||
|
python-versions = "<4,>=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "dash_extensions-1.0.18-py3-none-any.whl", hash = "sha256:17f4469670bd70ce12fac1a05baaae119fb65eee7b012af47aff7377d0399eeb"},
|
||||||
|
{file = "dash_extensions-1.0.18.tar.gz", hash = "sha256:a6b6c0952b3af7ae84c418fea4b43cbd0bd4e82f20d91f1573380b8a3d90df0a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
dash = ">=2.17.0"
|
||||||
|
dataclass-wizard = ">=0.22.2,<0.23.0"
|
||||||
|
Flask-Caching = ">=2.1.0,<3.0.0"
|
||||||
|
jsbeautifier = ">=1.14.3,<2.0.0"
|
||||||
|
more-itertools = ">=10.2.0,<11.0.0"
|
||||||
|
pydantic = ">=2.7.1,<3.0.0"
|
||||||
|
ruff = ">=0.4.5,<0.5.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
mantine = ["dash-mantine-components (>=0.14.3,<0.15.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash-html-components"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "Vanilla HTML components for Dash"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"},
|
||||||
|
{file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dash-table"
|
||||||
|
version = "5.0.0"
|
||||||
|
description = "Dash table"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"},
|
||||||
|
{file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dataclass-wizard"
|
||||||
|
version = "0.22.3"
|
||||||
|
description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "dataclass-wizard-0.22.3.tar.gz", hash = "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424"},
|
||||||
|
{file = "dataclass_wizard-0.22.3-py2.py3-none-any.whl", hash = "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["Sphinx (==5.3.0)", "bump2version (==1.0.1)", "coverage (>=6.2)", "dataclass-factory (==2.12)", "dataclasses-json (==0.5.6)", "flake8 (>=3)", "jsons (==1.6.1)", "pip (>=21.3.1)", "pytest (==7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytimeparse (==1.1.8)", "sphinx-issues (==3.0.1)", "sphinx-issues (==4.0.0)", "tox (==3.24.5)", "twine (==3.8.0)", "watchdog[watchmedo] (==2.1.6)", "wheel (==0.37.1)", "wheel (==0.42.0)"]
|
||||||
|
timedelta = ["pytimeparse (>=1.1.7)"]
|
||||||
|
yaml = ["PyYAML (>=5.3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dataclasses-json"
|
name = "dataclasses-json"
|
||||||
version = "0.6.7"
|
version = "0.6.7"
|
||||||
@ -525,6 +669,16 @@ idna = ["idna (>=3.6)"]
|
|||||||
trio = ["trio (>=0.23)"]
|
trio = ["trio (>=0.23)"]
|
||||||
wmi = ["wmi (>=1.5.1)"]
|
wmi = ["wmi (>=1.5.1)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "editorconfig"
|
||||||
|
version = "0.12.4"
|
||||||
|
description = "EditorConfig File Locator and Interpreter for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "email-validator"
|
name = "email-validator"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@ -614,6 +768,43 @@ mccabe = ">=0.7.0,<0.8.0"
|
|||||||
pycodestyle = ">=2.12.0,<2.13.0"
|
pycodestyle = ">=2.12.0,<2.13.0"
|
||||||
pyflakes = ">=3.2.0,<3.3.0"
|
pyflakes = ">=3.2.0,<3.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask"
|
||||||
|
version = "3.0.3"
|
||||||
|
description = "A simple framework for building complex web applications."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
|
||||||
|
{file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
blinker = ">=1.6.2"
|
||||||
|
click = ">=8.1.3"
|
||||||
|
itsdangerous = ">=2.1.2"
|
||||||
|
Jinja2 = ">=3.1.2"
|
||||||
|
Werkzeug = ">=3.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
async = ["asgiref (>=3.2)"]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask-caching"
|
||||||
|
version = "2.3.0"
|
||||||
|
description = "Adds caching support to Flask applications."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "Flask_Caching-2.3.0-py3-none-any.whl", hash = "sha256:51771c75682e5abc1483b78b96d9131d7941dc669b073852edfa319dd4e29b6e"},
|
||||||
|
{file = "flask_caching-2.3.0.tar.gz", hash = "sha256:d7e4ca64a33b49feb339fcdd17e6ba25f5e01168cf885e53790e885f83a4d2cf"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cachelib = ">=0.9.0,<0.10.0"
|
||||||
|
Flask = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flet"
|
name = "flet"
|
||||||
version = "0.23.2"
|
version = "0.23.2"
|
||||||
@ -947,6 +1138,25 @@ files = [
|
|||||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "importlib-metadata"
|
||||||
|
version = "8.0.0"
|
||||||
|
description = "Read metadata from Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
|
||||||
|
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
zipp = ">=0.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
perf = ["ipython"]
|
||||||
|
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isort"
|
name = "isort"
|
||||||
version = "5.13.2"
|
version = "5.13.2"
|
||||||
@ -961,6 +1171,17 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
colors = ["colorama (>=0.4.6)"]
|
colors = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itsdangerous"
|
||||||
|
version = "2.2.0"
|
||||||
|
description = "Safely pass data to untrusted environments and back."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||||
|
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.4"
|
version = "3.1.4"
|
||||||
@ -978,6 +1199,20 @@ MarkupSafe = ">=2.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
i18n = ["Babel (>=2.7)"]
|
i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsbeautifier"
|
||||||
|
version = "1.15.1"
|
||||||
|
description = "JavaScript unobfuscator and beautifier."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
editorconfig = ">=0.12.2"
|
||||||
|
six = ">=1.13.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magic-filter"
|
name = "magic-filter"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@ -1126,6 +1361,17 @@ files = [
|
|||||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "more-itertools"
|
||||||
|
version = "10.3.0"
|
||||||
|
description = "More routines for operating on iterables, beyond itertools"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
|
||||||
|
{file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multidict"
|
name = "multidict"
|
||||||
version = "6.0.5"
|
version = "6.0.5"
|
||||||
@ -1236,6 +1482,17 @@ files = [
|
|||||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nest-asyncio"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = "Patch asyncio to allow nested event loops"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"},
|
||||||
|
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nodeenv"
|
name = "nodeenv"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@ -1322,6 +1579,21 @@ docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-
|
|||||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
||||||
type = ["mypy (>=1.8)"]
|
type = ["mypy (>=1.8)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotly"
|
||||||
|
version = "5.22.0"
|
||||||
|
description = "An open-source, interactive data visualization library for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"},
|
||||||
|
{file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
packaging = "*"
|
||||||
|
tenacity = ">=6.2.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pre-commit"
|
name = "pre-commit"
|
||||||
version = "3.7.1"
|
version = "3.7.1"
|
||||||
@ -1710,6 +1982,20 @@ files = [
|
|||||||
docs = ["Sphinx", "sphinx-rtd-theme"]
|
docs = ["Sphinx", "sphinx-rtd-theme"]
|
||||||
test = ["pytest", "pytest-mock"]
|
test = ["pytest", "pytest-mock"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "retrying"
|
||||||
|
version = "1.3.4"
|
||||||
|
description = "Retrying"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"},
|
||||||
|
{file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "13.7.1"
|
version = "13.7.1"
|
||||||
@ -1728,6 +2014,32 @@ pygments = ">=2.13.0,<3.0.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.4.10"
|
||||||
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"},
|
||||||
|
{file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"},
|
||||||
|
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
@ -1739,6 +2051,22 @@ files = [
|
|||||||
{file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"},
|
{file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "setuptools"
|
||||||
|
version = "71.0.1"
|
||||||
|
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "setuptools-71.0.1-py3-none-any.whl", hash = "sha256:1eb8ef012efae7f6acbc53ec0abde4bc6746c43087fd215ee09e1df48998711f"},
|
||||||
|
{file = "setuptools-71.0.1.tar.gz", hash = "sha256:c51d7fd29843aa18dad362d4b4ecd917022131425438251f4e3d766c964dd1ad"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||||
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (<7.4)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||||
|
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shellingham"
|
name = "shellingham"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
@ -1803,6 +2131,21 @@ files = [
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
|
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tenacity"
|
||||||
|
version = "8.5.0"
|
||||||
|
description = "Retry code until it succeeds"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"},
|
||||||
|
{file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["reno", "sphinx"]
|
||||||
|
test = ["pytest", "tornado (>=4.5)", "typeguard"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-unidecode"
|
name = "text-unidecode"
|
||||||
version = "1.3"
|
version = "1.3"
|
||||||
@ -2186,6 +2529,23 @@ files = [
|
|||||||
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "werkzeug"
|
||||||
|
version = "3.0.3"
|
||||||
|
description = "The comprehensive WSGI web application library."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
|
||||||
|
{file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=2.1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
watchdog = ["watchdog (>=2.3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wsproto"
|
name = "wsproto"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -2303,7 +2663,22 @@ files = [
|
|||||||
idna = ">=2.0"
|
idna = ">=2.0"
|
||||||
multidict = ">=4.0"
|
multidict = ">=4.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zipp"
|
||||||
|
version = "3.19.2"
|
||||||
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
|
||||||
|
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
|
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.11.6,<3.13"
|
python-versions = ">=3.11.6,<3.13"
|
||||||
content-hash = "3dfc8d87dd19fe6222428891e73a1bc61edf045596fdecbc7897ecd1356d8b3d"
|
content-hash = "60d3a08ec1ea70b53fe4e2f3ccc112c30e88a1908acf4af93a24e186f3addea8"
|
||||||
|
@ -38,6 +38,10 @@ semver = "^3.0.2"
|
|||||||
hypercorn = "^0.17.3"
|
hypercorn = "^0.17.3"
|
||||||
flet = "^0.23.2"
|
flet = "^0.23.2"
|
||||||
fastapi = "^0.111.1"
|
fastapi = "^0.111.1"
|
||||||
|
setuptools = "^71.0.1"
|
||||||
|
dash = "^2.17.1"
|
||||||
|
dash-extensions = "^1.0.18"
|
||||||
|
dash-bootstrap-components = "^1.6.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
flake8 = "^7.1.0"
|
flake8 = "^7.1.0"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
|
# import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
@ -8,13 +9,13 @@ def setup_logger():
|
|||||||
"""
|
"""
|
||||||
Настройка логирования
|
Настройка логирования
|
||||||
"""
|
"""
|
||||||
current_date = time.strftime("%d-%m-%Y")
|
# current_date = time.strftime("%d-%m-%Y")
|
||||||
log_dir = os.path.join(os.path.dirname(__file__), "log")
|
log_dir = os.path.join(os.path.dirname(__file__), "log")
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
log_file = os.path.join(log_dir, f"log-{current_date}.log")
|
# log_file = os.path.join(log_dir, f"log-{current_date}.log")
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
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",
|
||||||
|
@ -11,6 +11,8 @@ from ocab_core.logger import log, setup_logger
|
|||||||
from ocab_core.modules_system import ModulesManager
|
from ocab_core.modules_system import ModulesManager
|
||||||
from ocab_core.modules_system.loaders import FSLoader, UnsafeFSLoader
|
from ocab_core.modules_system.loaders import FSLoader, UnsafeFSLoader
|
||||||
from ocab_core.singleton import Singleton
|
from ocab_core.singleton import Singleton
|
||||||
|
|
||||||
|
# TODO: заменить на get_module("standard.config")
|
||||||
from ocab_modules.standard.config.config import get_telegram_token
|
from ocab_modules.standard.config.config import get_telegram_token
|
||||||
|
|
||||||
ocab_modules_path = get_module_directory("ocab_modules")
|
ocab_modules_path = get_module_directory("ocab_modules")
|
||||||
@ -26,16 +28,17 @@ def ocab_modules_loader(namespace: str, module_name: str, safe=True):
|
|||||||
bot_modules = [
|
bot_modules = [
|
||||||
ocab_modules_loader("standard", "config", safe=False),
|
ocab_modules_loader("standard", "config", safe=False),
|
||||||
ocab_modules_loader("standard", "database", safe=False),
|
ocab_modules_loader("standard", "database", safe=False),
|
||||||
ocab_modules_loader("standard", "fsm_database_storage", safe=False),
|
# ocab_modules_loader("standard", "fsm_database_storage", safe=False),
|
||||||
ocab_modules_loader("standard", "roles", safe=False),
|
ocab_modules_loader("standard", "roles", safe=False),
|
||||||
ocab_modules_loader("external", "yandexgpt", safe=False),
|
ocab_modules_loader("external", "yandexgpt", safe=False),
|
||||||
ocab_modules_loader("standard", "miniapp", safe=False),
|
#
|
||||||
ocab_modules_loader("standard", "command_helper"),
|
ocab_modules_loader("standard", "command_helper"),
|
||||||
ocab_modules_loader("standard", "info"),
|
# ocab_modules_loader("standard", "info"),
|
||||||
ocab_modules_loader("standard", "filters"),
|
# ocab_modules_loader("standard", "filters"),
|
||||||
ocab_modules_loader("external", "create_report_apps"),
|
# ocab_modules_loader("external", "create_report_apps"),
|
||||||
ocab_modules_loader("standard", "admin"),
|
# ocab_modules_loader("standard", "admin"),
|
||||||
ocab_modules_loader("standard", "message_processing"),
|
ocab_modules_loader("standard", "message_processing"),
|
||||||
|
ocab_modules_loader("standard", "miniapp", safe=False),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +51,9 @@ async def long_polling_mode():
|
|||||||
async def webhook_mode():
|
async def webhook_mode():
|
||||||
singleton = Singleton()
|
singleton = Singleton()
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.mount("/webapp", singleton.storage["webapp"])
|
||||||
|
|
||||||
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
||||||
await singleton.bot.set_webhook(
|
await singleton.bot.set_webhook(
|
||||||
"https://mackerel-pumped-foal.ngrok-free.app/webhook"
|
"https://mackerel-pumped-foal.ngrok-free.app/webhook"
|
||||||
@ -71,7 +77,6 @@ async def init_app():
|
|||||||
await singleton.modules_manager.load(module_loader)
|
await singleton.modules_manager.load(module_loader)
|
||||||
|
|
||||||
singleton.dp = Dispatcher(storage=singleton.storage["_fsm_storage"])
|
singleton.dp = Dispatcher(storage=singleton.storage["_fsm_storage"])
|
||||||
|
|
||||||
singleton.dp.include_routers(*singleton.storage["_routers"])
|
singleton.dp.include_routers(*singleton.storage["_routers"])
|
||||||
|
|
||||||
for middleware in singleton.storage["_outer_message_middlewares"]:
|
for middleware in singleton.storage["_outer_message_middlewares"]:
|
||||||
|
@ -1,9 +1,27 @@
|
|||||||
import types
|
import types
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List, Optional, Union
|
||||||
|
|
||||||
from dataclasses_json import dataclass_json
|
from dataclasses_json import dataclass_json
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass_json
|
||||||
|
@dataclass
|
||||||
|
class DependencyInfo:
|
||||||
|
version: str
|
||||||
|
uses: Optional[List[str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
DependencyType = Union[str, DependencyInfo]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass_json
|
||||||
|
@dataclass
|
||||||
|
class Dependencies:
|
||||||
|
required: Optional[Dict[str, DependencyType]] = None
|
||||||
|
optional: Optional[Dict[str, DependencyType]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass_json
|
@dataclass_json
|
||||||
@dataclass
|
@dataclass
|
||||||
class ModuleInfo:
|
class ModuleInfo:
|
||||||
@ -11,9 +29,10 @@ class ModuleInfo:
|
|||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
version: str
|
version: str
|
||||||
author: str | list[str]
|
author: Union[str, List[str]]
|
||||||
privileged: bool
|
privileged: bool
|
||||||
dependencies: dict
|
dependencies: Dependencies
|
||||||
|
pythonDependencies: Optional[Dependencies] = None
|
||||||
|
|
||||||
|
|
||||||
class AbstractLoader:
|
class AbstractLoader:
|
||||||
|
@ -3,6 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from RestrictedPython import compile_restricted_exec
|
from RestrictedPython import compile_restricted_exec
|
||||||
|
|
||||||
|
# from ocab_core.logger import log
|
||||||
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
|
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
|
||||||
from ocab_core.modules_system.safe.policy import (
|
from ocab_core.modules_system.safe.policy import (
|
||||||
ALLOWED_IMPORTS,
|
ALLOWED_IMPORTS,
|
||||||
@ -16,13 +17,30 @@ class FSLoader(UnsafeFSLoader):
|
|||||||
super().__init__(path)
|
super().__init__(path)
|
||||||
self.builtins = BUILTINS.copy()
|
self.builtins = BUILTINS.copy()
|
||||||
self.builtins["__import__"] = self._hook_import
|
self.builtins["__import__"] = self._hook_import
|
||||||
|
self.module_info = self.info()
|
||||||
|
self.allowed_python_dependencies = self._get_allowed_python_dependencies()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
info = self.info()
|
if self.module_info.privileged:
|
||||||
if info.privileged:
|
|
||||||
raise Exception("Only non privileged modules are allowed to be imported")
|
raise Exception("Only non privileged modules are allowed to be imported")
|
||||||
|
self.module_id = self.module_info.id
|
||||||
|
|
||||||
return self._hook_import(".")
|
return self._hook_import(".")
|
||||||
|
|
||||||
|
def _get_allowed_python_dependencies(self):
|
||||||
|
allowed = {}
|
||||||
|
|
||||||
|
if self.module_info.pythonDependencies:
|
||||||
|
if self.module_info.pythonDependencies.required:
|
||||||
|
allowed.update(self.module_info.pythonDependencies.required)
|
||||||
|
if self.module_info.pythonDependencies.optional:
|
||||||
|
allowed.update(self.module_info.pythonDependencies.optional)
|
||||||
|
|
||||||
|
for allowed_module in ALLOWED_IMPORTS:
|
||||||
|
allowed[allowed_module] = "*"
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
|
||||||
def _resolve_module_from_path(self, module_name: str):
|
def _resolve_module_from_path(self, module_name: str):
|
||||||
path = Path(self.path)
|
path = Path(self.path)
|
||||||
|
|
||||||
@ -44,12 +62,14 @@ class FSLoader(UnsafeFSLoader):
|
|||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def _hook_import(self, name: str, *args, **kwargs):
|
def _hook_import(self, name: str, *args, **kwargs):
|
||||||
for allowed in ALLOWED_IMPORTS:
|
|
||||||
if name == allowed or name.startswith(f"{allowed}."):
|
|
||||||
return __import__(name, *args, **kwargs)
|
|
||||||
|
|
||||||
if name == "ocab_core.modules_system.public_api":
|
if name == "ocab_core.modules_system.public_api":
|
||||||
return __import__(name, *args, **kwargs)
|
module = __import__(name, *args, **kwargs)
|
||||||
|
module.__ocab_module_id__ = self.module_id
|
||||||
|
return module
|
||||||
|
|
||||||
|
for key in self.allowed_python_dependencies.keys():
|
||||||
|
if name == key or name.startswith(f"{key}."):
|
||||||
|
return __import__(name, *args, **kwargs)
|
||||||
|
|
||||||
module_file_path = self._resolve_module_from_path(name)
|
module_file_path = self._resolve_module_from_path(name)
|
||||||
|
|
||||||
@ -58,9 +78,7 @@ class FSLoader(UnsafeFSLoader):
|
|||||||
|
|
||||||
module = types.ModuleType(name)
|
module = types.ModuleType(name)
|
||||||
module.__dict__.update(
|
module.__dict__.update(
|
||||||
{
|
{"__builtins__": self.builtins, "__ocab_module_id__": self.module_id}
|
||||||
"__builtins__": self.builtins,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
result = compile_restricted_exec(src, "<string>", policy=RestrictedPythonPolicy)
|
result = compile_restricted_exec(src, "<string>", policy=RestrictedPythonPolicy)
|
||||||
|
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
import semver
|
import semver
|
||||||
|
|
||||||
from ocab_core.modules_system.loaders.base import AbstractLoader
|
from ocab_core.modules_system.loaders.base import (
|
||||||
|
AbstractLoader,
|
||||||
|
DependencyInfo,
|
||||||
|
ModuleInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_version_compatible(version, requirement):
|
def is_version_compatible(version, requirement):
|
||||||
@ -23,6 +28,46 @@ def is_version_compatible(version, requirement):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_python_dependencies(info: ModuleInfo):
|
||||||
|
if info.pythonDependencies and info.pythonDependencies.required:
|
||||||
|
for dependency, req in info.pythonDependencies.required.items():
|
||||||
|
try:
|
||||||
|
installed_version = pkg_resources.get_distribution(dependency).version
|
||||||
|
except pkg_resources.DistributionNotFound:
|
||||||
|
raise Exception(
|
||||||
|
f"Module {info.id} requires {dependency},"
|
||||||
|
f"but it is not installed"
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(req, str):
|
||||||
|
required_version = req
|
||||||
|
elif isinstance(req, DependencyInfo):
|
||||||
|
required_version = req.version
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid dependency specification for {dependency}")
|
||||||
|
|
||||||
|
if not is_version_compatible(installed_version, required_version):
|
||||||
|
raise Exception(
|
||||||
|
f"Module {info.id} depends on {dependency} {required_version}, "
|
||||||
|
f"but version {installed_version} is installed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_dependency_uses(
|
||||||
|
loaded_dependency, required_uses, dependent_module_id, dependency_id
|
||||||
|
):
|
||||||
|
module = loaded_dependency.get("module")
|
||||||
|
if not module:
|
||||||
|
raise Exception(f"Module object not found for dependency {dependency_id}")
|
||||||
|
|
||||||
|
for required_attr in required_uses:
|
||||||
|
if not hasattr(module, required_attr):
|
||||||
|
raise Exception(
|
||||||
|
f"Module {dependent_module_id} requires '{required_attr}' "
|
||||||
|
f"from {dependency_id}, but it is not available"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def await_if_async(module, method_name):
|
async def await_if_async(module, method_name):
|
||||||
if hasattr(module, method_name):
|
if hasattr(module, method_name):
|
||||||
method = getattr(module, method_name)
|
method = getattr(module, method_name)
|
||||||
@ -43,33 +88,54 @@ class ModulesManager:
|
|||||||
if any(mod["info"].id == info.id for mod in self.modules):
|
if any(mod["info"].id == info.id for mod in self.modules):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check dependencies
|
self.check_module_dependencies(info)
|
||||||
for dependency, version in info.dependencies.items():
|
check_python_dependencies(info)
|
||||||
loaded_dependency = next(
|
|
||||||
(mod for mod in self.modules if mod["info"].id == dependency), None
|
|
||||||
)
|
|
||||||
if not loaded_dependency:
|
|
||||||
raise Exception(
|
|
||||||
f"Module {info.id} depends on {dependency}, but it is not loaded"
|
|
||||||
)
|
|
||||||
loaded_dependency_info = loaded_dependency["info"]
|
|
||||||
if not is_version_compatible(loaded_dependency_info.version, version):
|
|
||||||
raise Exception(
|
|
||||||
f"Module {info.id} depends on {dependency}, "
|
|
||||||
f"but version {version} is not compatible"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
module_info = {
|
||||||
|
"info": info,
|
||||||
|
"module": None,
|
||||||
|
}
|
||||||
|
self.modules.append(module_info)
|
||||||
module = loader.load()
|
module = loader.load()
|
||||||
|
module_info["module"] = module
|
||||||
self.modules.append(
|
|
||||||
{
|
|
||||||
"info": info,
|
|
||||||
"module": module,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
await await_if_async(module, "module_init")
|
await await_if_async(module, "module_init")
|
||||||
|
|
||||||
|
def check_module_dependencies(self, info: ModuleInfo):
|
||||||
|
if info.dependencies.required:
|
||||||
|
for dependency, req in info.dependencies.required.items():
|
||||||
|
loaded_dependency = next(
|
||||||
|
(mod for mod in self.modules if mod["info"].id == dependency), None
|
||||||
|
)
|
||||||
|
if not loaded_dependency:
|
||||||
|
raise Exception(
|
||||||
|
f"Module {info.id} depends on {dependency},"
|
||||||
|
f"but it is not loaded"
|
||||||
|
)
|
||||||
|
|
||||||
|
loaded_dependency_info = loaded_dependency["info"]
|
||||||
|
|
||||||
|
if isinstance(req, str):
|
||||||
|
required_version = req
|
||||||
|
elif isinstance(req, DependencyInfo):
|
||||||
|
required_version = req.version
|
||||||
|
if req.uses:
|
||||||
|
check_dependency_uses(
|
||||||
|
loaded_dependency, req.uses, info.id, dependency
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid dependency specification for {dependency}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_version_compatible(
|
||||||
|
loaded_dependency_info.version, required_version
|
||||||
|
):
|
||||||
|
raise Exception(
|
||||||
|
f"Module {info.id} depends on {dependency} {required_version}, "
|
||||||
|
f"but version {loaded_dependency_info.version} is loaded"
|
||||||
|
)
|
||||||
|
|
||||||
async def late_init(self):
|
async def late_init(self):
|
||||||
for m in self.modules:
|
for m in self.modules:
|
||||||
module = m["module"]
|
module = m["module"]
|
||||||
|
@ -6,6 +6,7 @@ from .public_api import (
|
|||||||
get_module,
|
get_module,
|
||||||
register_outer_message_middleware,
|
register_outer_message_middleware,
|
||||||
register_router,
|
register_router,
|
||||||
|
set_chat_menu_button,
|
||||||
set_my_commands,
|
set_my_commands,
|
||||||
)
|
)
|
||||||
from .utils import Utils
|
from .utils import Utils
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import inspect
|
||||||
import types
|
import types
|
||||||
from typing import Any, Tuple, Union
|
from typing import Any, Tuple, Union
|
||||||
|
|
||||||
@ -5,9 +6,16 @@ from aiogram import BaseMiddleware, Router
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.storage.base import StorageKey
|
from aiogram.fsm.storage.base import StorageKey
|
||||||
|
|
||||||
|
# from ocab_core.logger import log
|
||||||
|
from ocab_core.modules_system.loaders.base import DependencyInfo
|
||||||
from ocab_core.singleton import Singleton
|
from ocab_core.singleton import Singleton
|
||||||
|
|
||||||
|
|
||||||
|
async def set_chat_menu_button(menu_button):
|
||||||
|
app = Singleton()
|
||||||
|
await app.bot.set_chat_menu_button(menu_button=menu_button)
|
||||||
|
|
||||||
|
|
||||||
def register_router(router: Router):
|
def register_router(router: Router):
|
||||||
app = Singleton()
|
app = Singleton()
|
||||||
app.storage["_routers"].append(router)
|
app.storage["_routers"].append(router)
|
||||||
@ -45,10 +53,41 @@ def set_fsm(storage):
|
|||||||
def get_module(
|
def get_module(
|
||||||
module_id: str, paths=None
|
module_id: str, paths=None
|
||||||
) -> Union[types.ModuleType, Union[Any, None], Tuple[Union[Any, None], ...]]:
|
) -> Union[types.ModuleType, Union[Any, None], Tuple[Union[Any, None], ...]]:
|
||||||
|
|
||||||
|
caller_globals = inspect.currentframe().f_back.f_globals
|
||||||
app = Singleton()
|
app = Singleton()
|
||||||
|
|
||||||
|
allowed_uses = None
|
||||||
|
|
||||||
|
if "__ocab_module_id__" in caller_globals:
|
||||||
|
caller_module_id = caller_globals["__ocab_module_id__"]
|
||||||
|
caller_module_info = app.modules_manager.get_info_by_id(caller_module_id)
|
||||||
|
|
||||||
|
if caller_module_info and caller_module_info.dependencies:
|
||||||
|
dependency = None
|
||||||
|
if caller_module_info.dependencies.required:
|
||||||
|
dependency = caller_module_info.dependencies.required.get(module_id)
|
||||||
|
if not dependency and caller_module_info.dependencies.optional:
|
||||||
|
dependency = caller_module_info.dependencies.optional.get(module_id)
|
||||||
|
|
||||||
|
if (
|
||||||
|
dependency
|
||||||
|
and isinstance(dependency, DependencyInfo)
|
||||||
|
and dependency.uses
|
||||||
|
):
|
||||||
|
allowed_uses = set(dependency.uses)
|
||||||
|
|
||||||
module = app.modules_manager.get_by_id(module_id)
|
module = app.modules_manager.get_by_id(module_id)
|
||||||
|
|
||||||
|
if not module:
|
||||||
|
raise ModuleNotFoundError(f"Module {module_id} not found")
|
||||||
|
|
||||||
if paths is None:
|
if paths is None:
|
||||||
|
if allowed_uses is not None:
|
||||||
|
raise PermissionError(
|
||||||
|
f"Direct access to module {module_id} is "
|
||||||
|
f"not allowed for {caller_module_id}. Specify allowed attributes."
|
||||||
|
)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
if isinstance(paths, str):
|
if isinstance(paths, str):
|
||||||
@ -61,9 +100,16 @@ def get_module(
|
|||||||
try:
|
try:
|
||||||
parts = path.split(".")
|
parts = path.split(".")
|
||||||
for part in parts:
|
for part in parts:
|
||||||
|
if allowed_uses is not None and part not in allowed_uses:
|
||||||
|
raise AttributeError(
|
||||||
|
f"Access to '{part}' is not allowed "
|
||||||
|
+ f"for module {caller_module_id}"
|
||||||
|
)
|
||||||
current_obj = getattr(current_obj, part)
|
current_obj = getattr(current_obj, part)
|
||||||
results.append(current_obj)
|
results.append(current_obj)
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
|
if "is not allowed" in str(e):
|
||||||
|
raise PermissionError(str(e))
|
||||||
results.append(None)
|
results.append(None)
|
||||||
|
|
||||||
if len(results) == 1:
|
if len(results) == 1:
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import types
|
||||||
from _ast import AnnAssign
|
from _ast import AnnAssign
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import flet as ft
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from RestrictedPython import (
|
from RestrictedPython import (
|
||||||
RestrictingNodeTransformer,
|
RestrictingNodeTransformer,
|
||||||
@ -9,8 +11,8 @@ from RestrictedPython import (
|
|||||||
utility_builtins,
|
utility_builtins,
|
||||||
)
|
)
|
||||||
from RestrictedPython.Eval import default_guarded_getitem, default_guarded_getiter
|
from RestrictedPython.Eval import default_guarded_getitem, default_guarded_getiter
|
||||||
from RestrictedPython.Guards import (
|
from RestrictedPython.Guards import ( # guarded_setattr,; full_write_guard,
|
||||||
full_write_guard,
|
_write_wrapper,
|
||||||
guarded_unpack_sequence,
|
guarded_unpack_sequence,
|
||||||
safer_getattr,
|
safer_getattr,
|
||||||
)
|
)
|
||||||
@ -95,6 +97,39 @@ def safes_getattr(object, name, default=None, getattr=safer_getattr):
|
|||||||
return getattr(object, name, default)
|
return getattr(object, name, default)
|
||||||
|
|
||||||
|
|
||||||
|
trusted_settters_classes = [ft.Page, ft.View]
|
||||||
|
|
||||||
|
|
||||||
|
def safes_setattr(self, key, value):
|
||||||
|
if (
|
||||||
|
isinstance(getattr(type(self), key, None), property)
|
||||||
|
and getattr(type(self), key).fset is not None
|
||||||
|
):
|
||||||
|
getattr(type(self), key).fset(self, value)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def write_guard():
|
||||||
|
# ed scope abuse!
|
||||||
|
# safetypes and Wrapper variables are used by guard()
|
||||||
|
safetypes = {dict, list}
|
||||||
|
Wrapper = _write_wrapper()
|
||||||
|
|
||||||
|
def guard(ob):
|
||||||
|
# Don't bother wrapping simple types, or objects that claim to
|
||||||
|
# handle their own write security.
|
||||||
|
if type(ob) in safetypes or hasattr(ob, "_guarded_writes"):
|
||||||
|
return ob
|
||||||
|
|
||||||
|
if type(ob) in trusted_settters_classes:
|
||||||
|
setattr(ob, "__guarded_setattr__", types.MethodType(safes_setattr, ob))
|
||||||
|
|
||||||
|
# Hand the object to the Wrapper instance, then return the instance.
|
||||||
|
return Wrapper(ob)
|
||||||
|
|
||||||
|
return guard
|
||||||
|
|
||||||
|
|
||||||
BUILTINS = safe_builtins.copy()
|
BUILTINS = safe_builtins.copy()
|
||||||
BUILTINS.update(utility_builtins)
|
BUILTINS.update(utility_builtins)
|
||||||
BUILTINS.update(limited_builtins)
|
BUILTINS.update(limited_builtins)
|
||||||
@ -103,7 +138,7 @@ BUILTINS["__metaclass__"] = _metaclass
|
|||||||
BUILTINS["_getitem_"] = default_guarded_getitem
|
BUILTINS["_getitem_"] = default_guarded_getitem
|
||||||
BUILTINS["_getattr_"] = safes_getattr
|
BUILTINS["_getattr_"] = safes_getattr
|
||||||
BUILTINS["_getiter_"] = default_guarded_getiter
|
BUILTINS["_getiter_"] = default_guarded_getiter
|
||||||
BUILTINS["_write_"] = full_write_guard
|
BUILTINS["_write_"] = write_guard()
|
||||||
BUILTINS["_unpack_sequence_"] = guarded_unpack_sequence
|
BUILTINS["_unpack_sequence_"] = guarded_unpack_sequence
|
||||||
BUILTINS["staticmethod"] = staticmethod
|
BUILTINS["staticmethod"] = staticmethod
|
||||||
BUILTINS["tuple"] = tuple
|
BUILTINS["tuple"] = tuple
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.filters": "^1.0.0"
|
"required": {
|
||||||
|
"standard.filters": "^1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
{
|
{
|
||||||
"id": "standard.command_helper",
|
"id": "standard.command_helper",
|
||||||
"name": "Command helper",
|
"name": "Command helper",
|
||||||
"description": "Модуль для отображения команд при вводе '/'",
|
"description": "Модуль для отображения команд при вводе '/'",
|
||||||
"author": "OCAB Team",
|
"author": "OCAB Team",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.roles": "^1.0.0",
|
"required": {
|
||||||
"standard.database": "^1.0.0"
|
"standard.roles": "^1.0.0",
|
||||||
|
"standard.database": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,4 @@ from .config import (
|
|||||||
get_yandexgpt_in_words,
|
get_yandexgpt_in_words,
|
||||||
get_yandexgpt_start_words,
|
get_yandexgpt_start_words,
|
||||||
)
|
)
|
||||||
|
from .main import module_late_init
|
||||||
|
@ -1,22 +1,67 @@
|
|||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
import yaml
|
from .config_manager import ConfigManager
|
||||||
|
|
||||||
from src.service import paths
|
config = ConfigManager(
|
||||||
|
config_path="/home/maxim/dev/alt-gnome-infrastructure/ocab/src/ocab_core/config.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_config(is_test: bool = False) -> dict:
|
def register_settings(settings_manager: ConfigManager):
|
||||||
if is_test:
|
# TELEGRAM settings
|
||||||
path = f"{paths.modules_standard}/config/tests"
|
settings_manager.register_setting(
|
||||||
else:
|
["TELEGRAM", "TOKEN"], "", "string", is_private=True
|
||||||
path = paths.core
|
)
|
||||||
path = f"{path}/config.yaml"
|
settings_manager.register_setting(
|
||||||
|
["TELEGRAM", "APPROVED_CHAT_ID"],
|
||||||
|
"-123456789 | -012345678",
|
||||||
|
"string",
|
||||||
|
pretty_name="ID разрешенных чатов",
|
||||||
|
description='Чаты, в которых будет работать бот. "|" - разделитель',
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["TELEGRAM", "ADMINCHATID"],
|
||||||
|
-12345678,
|
||||||
|
"number",
|
||||||
|
pretty_name="ID чата администраторов",
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["TELEGRAM", "DEFAULT_CHAT_TAG"],
|
||||||
|
"@alt_gnome_chat",
|
||||||
|
"string",
|
||||||
|
pretty_name="Основной чат",
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox")
|
||||||
|
|
||||||
with open(path, "r") as file:
|
# YANDEXGPT settings
|
||||||
return yaml.full_load(file)
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "TOKEN"], "", "string", is_private=True
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "TOKEN_FOR_REQUEST"], 8000, "number"
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(["YANDEXGPT", "TOKEN_FOR_ANSWER"], 2000, "number")
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "CATALOGID"], "", "string", is_private=True
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "PROMPT"], "Ты чат-бот ...", "string"
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "STARTWORD"], "Бот| Бот, | бот | бот,", "string"
|
||||||
|
)
|
||||||
|
settings_manager.register_setting(
|
||||||
|
["YANDEXGPT", "INWORD"], "помогите | не работает", "string"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ROLES settings
|
||||||
|
settings_manager.register_setting(["ROLES", "ADMIN"], 2, "number")
|
||||||
|
settings_manager.register_setting(["ROLES", "MODERATOR"], 1, "number")
|
||||||
|
settings_manager.register_setting(["ROLES", "USER"], 0, "number")
|
||||||
|
settings_manager.register_setting(["ROLES", "BOT"], 3, "number")
|
||||||
|
|
||||||
|
|
||||||
config = get_config()
|
register_settings(config)
|
||||||
|
|
||||||
|
|
||||||
def get_telegram_token() -> str:
|
def get_telegram_token() -> str:
|
||||||
@ -76,4 +121,4 @@ def get_yandexgpt_token_for_answer() -> int:
|
|||||||
|
|
||||||
|
|
||||||
def get_access_rights() -> dict:
|
def get_access_rights() -> dict:
|
||||||
return get_config()["ACCESS_RIGHTS"]
|
return config["ACCESS_RIGHTS"]
|
||||||
|
259
src/ocab_modules/standard/config/config_manager.py
Normal file
259
src/ocab_modules/standard/config/config_manager.py
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash_extensions.enrich import Input, Output, dcc, html
|
||||||
|
|
||||||
|
DASH_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
DASH_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager:
|
||||||
|
def __init__(self, config_path: str):
|
||||||
|
self._config_path = config_path
|
||||||
|
self._config = self.load_config()
|
||||||
|
self._registered_settings = dict()
|
||||||
|
self._registered_settings_meta = dict()
|
||||||
|
self._update_callbacks = []
|
||||||
|
self._update_required = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self):
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
@config.setter
|
||||||
|
def config(self, value):
|
||||||
|
self._config = value
|
||||||
|
|
||||||
|
def load_config(self) -> Dict[str, Any]:
|
||||||
|
with open(self._config_path, "r") as file:
|
||||||
|
return yaml.safe_load(file)
|
||||||
|
|
||||||
|
def save_config(self):
|
||||||
|
with open(self._config_path, "w") as file:
|
||||||
|
yaml.dump(self._config, file)
|
||||||
|
|
||||||
|
def register_setting(
|
||||||
|
self,
|
||||||
|
key: Union[str, List[str]],
|
||||||
|
default_value: Any,
|
||||||
|
setting_type: str,
|
||||||
|
is_private: bool = False,
|
||||||
|
pretty_name: str = None,
|
||||||
|
description: str = None,
|
||||||
|
options: Optional[List[str]] = None,
|
||||||
|
):
|
||||||
|
if isinstance(key, str):
|
||||||
|
key = [key]
|
||||||
|
|
||||||
|
current = self._registered_settings
|
||||||
|
for k in key[:-1]:
|
||||||
|
if k not in current:
|
||||||
|
current[k] = {}
|
||||||
|
current = current[k]
|
||||||
|
|
||||||
|
current[key[-1]] = self.get_nested_setting(self._config, key, default_value)
|
||||||
|
|
||||||
|
self.set_nested_setting(
|
||||||
|
self._registered_settings_meta,
|
||||||
|
key,
|
||||||
|
{
|
||||||
|
"type": setting_type,
|
||||||
|
"is_private": is_private,
|
||||||
|
"options": options,
|
||||||
|
"pretty_name": pretty_name,
|
||||||
|
"description": description,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_nested_setting(
|
||||||
|
self, config: dict, keys: List[str], default: Any = None
|
||||||
|
) -> Any:
|
||||||
|
current = config
|
||||||
|
for key in keys:
|
||||||
|
if key in current:
|
||||||
|
current = current[key]
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
return current
|
||||||
|
|
||||||
|
def set_nested_setting(self, config: dict, keys: List[str], value: Any):
|
||||||
|
current = config
|
||||||
|
for key in keys[:-1]:
|
||||||
|
if key not in current:
|
||||||
|
current[key] = {}
|
||||||
|
current = current[key]
|
||||||
|
current[keys[-1]] = value
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self._registered_settings:
|
||||||
|
return self._registered_settings[key]
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def update_setting(self, key: Union[str, List[str]], value: Any):
|
||||||
|
if isinstance(key, str):
|
||||||
|
key = [key]
|
||||||
|
self.set_nested_setting(self._registered_settings, key, value)
|
||||||
|
self.set_nested_setting(self._config, key, value)
|
||||||
|
self.save_config()
|
||||||
|
|
||||||
|
def get_settings_layout(self):
|
||||||
|
from dash_extensions.enrich import DashBlueprint
|
||||||
|
|
||||||
|
bp = DashBlueprint()
|
||||||
|
|
||||||
|
def create_layout():
|
||||||
|
def create_nested_layout(settings: dict, key_list=None):
|
||||||
|
if key_list is None:
|
||||||
|
key_list = []
|
||||||
|
|
||||||
|
components = []
|
||||||
|
|
||||||
|
for key, value in settings.items():
|
||||||
|
current_key_list = key_list.copy()
|
||||||
|
current_key_list.append(key)
|
||||||
|
|
||||||
|
if isinstance(value, dict):
|
||||||
|
nested = create_nested_layout(value, current_key_list)
|
||||||
|
if len(nested) > 0:
|
||||||
|
components.append(
|
||||||
|
dbc.Card(
|
||||||
|
[
|
||||||
|
dbc.CardHeader(html.H3(key, className="mb-0")),
|
||||||
|
dbc.CardBody(nested),
|
||||||
|
],
|
||||||
|
className="mb-3",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
meta = self.get_nested_setting(
|
||||||
|
self._registered_settings_meta, current_key_list
|
||||||
|
)
|
||||||
|
|
||||||
|
if not meta.get("is_private"):
|
||||||
|
row = []
|
||||||
|
label_text = meta.get("pretty_name", key)
|
||||||
|
|
||||||
|
if meta.get("type") != "checkbox":
|
||||||
|
row.append(dbc.Label(label_text))
|
||||||
|
|
||||||
|
component_id = {
|
||||||
|
"type": "setting",
|
||||||
|
"key": "-".join(current_key_list),
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.get("type") == "string":
|
||||||
|
component = dbc.Input(
|
||||||
|
id=component_id, type="text", value=value
|
||||||
|
)
|
||||||
|
elif meta.get("type") == "number":
|
||||||
|
component = dbc.Input(
|
||||||
|
id=component_id, type="number", value=value
|
||||||
|
)
|
||||||
|
elif meta.get("type") == "checkbox":
|
||||||
|
component = dbc.Col(
|
||||||
|
[
|
||||||
|
dbc.Checkbox(
|
||||||
|
id=component_id,
|
||||||
|
value=value,
|
||||||
|
label=dbc.Label(
|
||||||
|
label_text,
|
||||||
|
style={"margin-right": "10px"},
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif meta.get("type") == "select":
|
||||||
|
options = [
|
||||||
|
{"label": opt, "value": opt}
|
||||||
|
for opt in meta.get("options", [])
|
||||||
|
]
|
||||||
|
component = dcc.Dropdown(
|
||||||
|
id=component_id, options=options, value=value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
row.append(component)
|
||||||
|
|
||||||
|
if meta.get("description"):
|
||||||
|
row.append(dbc.FormText(meta.get("description")))
|
||||||
|
|
||||||
|
components.append(dbc.Row(row, className="mb-3"))
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
settings_components = create_nested_layout(self._registered_settings)
|
||||||
|
|
||||||
|
layout = html.Div(
|
||||||
|
[
|
||||||
|
html.H1("Настройки"),
|
||||||
|
dbc.Form(settings_components),
|
||||||
|
dbc.Button(
|
||||||
|
"Сохранить",
|
||||||
|
id="save-settings",
|
||||||
|
color="primary",
|
||||||
|
className="mt-3",
|
||||||
|
n_clicks=0,
|
||||||
|
),
|
||||||
|
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
||||||
|
html.Span(
|
||||||
|
id="save-confirmation", style={"verticalAlign": "middle"}
|
||||||
|
),
|
||||||
|
dcc.Store(id="settings-store"),
|
||||||
|
],
|
||||||
|
style={
|
||||||
|
"padding": "20px",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return layout
|
||||||
|
|
||||||
|
bp.layout = create_layout
|
||||||
|
self.setup_callbacks(bp)
|
||||||
|
|
||||||
|
return bp
|
||||||
|
|
||||||
|
def setup_callbacks(self, app):
|
||||||
|
# ws = WebSocket(app, url="/ws")
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output("save-confirmation", "children"),
|
||||||
|
# Output("settings-store", "data"),
|
||||||
|
Input("save-settings", "n_clicks"),
|
||||||
|
# State({"type": "setting", "key": ALL}, "value"),
|
||||||
|
# State({"type": "setting", "key": ALL}, "id"),
|
||||||
|
running=[(Output("save-settings", "disabled"), True, False)],
|
||||||
|
)
|
||||||
|
def save_settings(n_clicks, values, ids):
|
||||||
|
if n_clicks > 0:
|
||||||
|
# updated_settings = {}
|
||||||
|
# print(ids)
|
||||||
|
# for value, id_dict in zip(values, ids):
|
||||||
|
# key = id_dict["key"]
|
||||||
|
# self.update_setting(key.split("-"), value)
|
||||||
|
# updated_settings[key] = value
|
||||||
|
|
||||||
|
return "Настройки сохранены!" # , json.dumps(updated_settings)
|
||||||
|
return "" # , None
|
||||||
|
|
||||||
|
# @app.callback(
|
||||||
|
# Output({"type": "setting", "key": ALL}, "value"),
|
||||||
|
# Input("settings-store", "data"),
|
||||||
|
# )
|
||||||
|
# def update_settings_from_store(data):
|
||||||
|
# if data:
|
||||||
|
# updated_settings = json.loads(data)
|
||||||
|
# print(
|
||||||
|
# [current_value for key, current_value in updated_settings.items()]
|
||||||
|
# )
|
||||||
|
# return [
|
||||||
|
# current_value for key, current_value in updated_settings.items()
|
||||||
|
# ]
|
||||||
|
# raise dash.exceptions.PreventUpdate()
|
@ -5,5 +5,14 @@
|
|||||||
"author": "OCAB Team",
|
"author": "OCAB Team",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": true,
|
"privileged": true,
|
||||||
"dependencies": {}
|
"dependencies": {
|
||||||
|
"optional": {
|
||||||
|
"standard.miniapp": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pythonDependencies": {
|
||||||
|
"optional": {
|
||||||
|
"flet": "^0.23.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
35
src/ocab_modules/standard/config/main.py
Normal file
35
src/ocab_modules/standard/config/main.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
|
from .config import config
|
||||||
|
|
||||||
|
|
||||||
|
def register_settings_page():
|
||||||
|
try:
|
||||||
|
register_page = get_module("standard.miniapp", "register_page")
|
||||||
|
#
|
||||||
|
# def setup_callbacks_wrapper(config_manager):
|
||||||
|
# def setup(app):
|
||||||
|
# config_manager.setup_callbacks(app)
|
||||||
|
#
|
||||||
|
# return setup
|
||||||
|
#
|
||||||
|
# register_page(
|
||||||
|
# name="Настройки",
|
||||||
|
# path="/settings",
|
||||||
|
# layout=config.get_settings_layout(),
|
||||||
|
# setup_callbacks=setup_callbacks_wrapper(config),
|
||||||
|
# )
|
||||||
|
register_page(
|
||||||
|
name="Настройки", path="/settings", blueprint=config.get_settings_layout()
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log(str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def module_late_init():
|
||||||
|
register_settings_page()
|
||||||
|
|
||||||
|
pass
|
@ -6,7 +6,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.roles": "^1.0.0",
|
"required": {
|
||||||
"standard.config": "^1.0.0"
|
"standard.roles": "^1.0.0",
|
||||||
|
"standard.config": "^1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"id": "standard.fsm_database_storage",
|
"id": "standard.fsm_database_storage",
|
||||||
"name": "FSM Database Storage",
|
"name": "FSM Database Storage",
|
||||||
"description": "Очень полезный модуль",
|
"description": "Очень полезный модуль",
|
||||||
"author": "OCAB Team",
|
"author": "OCAB Team",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.database": "^1.0.0"
|
"required": {
|
||||||
|
"standard.database": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.roles": "^1.0.0",
|
"required": {
|
||||||
"standard.database": "^1.0.0",
|
"standard.roles": "^1.0.0",
|
||||||
"standard.command_helper": "^1.0.0"
|
"standard.database": "^1.0.0",
|
||||||
|
"standard.command_helper": "^1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.roles": "^1.0.0",
|
"required": {
|
||||||
"standard.database": "^1.0.0",
|
"standard.roles": "^1.0.0",
|
||||||
"standard.command_helper": "^1.0.0"
|
"standard.database": "^1.0.0",
|
||||||
|
"standard.command_helper": "^1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
from .main import module_init
|
from .lib import register_page
|
||||||
|
from .main import module_init, module_late_init
|
||||||
|
@ -5,5 +5,17 @@
|
|||||||
"author": "OCAB Team",
|
"author": "OCAB Team",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": false,
|
"privileged": false,
|
||||||
"dependencies": {}
|
"dependencies": {
|
||||||
|
"required": {
|
||||||
|
"standard.config": {
|
||||||
|
"version": "^1.0.0",
|
||||||
|
"uses": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pythonDependencies": {
|
||||||
|
"required": {
|
||||||
|
"flet": "^0.23.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
172
src/ocab_modules/standard/miniapp/lib.py
Normal file
172
src/ocab_modules/standard/miniapp/lib.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import dash
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
||||||
|
from dash_extensions.pages import setup_page_components
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
pages = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
|
def register_page(name, path, blueprint):
|
||||||
|
pages[path] = {
|
||||||
|
"name": name,
|
||||||
|
"blueprint": blueprint,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def register_home_page():
|
||||||
|
page = DashBlueprint()
|
||||||
|
page.layout = html.Div([html.H1("Главная")])
|
||||||
|
register_page("Главная", path="/", blueprint=page)
|
||||||
|
|
||||||
|
|
||||||
|
register_home_page()
|
||||||
|
|
||||||
|
|
||||||
|
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
||||||
|
server = Flask(__name__)
|
||||||
|
app = DashProxy(
|
||||||
|
pages_folder="",
|
||||||
|
use_pages=True,
|
||||||
|
suppress_callback_exceptions=True,
|
||||||
|
external_stylesheets=[
|
||||||
|
dbc.themes.BOOTSTRAP,
|
||||||
|
dbc.icons.BOOTSTRAP,
|
||||||
|
],
|
||||||
|
external_scripts=[
|
||||||
|
"https://telegram.org/js/telegram-web-app.js"
|
||||||
|
], # Add Telegram Mini Apps script to <head>
|
||||||
|
server=server,
|
||||||
|
requests_pathname_prefix=requests_pathname_prefix,
|
||||||
|
meta_tags=[
|
||||||
|
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.enable_dev_tools(
|
||||||
|
dev_tools_ui=True,
|
||||||
|
dev_tools_serve_dev_bundles=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register pages
|
||||||
|
for path, page in pages.items():
|
||||||
|
# dash.register_page(page["name"], path=path, layout=page["layout"])
|
||||||
|
page["blueprint"].register(app, path, prefix="a")
|
||||||
|
|
||||||
|
# Create sidebar
|
||||||
|
sidebar = dbc.Offcanvas(
|
||||||
|
id="offcanvas",
|
||||||
|
title="Меню",
|
||||||
|
is_open=False,
|
||||||
|
children=[
|
||||||
|
dbc.Nav(
|
||||||
|
[
|
||||||
|
dbc.NavLink(
|
||||||
|
page["name"],
|
||||||
|
href=f"{requests_pathname_prefix}{path.lstrip('/')}",
|
||||||
|
id={"type": "nav-link", "index": path},
|
||||||
|
)
|
||||||
|
for path, page in pages.items()
|
||||||
|
],
|
||||||
|
vertical=True,
|
||||||
|
pills=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create navbar
|
||||||
|
navbar = dbc.Navbar(
|
||||||
|
dbc.Container(
|
||||||
|
[
|
||||||
|
dbc.Button(
|
||||||
|
html.I(className="bi bi-list"),
|
||||||
|
id="open-offcanvas",
|
||||||
|
color="light",
|
||||||
|
className="me-2",
|
||||||
|
),
|
||||||
|
dbc.NavbarBrand("OCAB"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
color="primary",
|
||||||
|
dark=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define app layout
|
||||||
|
app.layout = html.Div(
|
||||||
|
[
|
||||||
|
dcc.Location(id="url", refresh=False),
|
||||||
|
dcc.Store(id="user-data", storage_type="session"),
|
||||||
|
dcc.Interval(
|
||||||
|
id="init-telegram-interval",
|
||||||
|
interval=100,
|
||||||
|
n_intervals=0,
|
||||||
|
max_intervals=1,
|
||||||
|
),
|
||||||
|
# WebSocket(url="/ws"),
|
||||||
|
html.Div(id="telegram-login-info"),
|
||||||
|
navbar,
|
||||||
|
sidebar,
|
||||||
|
dash.page_container,
|
||||||
|
setup_page_components(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clientside callback to initialize Telegram Mini Apps and get user data
|
||||||
|
app.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(n_intervals) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolve("test");
|
||||||
|
if (window.Telegram && window.Telegram.WebApp) {
|
||||||
|
const webapp = window.Telegram.WebApp;
|
||||||
|
webapp.ready();
|
||||||
|
if (webapp.initDataUnsafe && webapp.initDataUnsafe.user) {
|
||||||
|
resolve(webapp.initDataUnsafe.user);
|
||||||
|
} else {
|
||||||
|
reject("User not authorized");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject("Telegram Mini Apps not available");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
Output("user-data", "data"),
|
||||||
|
Input("init-telegram-interval", "n_intervals"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Открытие на кнопку меню
|
||||||
|
app.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(n_clicks) {
|
||||||
|
if (n_clicks == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
Output(
|
||||||
|
"offcanvas",
|
||||||
|
"is_open",
|
||||||
|
),
|
||||||
|
Input("open-offcanvas", "n_clicks"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Закрываем offcanvas при клике на ссылку в меню
|
||||||
|
app.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(n_clicks) {
|
||||||
|
if (n_clicks == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
Output("offcanvas", "is_open", allow_duplicate=True),
|
||||||
|
Input({"type": "nav-link", "index": dash.dependencies.ALL}, "n_clicks"),
|
||||||
|
prevent_initial_call="initial_duplicate",
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
@ -1,2 +1,31 @@
|
|||||||
|
from aiogram import types
|
||||||
|
from fastapi.middleware.wsgi import WSGIMiddleware
|
||||||
|
|
||||||
|
from ocab_core.modules_system.public_api import Storage, set_chat_menu_button
|
||||||
|
|
||||||
|
|
||||||
|
def get_link():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def module_init():
|
def module_init():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def register_page():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def module_late_init():
|
||||||
|
from .lib import create_dash_app
|
||||||
|
|
||||||
|
dash_app = create_dash_app(requests_pathname_prefix="/webapp/")
|
||||||
|
|
||||||
|
Storage.set("webapp", WSGIMiddleware(dash_app.server))
|
||||||
|
|
||||||
|
web_app_info = types.WebAppInfo(
|
||||||
|
url="https://mackerel-pumped-foal.ngrok-free.app/webapp"
|
||||||
|
)
|
||||||
|
menu_button = types.MenuButtonWebApp(text="Меню", web_app=web_app_info)
|
||||||
|
|
||||||
|
await set_chat_menu_button(menu_button)
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"privileged": true,
|
"privileged": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"standard.config": "^1.0.0",
|
"required": {
|
||||||
"standard.database": "^1.0.0"
|
"standard.config": "^1.0.0",
|
||||||
|
"standard.database": "^1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user