0
0
mirror of https://gitflic.ru/project/maks1ms/ocab.git synced 2025-01-27 16:53:25 +03:00
This commit is contained in:
Maxim Slipenko 2024-07-22 00:44:27 +03:00
parent 2177c712a3
commit 6aab1ee244
16 changed files with 184 additions and 94 deletions

View File

@ -10,11 +10,9 @@ from ocab_core.lib import get_module_directory, register_bot_webhook
from ocab_core.logger import CustomLogger, log, setup_logger from ocab_core.logger import CustomLogger, 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.modules_system.public_api import get_module
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
ocab_modules_path = get_module_directory("ocab_modules") ocab_modules_path = get_module_directory("ocab_modules")
@ -28,15 +26,15 @@ 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=True),
# #
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), ocab_modules_loader("standard", "miniapp", safe=False),
] ]
@ -69,7 +67,6 @@ async def init_app():
singleton = Singleton() singleton = Singleton()
try: try:
singleton.bot = Bot(token=get_telegram_token())
singleton.modules_manager = ModulesManager() singleton.modules_manager = ModulesManager()
for module_loader in bot_modules: for module_loader in bot_modules:
@ -77,6 +74,9 @@ 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"])
singleton.bot = Bot(token=get_telegram_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"])

View File

@ -1,3 +1,4 @@
import importlib
import inspect import inspect
import pkg_resources import pkg_resources
@ -22,6 +23,9 @@ def is_version_compatible(version, requirement):
return [req] return [req]
for r in parse_requirement(requirement): for r in parse_requirement(requirement):
if r == "*":
continue
if not semver.Version.parse(version).match(r): if not semver.Version.parse(version).match(r):
return False return False
@ -32,13 +36,18 @@ def check_python_dependencies(info: ModuleInfo):
if info.pythonDependencies and info.pythonDependencies.required: if info.pythonDependencies and info.pythonDependencies.required:
for dependency, req in info.pythonDependencies.required.items(): for dependency, req in info.pythonDependencies.required.items():
try: try:
installed_version = pkg_resources.get_distribution(dependency).version importlib.import_module(dependency)
except pkg_resources.DistributionNotFound: except ImportError:
raise Exception( raise Exception(
f"Module {info.id} requires {dependency}," f"Module {info.id} requires {dependency}, "
f"but it is not installed" f"but it is not installed"
) )
try:
installed_version = pkg_resources.get_distribution(dependency).version
except pkg_resources.DistributionNotFound:
installed_version = "*"
if isinstance(req, str): if isinstance(req, str):
required_version = req required_version = req
elif isinstance(req, DependencyInfo): elif isinstance(req, DependencyInfo):

View File

@ -1 +1,2 @@
from .handlers import answer_to_message from .handlers import answer_to_message
from .main import module_init

View File

@ -2,13 +2,16 @@
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message from aiogram.types import Message
from ocab_modules.external.yandexgpt.yandexgpt import * from ocab_core.modules_system.public_api import get_module, log
from ocab_modules.standard.config.config import (
get_yandexgpt_catalog_id, from .yandexgpt import YandexGPT
get_yandexgpt_prompt,
get_yandexgpt_token, (get_yandexgpt_catalog_id, get_yandexgpt_token, get_yandexgpt_prompt) = get_module(
"standard.config",
["get_yandexgpt_catalog_id", "get_yandexgpt_token", "get_yandexgpt_prompt"],
) )
from ocab_modules.standard.database.db_api import add_message
add_message = get_module("standard.database", "db_api.add_message")
async def answer_to_message(message: Message, bot: Bot): async def answer_to_message(message: Message, bot: Bot):

View File

@ -4,6 +4,18 @@
"description": "Модуль для работы с Yandex GPT", "description": "Модуль для работы с Yandex GPT",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0.0", "version": "1.0.0",
"privileged": true, "privileged": false,
"dependencies": {} "dependencies": {
"required": {
"standard.config": "^1.0.0",
"standard.database": "^1.0.0"
}
},
"pythonDependencies": {
"required": {
"aiohttp": "*",
"requests": "*",
"json": "*"
}
}
} }

View File

