mirror of
https://gitflic.ru/project/maks1ms/ocab.git
synced 2025-01-11 17:28:12 +03:00
wip
This commit is contained in:
parent
6aab1ee244
commit
e48e83bf2c
@ -49,17 +49,48 @@ async def long_polling_mode():
|
|||||||
async def webhook_mode():
|
async def webhook_mode():
|
||||||
singleton = Singleton()
|
singleton = Singleton()
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
app.mount("/webapp", singleton.storage["webapp"])
|
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(config.get("core::webhook::public_url"))
|
||||||
"https://mackerel-pumped-foal.ngrok-free.app/webhook"
|
|
||||||
|
hyperConfig = HyperConfig()
|
||||||
|
hyperConfig.bind = [f"0.0.0.0:{config.get("core::webhook::port")}"]
|
||||||
|
hyperConfig.logger_class = CustomLogger
|
||||||
|
await serve(app, hyperConfig)
|
||||||
|
|
||||||
|
|
||||||
|
def register_config():
|
||||||
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"core::token",
|
||||||
|
"password",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"core::mode",
|
||||||
|
"select",
|
||||||
|
options=["WEBHOOK", "LONG_POLLING"],
|
||||||
|
default_value="WEBHOOK",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"core::webhook::port",
|
||||||
|
"int",
|
||||||
|
default_value=9000,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"core::webhook::public_url",
|
||||||
|
"string",
|
||||||
|
visible=False,
|
||||||
)
|
)
|
||||||
config = HyperConfig()
|
|
||||||
config.bind = ["0.0.0.0:9000"]
|
|
||||||
config.logger_class = CustomLogger
|
|
||||||
await serve(app, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def init_app():
|
async def init_app():
|
||||||
@ -74,9 +105,13 @@ async def init_app():
|
|||||||
log(f"Loading {info.name} ({info.id}) module")
|
log(f"Loading {info.name} ({info.id}) module")
|
||||||
await singleton.modules_manager.load(module_loader)
|
await singleton.modules_manager.load(module_loader)
|
||||||
|
|
||||||
get_telegram_token = get_module("standard.config", ["get_telegram_token"])
|
register_config()
|
||||||
|
|
||||||
singleton.bot = Bot(token=get_telegram_token())
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
|
config.load()
|
||||||
|
|
||||||
|
singleton.bot = Bot(token=config.get("core::token"))
|
||||||
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"])
|
||||||
|
|
||||||
@ -92,8 +127,12 @@ async def init_app():
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await init_app()
|
await init_app()
|
||||||
await webhook_mode()
|
config = get_module("standard.config", "config")
|
||||||
# await long_polling_mode()
|
|
||||||
|
if config.get("core::mode") == "WEBHOOK":
|
||||||
|
await webhook_mode()
|
||||||
|
else:
|
||||||
|
await long_polling_mode()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
46
src/ocab_modules/external/yandexgpt/main.py
vendored
46
src/ocab_modules/external/yandexgpt/main.py
vendored
@ -4,12 +4,42 @@ config = get_module("standard.config", "config")
|
|||||||
|
|
||||||
|
|
||||||
def module_init():
|
def module_init():
|
||||||
config.register_setting(["YANDEXGPT", "TOKEN"], "", "string", is_private=True)
|
config.register(
|
||||||
config.register_setting(["YANDEXGPT", "TOKEN_FOR_REQUEST"], 8000, "number")
|
"yandexgpt::token",
|
||||||
config.register_setting(["YANDEXGPT", "TOKEN_FOR_ANSWER"], 2000, "number")
|
"password",
|
||||||
config.register_setting(["YANDEXGPT", "CATALOGID"], "", "string", is_private=True)
|
required=True,
|
||||||
config.register_setting(["YANDEXGPT", "PROMPT"], "Ты чат-бот ...", "string")
|
)
|
||||||
config.register_setting(
|
config.register(
|
||||||
["YANDEXGPT", "STARTWORD"], "Бот| Бот, | бот | бот,", "string"
|
"yandexgpt::token_for_request",
|
||||||
|
"int",
|
||||||
|
default_value=8000,
|
||||||
|
)
|
||||||
|
config.register(
|
||||||
|
"yandexgpt::token_for_answer",
|
||||||
|
"int",
|
||||||
|
default_value=2000,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"yandexgpt::catalogid",
|
||||||
|
"password",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"yandexgpt::prompt",
|
||||||
|
"string",
|
||||||
|
default_value="Ты чат-бот ...",
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"yandexgpt::startword",
|
||||||
|
"string",
|
||||||
|
default_value="Бот| Бот, | бот | бот,",
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"yandexgpt::inword",
|
||||||
|
"string",
|
||||||
|
default_value="помогите | не работает",
|
||||||
)
|
)
|
||||||
config.register_setting(["YANDEXGPT", "INWORD"], "помогите | не работает", "string")
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
from .config import (
|
from .config import (
|
||||||
config,
|
config,
|
||||||
get_approved_chat_id,
|
|
||||||
get_default_chat_tag,
|
get_default_chat_tag,
|
||||||
get_roles,
|
|
||||||
get_telegram_token,
|
|
||||||
get_yandexgpt_in_words,
|
get_yandexgpt_in_words,
|
||||||
get_yandexgpt_prompt,
|
get_yandexgpt_prompt,
|
||||||
get_yandexgpt_start_words,
|
get_yandexgpt_start_words,
|
||||||
|
@ -6,13 +6,16 @@ config = ConfigManager(
|
|||||||
config_path="/home/maxim/dev/alt-gnome-infrastructure/ocab/src/ocab_core/config.yaml"
|
config_path="/home/maxim/dev/alt-gnome-infrastructure/ocab/src/ocab_core/config.yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
def register_settings(settings_manager: ConfigManager):
|
def register_settings(settings_manager: ConfigManager):
|
||||||
# TELEGRAM settings
|
settings_manager.register_setting(["CORE", "TOKEN"], "", "string", is_private=True)
|
||||||
settings_manager.register_setting(
|
settings_manager.register_setting(
|
||||||
["TELEGRAM", "TOKEN"], "", "string", is_private=True
|
["CORE", "MODE"], "WEBHOOK", "string", options=["WEBHOOK", "LONG_POLLING"]
|
||||||
)
|
)
|
||||||
settings_manager.register_setting(
|
|
||||||
|
# TODO: вынести в конкретные модули
|
||||||
|
|
||||||
|
settings_manager.register(
|
||||||
["TELEGRAM", "APPROVED_CHAT_ID"],
|
["TELEGRAM", "APPROVED_CHAT_ID"],
|
||||||
"-123456789 | -012345678",
|
"-123456789 | -012345678",
|
||||||
"string",
|
"string",
|
||||||
@ -32,13 +35,7 @@ def register_settings(settings_manager: ConfigManager):
|
|||||||
pretty_name="Основной чат",
|
pretty_name="Основной чат",
|
||||||
)
|
)
|
||||||
settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox")
|
settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox")
|
||||||
|
"""
|
||||||
|
|
||||||
register_settings(config)
|
|
||||||
|
|
||||||
|
|
||||||
def get_telegram_token() -> str:
|
|
||||||
return config["TELEGRAM"]["TOKEN"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_telegram_check_bot() -> bool:
|
def get_telegram_check_bot() -> bool:
|
||||||
@ -52,15 +49,6 @@ def get_approved_chat_id() -> list:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_roles():
|
|
||||||
return config["ROLES"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_role_name(role_number) -> dict:
|
|
||||||
# Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно
|
|
||||||
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"]
|
||||||
|
|
||||||
|
@ -1,299 +1,92 @@
|
|||||||
from typing import Any, Dict, List, Optional, Union
|
import inspect
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import flask
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
try:
|
|
||||||
import dash_bootstrap_components as dbc
|
|
||||||
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
|
||||||
|
|
||||||
DASH_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
DASH_AVAILABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
def __init__(self, config_path: str):
|
def __init__(self, config_path: str):
|
||||||
self._config_path = config_path
|
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
|
self._config: Dict[str, Dict[str, Any]] = {}
|
||||||
def config(self):
|
|
||||||
return self._config
|
|
||||||
|
|
||||||
@config.setter
|
self._metadata: Dict[str, Dict[str, Any]] = {}
|
||||||
def config(self, value):
|
|
||||||
self._config = value
|
|
||||||
|
|
||||||
def load_config(self) -> Dict[str, Any]:
|
def load(self, file_path: str = ""):
|
||||||
with open(self._config_path, "r") as file:
|
if not file_path:
|
||||||
return yaml.safe_load(file)
|
file_path = self.config_path
|
||||||
|
|
||||||
def save_config(self):
|
def build_key(prev, next):
|
||||||
with open(self._config_path, "w") as file:
|
if prev:
|
||||||
|
return f"{prev}::{next}"
|
||||||
|
return next
|
||||||
|
|
||||||
|
def recurse_set(value, key=""):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for k, v in value.items():
|
||||||
|
recurse_set(v, build_key(key, k))
|
||||||
|
return
|
||||||
|
if key in self._metadata:
|
||||||
|
self._config[key] = value
|
||||||
|
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
recurse_set(data)
|
||||||
|
|
||||||
|
def save(self, file_path: str = ""):
|
||||||
|
if not file_path:
|
||||||
|
file_path = self.config_path
|
||||||
|
with open(file_path, "w", encoding="utf-8") as file:
|
||||||
yaml.dump(self._config, file, allow_unicode=True)
|
yaml.dump(self._config, file, allow_unicode=True)
|
||||||
|
|
||||||
def register_setting(
|
def _check_rights(self, key, module_id, access_type="get"):
|
||||||
|
return
|
||||||
|
|
||||||
|
def get(self, key: str):
|
||||||
|
module_id = self._get_module_id()
|
||||||
|
self._check_rights(key, module_id)
|
||||||
|
return self._config.get(key, self._metadata.get(key).get("default_value"))
|
||||||
|
|
||||||
|
def get_meta(self, key: str):
|
||||||
|
module_id = self._get_module_id()
|
||||||
|
self._check_rights(key, module_id, "get_meta")
|
||||||
|
return self._metadata.get(key)
|
||||||
|
|
||||||
|
def _get_module_id(self):
|
||||||
|
caller_frame = inspect.currentframe().f_back.f_back
|
||||||
|
caller_globals = caller_frame.f_globals
|
||||||
|
module_id = caller_globals.get("__ocab_module_id__")
|
||||||
|
return module_id
|
||||||
|
|
||||||
|
def register(
|
||||||
self,
|
self,
|
||||||
key: Union[str, List[str]],
|
key: str,
|
||||||
default_value: Any,
|
value_type: str,
|
||||||
setting_type: str,
|
options: List[Any] = None,
|
||||||
is_private: bool = False,
|
default_value=None,
|
||||||
pretty_name: str = None,
|
editable: bool = True,
|
||||||
description: str = None,
|
shared: bool = False,
|
||||||
options: Optional[List[str]] = None,
|
required: bool = False,
|
||||||
|
visible: bool = True,
|
||||||
|
pretty_name: str = "",
|
||||||
|
description: str = "",
|
||||||
):
|
):
|
||||||
if isinstance(key, str):
|
module_id = self._get_module_id()
|
||||||
key = [key]
|
|
||||||
|
|
||||||
current = self._registered_settings
|
self._check_rights(key, module_id, "register")
|
||||||
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)
|
if key in self._metadata:
|
||||||
|
raise ValueError("ERROR")
|
||||||
|
|
||||||
self.set_nested_setting(
|
self._metadata[key] = {
|
||||||
self._registered_settings_meta,
|
"type": value_type,
|
||||||
key,
|
"options": options,
|
||||||
{
|
"default_value": default_value,
|
||||||
"type": setting_type,
|
"visible": visible,
|
||||||
"is_private": is_private,
|
"editable": editable,
|
||||||
"options": options,
|
"shared": shared,
|
||||||
"pretty_name": pretty_name,
|
"required": required,
|
||||||
"description": description,
|
"pretty_name": pretty_name,
|
||||||
},
|
"description": description,
|
||||||
)
|
"module_id": module_id,
|
||||||
|
}
|
||||||
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, prefix):
|
|
||||||
from dash_extensions.enrich import DashBlueprint
|
|
||||||
|
|
||||||
self._prefix = prefix
|
|
||||||
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 not label_text:
|
|
||||||
label_text = 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=label_text,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
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),
|
|
||||||
html.Div(id="save-confirmation"),
|
|
||||||
dbc.Button(
|
|
||||||
"Сохранить",
|
|
||||||
id="save-settings",
|
|
||||||
color="primary",
|
|
||||||
className="mt-3 w-100",
|
|
||||||
n_clicks=0,
|
|
||||||
),
|
|
||||||
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
|
||||||
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):
|
|
||||||
@app.callback(
|
|
||||||
Output("save-confirmation", "children", allow_duplicate=True),
|
|
||||||
Output("settings-store", "data"),
|
|
||||||
Input("save-settings", "n_clicks"),
|
|
||||||
State({"type": "setting", "key": ALL}, "value"),
|
|
||||||
State({"type": "setting", "key": ALL}, "id"),
|
|
||||||
prevent_initial_call=True,
|
|
||||||
allow_duplicate=True,
|
|
||||||
# https://github.com/emilhe/dash-extensions/issues/344
|
|
||||||
# running=[
|
|
||||||
# (
|
|
||||||
# Output({
|
|
||||||
# id: "save-settings",
|
|
||||||
# 'n_clicks': MATCH
|
|
||||||
# }, "disabled"), True, False
|
|
||||||
# )
|
|
||||||
# ]
|
|
||||||
)
|
|
||||||
def save_settings(n_clicks, values, keys):
|
|
||||||
print(flask.g.user)
|
|
||||||
|
|
||||||
if n_clicks > 0:
|
|
||||||
updated_settings = {}
|
|
||||||
|
|
||||||
for value, id_dict in zip(values, keys):
|
|
||||||
key: str = id_dict["key"]
|
|
||||||
if self._prefix:
|
|
||||||
key = key.removeprefix(f"{self._prefix}-")
|
|
||||||
|
|
||||||
self.update_setting(key.split("::"), value)
|
|
||||||
updated_settings[key] = value
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import locale
|
|
||||||
|
|
||||||
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
|
|
||||||
date_str = now.strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
return (
|
|
||||||
dbc.Alert(
|
|
||||||
f"Настройки сохранены в {date_str}",
|
|
||||||
color="success",
|
|
||||||
duration=10000,
|
|
||||||
),
|
|
||||||
date_str,
|
|
||||||
)
|
|
||||||
|
|
||||||
app.clientside_callback(
|
|
||||||
"""
|
|
||||||
function(n_clicks) {
|
|
||||||
const buttonSelector = '#%s-save-settings';
|
|
||||||
if (n_clicks > 0) {
|
|
||||||
document.querySelector(buttonSelector).disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
% (self._prefix),
|
|
||||||
Input("save-settings", "n_clicks"),
|
|
||||||
)
|
|
||||||
|
|
||||||
app.clientside_callback(
|
|
||||||
"""
|
|
||||||
function(data) {
|
|
||||||
const buttonSelector = '#%s-save-settings';
|
|
||||||
if (data) {
|
|
||||||
document.querySelector(buttonSelector).disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
% (self._prefix),
|
|
||||||
Input("settings-store", "data"),
|
|
||||||
)
|
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
from ocab_core.modules_system.public_api import get_module, log
|
from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
from .config import config
|
from .config import config
|
||||||
|
from .miniapp_ui import get_miniapp_blueprint
|
||||||
|
|
||||||
|
|
||||||
def register_settings_page():
|
def register_settings_page():
|
||||||
try:
|
try:
|
||||||
register_page = get_module("standard.miniapp", "register_page")
|
register_page = get_module("standard.miniapp", "register_page")
|
||||||
|
|
||||||
|
prefix = "settings"
|
||||||
|
|
||||||
register_page(
|
register_page(
|
||||||
name="Настройки",
|
name="Настройки",
|
||||||
path="/settings",
|
path="/settings",
|
||||||
blueprint=config.get_settings_layout("settings"),
|
blueprint=get_miniapp_blueprint(config, prefix),
|
||||||
prefix="settings",
|
prefix=prefix,
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
223
src/ocab_modules/standard/config/miniapp_ui.py
Normal file
223
src/ocab_modules/standard/config/miniapp_ui.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
from .config_manager import ConfigManager
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
||||||
|
|
||||||
|
DASH_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
DASH_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
def create_control(key, config):
|
||||||
|
value = config.get(key)
|
||||||
|
meta = config.get_meta(key)
|
||||||
|
|
||||||
|
component_id = {
|
||||||
|
"type": "setting",
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
|
||||||
|
label_text = meta.get("pretty_name") or key
|
||||||
|
|
||||||
|
if meta.get("type") in ["string", "int", "float", "password"]:
|
||||||
|
input_type = {
|
||||||
|
"string": "text",
|
||||||
|
"int": "number",
|
||||||
|
"float": "number",
|
||||||
|
"password": "password",
|
||||||
|
}.get(meta.get("type"), "text")
|
||||||
|
|
||||||
|
input_props = {
|
||||||
|
"id": component_id,
|
||||||
|
"type": input_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.get("type") != "password":
|
||||||
|
input_props["value"] = value
|
||||||
|
|
||||||
|
if meta.get("type") == "int":
|
||||||
|
input_props["step"] = 1
|
||||||
|
input_props["pattern"] = r"\d+"
|
||||||
|
elif meta.get("type") == "float":
|
||||||
|
input_props["step"] = "any"
|
||||||
|
|
||||||
|
component = dbc.Input(**input_props, invalid=False)
|
||||||
|
|
||||||
|
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,
|
||||||
|
style={
|
||||||
|
"padding-left": 0,
|
||||||
|
"padding-right": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
elif meta.get("type") == "checkbox":
|
||||||
|
component = dbc.Checkbox(
|
||||||
|
id=component_id,
|
||||||
|
checked=value,
|
||||||
|
label=label_text,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
row = []
|
||||||
|
if meta.get("type") != "checkbox":
|
||||||
|
row.append(dbc.Label(label_text))
|
||||||
|
|
||||||
|
row.append(component)
|
||||||
|
|
||||||
|
if meta.get("description"):
|
||||||
|
row.append(dbc.FormText(meta.get("description")))
|
||||||
|
|
||||||
|
return dbc.Row(row, className="mb-3 mx-1")
|
||||||
|
|
||||||
|
|
||||||
|
def build_settings_tree(config):
|
||||||
|
tree = {}
|
||||||
|
|
||||||
|
for key, value in config._metadata.items():
|
||||||
|
if not value["visible"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
parts = key.split("::")
|
||||||
|
control = create_control(key, config)
|
||||||
|
|
||||||
|
current = tree
|
||||||
|
for i, part in enumerate(parts[:-1]):
|
||||||
|
if part not in current:
|
||||||
|
current[part] = {"__controls": []}
|
||||||
|
current = current[part]
|
||||||
|
|
||||||
|
current["__controls"].append(control)
|
||||||
|
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
def create_card(category, controls):
|
||||||
|
return dbc.Card(
|
||||||
|
[
|
||||||
|
dbc.CardHeader(html.H3(category, className="mb-0")),
|
||||||
|
dbc.CardBody(controls),
|
||||||
|
],
|
||||||
|
className="mb-3",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_settings_components(tree, level=0):
|
||||||
|
components = []
|
||||||
|
|
||||||
|
for category, subtree in tree.items():
|
||||||
|
if category == "__controls":
|
||||||
|
continue
|
||||||
|
|
||||||
|
controls = subtree.get("__controls", [])
|
||||||
|
subcomponents = create_settings_components(subtree, level + 1)
|
||||||
|
|
||||||
|
if controls or subcomponents:
|
||||||
|
card_content = controls + subcomponents
|
||||||
|
card = create_card(category, card_content)
|
||||||
|
components.append(card)
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
|
||||||
|
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
||||||
|
import datetime
|
||||||
|
import locale
|
||||||
|
|
||||||
|
from dash_extensions.enrich import DashBlueprint
|
||||||
|
|
||||||
|
bp = DashBlueprint()
|
||||||
|
|
||||||
|
def create_layout():
|
||||||
|
settings_tree = build_settings_tree(config)
|
||||||
|
settings_components = create_settings_components(settings_tree)
|
||||||
|
|
||||||
|
layout = html.Div(
|
||||||
|
[
|
||||||
|
html.Script(),
|
||||||
|
html.H1("Настройки"),
|
||||||
|
dbc.Form(settings_components),
|
||||||
|
html.Div(id="save-confirmation"),
|
||||||
|
dbc.Button(
|
||||||
|
"Сохранить",
|
||||||
|
id="save-settings",
|
||||||
|
color="primary",
|
||||||
|
className="mt-3 w-100",
|
||||||
|
n_clicks=0,
|
||||||
|
),
|
||||||
|
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
||||||
|
dcc.Store(id="settings-store"),
|
||||||
|
],
|
||||||
|
style={
|
||||||
|
"padding": "20px",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return layout
|
||||||
|
|
||||||
|
bp.layout = create_layout
|
||||||
|
|
||||||
|
@bp.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"),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
allow_duplicate=True,
|
||||||
|
)
|
||||||
|
def save_settings(n_clicks, values, keys):
|
||||||
|
if n_clicks > 0:
|
||||||
|
# TODO: добавить валидацию значений
|
||||||
|
|
||||||
|
updated_settings = {}
|
||||||
|
for value, id_dict in zip(values, keys):
|
||||||
|
key: str = id_dict["key"]
|
||||||
|
if prefix:
|
||||||
|
key = key.removeprefix(f"{prefix}-")
|
||||||
|
updated_settings[key] = value
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
date_str = now.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
return (
|
||||||
|
dbc.Alert(
|
||||||
|
f"Настройки сохранены в {date_str}",
|
||||||
|
color="success",
|
||||||
|
duration=10000,
|
||||||
|
),
|
||||||
|
date_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
bp.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(n_clicks) {
|
||||||
|
const buttonSelector = '#%s-save-settings';
|
||||||
|
if (n_clicks > 0) {
|
||||||
|
document.querySelector(buttonSelector).disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
% (prefix),
|
||||||
|
Input("save-settings", "n_clicks"),
|
||||||
|
)
|
||||||
|
|
||||||
|
bp.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(data) {
|
||||||
|
const buttonSelector = '#%s-save-settings';
|
||||||
|
if (data) {
|
||||||
|
document.querySelector(buttonSelector).disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
% (prefix),
|
||||||
|
Input("settings-store", "data"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return bp
|
@ -1 +1,6 @@
|
|||||||
from .filters import ChatModerOrAdminFilter, ChatNotInApproveFilter
|
from .filters import (
|
||||||
|
ChatModerOrAdminFilter,
|
||||||
|
ChatNotInApproveFilter,
|
||||||
|
chat_not_in_approve,
|
||||||
|
module_init,
|
||||||
|
)
|
||||||
|
@ -4,10 +4,31 @@ from aiogram.types import Message
|
|||||||
|
|
||||||
from ocab_core.modules_system.public_api import get_module, log
|
from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
get_approved_chat_id = get_module("standard.config", "get_approved_chat_id")
|
config = get_module("standard.config", "config")
|
||||||
Roles = get_module("standard.roles", "Roles")
|
Roles = get_module("standard.roles", "Roles")
|
||||||
|
|
||||||
|
|
||||||
|
def module_init():
|
||||||
|
config.register("filters::approved_chat_id", "string")
|
||||||
|
|
||||||
|
|
||||||
|
def get_approved_chat_id() -> list:
|
||||||
|
# Возваращем сплитованный список id чатов в формате int
|
||||||
|
return [
|
||||||
|
int(chat_id) for chat_id in config.get("filters::approved_chat_id").split(" | ")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def chat_not_in_approve(message: Message) -> bool:
|
||||||
|
chat_id = message.chat.id
|
||||||
|
if chat_id in get_approved_chat_id():
|
||||||
|
log(f"Chat in approve list: {chat_id}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
log(f"Chat not in approve list: {chat_id}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@ -22,11 +43,4 @@ class ChatModerOrAdminFilter(BaseFilter):
|
|||||||
|
|
||||||
class ChatNotInApproveFilter(BaseFilter):
|
class ChatNotInApproveFilter(BaseFilter):
|
||||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
async def __call__(self, message: Message, bot: Bot) -> bool:
|
||||||
log("chat_check")
|
return chat_not_in_approve(message)
|
||||||
chat_id = message.chat.id
|
|
||||||
if chat_id in get_approved_chat_id():
|
|
||||||
log(f"Chat in approve list: {chat_id}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
log(f"Chat not in approve list: {chat_id}")
|
|
||||||
return True
|
|
||||||
|
@ -6,11 +6,13 @@ from ocab_core.modules_system.public_api import get_module, log, register_router
|
|||||||
|
|
||||||
# from ocab_modules.standard.database.db_api import *
|
# from ocab_modules.standard.database.db_api import *
|
||||||
|
|
||||||
(get_approved_chat_id, get_yandexgpt_in_words, get_yandexgpt_start_words) = get_module(
|
(get_yandexgpt_in_words, get_yandexgpt_start_words) = get_module(
|
||||||
"standard.config",
|
"standard.config",
|
||||||
["get_approved_chat_id", "get_yandexgpt_in_words", "get_yandexgpt_start_words"],
|
["get_yandexgpt_in_words", "get_yandexgpt_start_words"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
chat_not_in_approve = get_module("standard.filters", ["chat_not_in_approve"])
|
||||||
|
|
||||||
answer_to_message = get_module("external.yandexgpt", "answer_to_message")
|
answer_to_message = get_module("external.yandexgpt", "answer_to_message")
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -48,7 +50,7 @@ async def chat_check(message: types.Message):
|
|||||||
# Если чата нет в базе данных, то проверяем его в наличии в конфиге и если он там есть то добавляем его в БД
|
# Если чата нет в базе данных, то проверяем его в наличии в конфиге и если он там есть то добавляем его в БД
|
||||||
# Если чат есть в базе данных, то pass
|
# Если чат есть в базе данных, то pass
|
||||||
if get_chat(message.chat.id) is None:
|
if get_chat(message.chat.id) is None:
|
||||||
if message.chat.id in get_approved_chat_id():
|
if not chat_not_in_approve(message):
|
||||||
# print(f"Chat in approve list: {message.chat.id} {message.chat.title}")
|
# print(f"Chat in approve list: {message.chat.id} {message.chat.title}")
|
||||||
log(f"Chat in approve list: {message.chat.id} {message.chat.title}")
|
log(f"Chat in approve list: {message.chat.id} {message.chat.title}")
|
||||||
add_chat(message.chat.id, message.chat.title)
|
add_chat(message.chat.id, message.chat.title)
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import time
|
|
||||||
from urllib.parse import parse_qsl
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
||||||
from dash import Dash
|
from dash import Dash
|
||||||
@ -13,28 +8,6 @@ from flask import request
|
|||||||
def get_auth_server(bot_token: str):
|
def get_auth_server(bot_token: str):
|
||||||
server = flask.Flask(__name__)
|
server = flask.Flask(__name__)
|
||||||
|
|
||||||
def validate_init_data(init_data):
|
|
||||||
try:
|
|
||||||
init_data = dict(parse_qsl(init_data))
|
|
||||||
received_hash = init_data.pop("hash")
|
|
||||||
data_check_string = "\n".join(
|
|
||||||
f"{k}={v}" for k, v in sorted(init_data.items())
|
|
||||||
)
|
|
||||||
secret_key = hmac.new(
|
|
||||||
b"WebAppData", bot_token.encode(), hashlib.sha256
|
|
||||||
).digest()
|
|
||||||
calculated_hash = hmac.new(
|
|
||||||
secret_key, data_check_string.encode(), hashlib.sha256
|
|
||||||
).hexdigest()
|
|
||||||
|
|
||||||
auth_date = int(init_data.get("auth_date", 0))
|
|
||||||
if (time.time() - auth_date) > 86400:
|
|
||||||
return False, "Init data is outdated"
|
|
||||||
|
|
||||||
return calculated_hash == received_hash, init_data
|
|
||||||
except Exception as e:
|
|
||||||
return False, str(e)
|
|
||||||
|
|
||||||
@server.before_request
|
@server.before_request
|
||||||
def add_auth_data():
|
def add_auth_data():
|
||||||
init_data = request.cookies.get("tg_init_data")
|
init_data = request.cookies.get("tg_init_data")
|
||||||
|
@ -5,8 +5,7 @@ import dash_bootstrap_components as dbc
|
|||||||
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
||||||
from dash_extensions.pages import setup_page_components
|
from dash_extensions.pages import setup_page_components
|
||||||
|
|
||||||
# TODO: заменить на get_module("standard.config")
|
from ocab_core.modules_system.public_api import get_module
|
||||||
from ocab_modules.standard.config.config import get_telegram_token
|
|
||||||
|
|
||||||
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallback
|
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallback
|
||||||
|
|
||||||
@ -29,9 +28,11 @@ def register_home_page():
|
|||||||
|
|
||||||
register_home_page()
|
register_home_page()
|
||||||
|
|
||||||
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
|
|
||||||
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
||||||
server = get_auth_server(get_telegram_token())
|
server = get_auth_server(config.get("core::token"))
|
||||||
|
|
||||||
app = DashProxy(
|
app = DashProxy(
|
||||||
pages_folder="",
|
pages_folder="",
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
from aiogram import types
|
from aiogram import types
|
||||||
from fastapi.middleware.wsgi import WSGIMiddleware
|
from fastapi.middleware.wsgi import WSGIMiddleware
|
||||||
|
|
||||||
from ocab_core.modules_system.public_api import Storage, set_chat_menu_button
|
from ocab_core.modules_system.public_api import (
|
||||||
|
Storage,
|
||||||
|
get_module,
|
||||||
|
set_chat_menu_button,
|
||||||
|
)
|
||||||
|
|
||||||
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
|
|
||||||
def get_link():
|
def get_link():
|
||||||
@ -9,6 +15,20 @@ def get_link():
|
|||||||
|
|
||||||
|
|
||||||
def module_init():
|
def module_init():
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"miniapp::prefix",
|
||||||
|
"string",
|
||||||
|
default_value="/webapp/",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
config.register(
|
||||||
|
"miniapp::public_url",
|
||||||
|
"string",
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -19,13 +39,11 @@ def register_page():
|
|||||||
async def module_late_init():
|
async def module_late_init():
|
||||||
from .lib import create_dash_app
|
from .lib import create_dash_app
|
||||||
|
|
||||||
dash_app = create_dash_app(requests_pathname_prefix="/webapp/")
|
dash_app = create_dash_app(requests_pathname_prefix=config.get("miniapp::prefix"))
|
||||||
|
|
||||||
Storage.set("webapp", WSGIMiddleware(dash_app.server))
|
Storage.set("webapp", WSGIMiddleware(dash_app.server))
|
||||||
|
|
||||||
web_app_info = types.WebAppInfo(
|
web_app_info = types.WebAppInfo(url=config.get("miniapp::public_url"))
|
||||||
url="https://mackerel-pumped-foal.ngrok-free.app/webapp"
|
|
||||||
)
|
|
||||||
menu_button = types.MenuButtonWebApp(text="Меню", web_app=web_app_info)
|
menu_button = types.MenuButtonWebApp(text="Меню", web_app=web_app_info)
|
||||||
|
|
||||||
await set_chat_menu_button(menu_button)
|
await set_chat_menu_button(menu_button)
|
||||||
|
@ -4,9 +4,29 @@ from ocab_core.modules_system.public_api import get_module
|
|||||||
def module_init():
|
def module_init():
|
||||||
config = get_module("standard.config", "config")
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
config.register_setting(["ROLES", "ADMIN"], 2, "number", is_private=True)
|
config.register(
|
||||||
config.register_setting(["ROLES", "MODERATOR"], 1, "number", is_private=True)
|
"roles::admin",
|
||||||
config.register_setting(["ROLES", "USER"], 0, "number", is_private=True)
|
"number",
|
||||||
config.register_setting(["ROLES", "BOT"], 3, "number", is_private=True)
|
default_value=2,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
config.register(
|
||||||
|
"roles::moderator",
|
||||||
|
"number",
|
||||||
|
default_value=1,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
config.register(
|
||||||
|
"roles::user",
|
||||||
|
"number",
|
||||||
|
default_value=0,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
config.register(
|
||||||
|
"roles::bot",
|
||||||
|
"number",
|
||||||
|
default_value=3,
|
||||||
|
visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from ocab_core.modules_system.public_api import get_module
|
from ocab_core.modules_system.public_api import get_module
|
||||||
|
|
||||||
get_user_role = get_module("standard.database", "db_api.get_user_role")
|
get_user_role = get_module("standard.database", "db_api.get_user_role")
|
||||||
get_roles = get_module("standard.config", "get_roles")
|
config = get_module("standard.config", "config")
|
||||||
|
|
||||||
|
|
||||||
class Roles:
|
class Roles:
|
||||||
@ -14,11 +14,10 @@ class Roles:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def update_roles(self):
|
def update_roles(self):
|
||||||
roles = get_roles()
|
self.user_role_id = config.get("roles::user")
|
||||||
self.user_role_id = roles[self.user]
|
self.moderator_role_id = config.get("roles::moderator")
|
||||||
self.moderator_role_id = roles[self.moderator]
|
self.admin_role_id = config.get("roles::admin")
|
||||||
self.admin_role_id = roles[self.admin]
|
self.bot_role_id = config.get("roles::bot")
|
||||||
self.bot_role_id = roles[self.bot]
|
|
||||||
|
|
||||||
async def check_admin_permission(self, user_id):
|
async def check_admin_permission(self, user_id):
|
||||||
self.update_roles()
|
self.update_roles()
|
||||||
|
Loading…
Reference in New Issue
Block a user