@ -0,0 +1,15 @@
from ocab_core.modules_system.public_api import get_module
config = get_module("standard.config", "config")
def module_init():
config.register_setting(["YANDEXGPT", "TOKEN"], "", "string", is_private=True)
config.register_setting(["YANDEXGPT", "TOKEN_FOR_REQUEST"], 8000, "number")
config.register_setting(["YANDEXGPT", "TOKEN_FOR_ANSWER"], 2000, "number")
config.register_setting(["YANDEXGPT", "CATALOGID"], "", "string", is_private=True)
config.register_setting(["YANDEXGPT", "PROMPT"], "Ты чат-бот ...", "string")
config.register_setting(
["YANDEXGPT", "STARTWORD"], "Бот| Бот, | бот | бот,", "string"
)
config.register_setting(["YANDEXGPT", "INWORD"], "помогите | не работает", "string")

View File

@ -1,7 +1,7 @@
# flake8: noqa # flake8: noqa
from aiogram import F, Router from aiogram import F, Router
from src.ocab_modules.external.yandexgpt.handlers import answer_to_message from .handlers import answer_to_message
router = Router() router = Router()
# Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message # Если сообщение содержит в начале текст "Гномик" или "гномик" или отвечает на сообщение бота, то вызывается функция answer_to_message

View File

@ -1,14 +1,25 @@
# flake8: noqa # flake8: noqa
import asyncio
import json import json
import aiohttp import aiohttp
import requests import requests
from ocab_core.logger import log from ocab_core.modules_system.public_api import get_module, log
from ...standard.config.config import * db_api = get_module("standard.database", "db_api")
from ...standard.database import *
(
get_yandexgpt_token_for_answer,
get_yandexgpt_prompt,
get_yandexgpt_token_for_request,
) = get_module(
"standard.config",
[
"get_yandexgpt_token_for_answer",
"get_yandexgpt_prompt",
"get_yandexgpt_token_for_request",
],
)
class YandexGPT: class YandexGPT:
@ -108,8 +119,11 @@ class YandexGPT:
input_messages, input_messages,
stream=False, stream=False,
temperature=0.6, temperature=0.6,
max_tokens=get_yandexgpt_token_for_request(), max_tokens=None,
): ):
if max_tokens is None:
max_tokens = get_yandexgpt_token_for_request()
url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" url = "https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
gpt = f"gpt://{self.catalog_id}/yandexgpt/latest" gpt = f"gpt://{self.catalog_id}/yandexgpt/latest"
headers = { headers = {

View File

@ -1,8 +1,13 @@
from .config import ( from .config import (
config,
get_approved_chat_id, get_approved_chat_id,
get_default_chat_tag, get_default_chat_tag,
get_roles, get_roles,
get_telegram_token,
get_yandexgpt_in_words, get_yandexgpt_in_words,
get_yandexgpt_prompt,
get_yandexgpt_start_words, get_yandexgpt_start_words,
get_yandexgpt_token_for_answer,
get_yandexgpt_token_for_request,
) )
from .main import module_late_init from .main import module_late_init

View File

@ -33,33 +33,6 @@ def register_settings(settings_manager: ConfigManager):
) )
settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox") settings_manager.register_setting(["TELEGRAM", "CHECK_BOT"], True, "checkbox")
# YANDEXGPT settings
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")
register_settings(config) register_settings(config)

View File

@ -1,10 +1,8 @@
import time
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
import flask
import yaml import yaml
# from ocab_core.modules_system.public_api import get_module, log
try: try:
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
@ -37,7 +35,7 @@ class ConfigManager:
def save_config(self): def save_config(self):
with open(self._config_path, "w") as file: with open(self._config_path, "w") as file:
yaml.dump(self._config, file) yaml.dump(self._config, file, allow_unicode=True)
def register_setting( def register_setting(
self, self,
@ -244,7 +242,8 @@ class ConfigManager:
# ] # ]
) )
def save_settings(n_clicks, values, keys): def save_settings(n_clicks, values, keys):
time.sleep(3) print(flask.g.user)
if n_clicks > 0: if n_clicks > 0:
updated_settings = {} updated_settings = {}

View File

@ -0,0 +1,60 @@
import hashlib
import hmac
import time
from urllib.parse import parse_qsl
import flask
from aiogram.utils.web_app import safe_parse_webapp_init_data
from dash import Dash
from dash_extensions.enrich import Input
from flask import request
def get_auth_server(bot_token: str):
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
def add_auth_data():
init_data = request.cookies.get("tg_init_data")
if init_data:
try:
data = safe_parse_webapp_init_data(token=bot_token, init_data=init_data)
flask.g.user = data.user.model_dump()
except ValueError:
pass
return server
def setup_auth_clientcallback(app: Dash):
app.clientside_callback(
"""
function(n_inervals) {
const tg = window.Telegram.WebApp;
document.cookie = `tg_init_data=${JSON.stringify(tg.initData)}; path=/`;
}
""",
Input("init-telegram-interval", "n_intervals"),
)

View File

@ -4,7 +4,11 @@ import dash
import dash_bootstrap_components as dbc 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
from flask import Flask
# TODO: заменить на get_module("standard.config")
from ocab_modules.standard.config.config import get_telegram_token
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallback
pages = OrderedDict() pages = OrderedDict()
@ -27,7 +31,8 @@ register_home_page()
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash: def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
server = Flask(__name__) server = get_auth_server(get_telegram_token())
app = DashProxy( app = DashProxy(
pages_folder="", pages_folder="",
use_pages=True, use_pages=True,
@ -46,10 +51,10 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
], ],
) )
app.enable_dev_tools( # app.enable_dev_tools(
dev_tools_ui=True, # dev_tools_ui=True,
dev_tools_serve_dev_bundles=True, # dev_tools_serve_dev_bundles=True,
) # )
# Register pages # Register pages
for path, page in pages.items(): for path, page in pages.items():
@ -98,15 +103,12 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
app.layout = html.Div( app.layout = html.Div(
[ [
dcc.Location(id="url", refresh=False), dcc.Location(id="url", refresh=False),
# dcc.Store(id="user-data", storage_type="session"),
dcc.Interval( dcc.Interval(
id="init-telegram-interval", id="init-telegram-interval",
interval=100, interval=100,
n_intervals=0, n_intervals=0,
max_intervals=1, max_intervals=1,
), ),
# WebSocket(url="/ws"),
html.Div(id="telegram-login-info"),
navbar, navbar,
sidebar, sidebar,
dash.page_container, dash.page_container,
@ -114,29 +116,7 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
] ]
) )
# Clientside callback to initialize Telegram Mini Apps and get user data setup_auth_clientcallback(app)
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( app.clientside_callback(
@ -155,7 +135,7 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
Input("open-offcanvas", "n_clicks"), Input("open-offcanvas", "n_clicks"),
) )
# # Закрываем offcanvas при клике на ссылку в меню # Закрываем offcanvas при клике на ссылку в меню
app.clientside_callback( app.clientside_callback(
""" """
function(n_clicks) { function(n_clicks) {

View File

@ -1 +1,2 @@
from .main import module_init
from .roles import Roles from .roles import Roles

View File

@ -0,0 +1,12 @@
from ocab_core.modules_system.public_api import get_module
def module_init():
config = get_module("standard.config", "config")
config.register_setting(["ROLES", "ADMIN"], 2, "number", is_private=True)
config.register_setting(["ROLES", "MODERATOR"], 1, "number", is_private=True)
config.register_setting(["ROLES", "USER"], 0, "number", is_private=True)
config.register_setting(["ROLES", "BOT"], 3, "number", is_private=True)
pass

View File

@ -3,8 +3,6 @@ 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") get_roles = get_module("standard.config", "get_roles")
roles = get_roles()
class Roles: class Roles:
user = "USER" user = "USER"
@ -13,12 +11,17 @@ class Roles:
bot = "BOT" bot = "BOT"
def __init__(self): def __init__(self):
pass
def update_roles(self):
roles = get_roles()
self.user_role_id = roles[self.user] self.user_role_id = roles[self.user]
self.moderator_role_id = roles[self.moderator] self.moderator_role_id = roles[self.moderator]
self.admin_role_id = roles[self.admin] self.admin_role_id = roles[self.admin]
self.bot_role_id = roles[self.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()
match get_user_role(user_id): match get_user_role(user_id):
case self.admin_role_id: case self.admin_role_id:
return True return True
@ -26,6 +29,7 @@ class Roles:
return False return False
async def check_moderator_permission(self, user_id): async def check_moderator_permission(self, user_id):
self.update_roles()
match get_user_role(user_id): match get_user_role(user_id):
case self.moderator_role_id: case self.moderator_role_id:
return True return True
@ -33,6 +37,7 @@ class Roles:
return False return False
async def get_role_name(self, role_id): async def get_role_name(self, role_id):
self.update_roles()
match role_id: match role_id:
case self.admin_role_id: case self.admin_role_id:
return self.admin return self.admin
@ -46,6 +51,7 @@ class Roles:
raise ValueError(f"Нет роли с id={role_id}") raise ValueError(f"Нет роли с id={role_id}")
async def get_user_permission(self, user_id): async def get_user_permission(self, user_id):
self.update_roles()
match get_user_role(user_id): match get_user_role(user_id):
case self.admin_role_id: case self.admin_role_id:
return self.admin return self.admin