0
0
mirror of https://gitflic.ru/project/maks1ms/ocab.git synced 2025-04-19 16:03:44 +03:00
This commit is contained in:
Maxim Slipenko 2024-07-13 22:33:10 +03:00
parent d624ed4a1b
commit c53f6025ae
19 changed files with 224 additions and 34 deletions

View File

@ -21,16 +21,7 @@ def setup_logger():
) )
async def log(message): def log(message):
"""
Функция для логирования сообщений
Она асинхронная, хотя таковой на самом деле не является.
"""
log_new(message)
def log_new(message):
if isinstance(message, Exception): if isinstance(message, Exception):
error_message = f"Error: {str(message)}\n{traceback.format_exc()}" error_message = f"Error: {str(message)}\n{traceback.format_exc()}"
logging.error(error_message) logging.error(error_message)

View File

@ -3,7 +3,7 @@ import traceback
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from ocab_core.logger import setup_logger 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 from ocab_core.modules_system.loaders import FSLoader
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
@ -14,6 +14,7 @@ from service import paths
bot_modules = [ bot_modules = [
UnsafeFSLoader(f"{paths.modules_standard}/config"), UnsafeFSLoader(f"{paths.modules_standard}/config"),
UnsafeFSLoader(f"{paths.modules_standard}/database"), UnsafeFSLoader(f"{paths.modules_standard}/database"),
UnsafeFSLoader(f"{paths.modules_standard}/fsm_database_storage"),
UnsafeFSLoader(f"{paths.modules_standard}/roles"), UnsafeFSLoader(f"{paths.modules_standard}/roles"),
UnsafeFSLoader(f"{paths.modules_external}/yandexgpt"), UnsafeFSLoader(f"{paths.modules_external}/yandexgpt"),
FSLoader(f"{paths.modules_standard}/command_helper"), FSLoader(f"{paths.modules_standard}/command_helper"),
@ -24,6 +25,11 @@ bot_modules = [
FSLoader(f"{paths.modules_standard}/message_processing"), FSLoader(f"{paths.modules_standard}/message_processing"),
] ]
# import logging
# logger = logging.getLogger('peewee')
# logger.addHandler(logging.StreamHandler())
# logger.setLevel(logging.DEBUG)
async def main(): async def main():
bot = None bot = None
@ -33,13 +39,20 @@ async def main():
try: try:
app.bot = Bot(token=get_telegram_token()) app.bot = Bot(token=get_telegram_token())
app.dp = Dispatcher()
app.modules_manager = ModulesManager() app.modules_manager = ModulesManager()
for module_loader in bot_modules: for module_loader in bot_modules:
info = module_loader.info()
log(f"Loading {info.name}({info.id}) module")
await app.modules_manager.load(module_loader) await app.modules_manager.load(module_loader)
app.dp = Dispatcher(storage=app.storage["_fsm_storage"])
app.dp.include_routers(*app.storage["_routers"])
for middleware in app.storage["_outer_message_middlewares"]:
app.dp.message.outer_middleware.register(middleware)
await app.modules_manager.late_init() await app.modules_manager.late_init()
await app.dp.start_polling(app.bot) await app.dp.start_polling(app.bot)
except Exception: except Exception:

View File

@ -5,20 +5,18 @@ 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_new from ocab_core.logger import log
from ocab_core.singleton import Singleton from ocab_core.singleton import Singleton
log = log_new
def register_router(router: Router): def register_router(router: Router):
app = Singleton() app = Singleton()
app.dp.include_router(router) app.storage["_routers"].append(router)
def register_outer_message_middleware(middleware: BaseMiddleware): def register_outer_message_middleware(middleware: BaseMiddleware):
app = Singleton() app = Singleton()
app.dp.message.outer_middleware.register(middleware) app.storage["_outer_message_middlewares"].append(middleware)
async def set_my_commands(commands): async def set_my_commands(commands):
@ -40,6 +38,12 @@ async def get_fsm_context(chat_id: int, user_id: int) -> FSMContext:
) )
def set_fsm(storage):
app = Singleton()
log(storage)
app.storage["_fsm_storage"] = 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], ...]]:

View File

@ -1,4 +1,5 @@
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage
from ocab_core.modules_system import ModulesManager from ocab_core.modules_system import ModulesManager
@ -17,4 +18,8 @@ class Singleton(metaclass=SingletonMeta):
bot: Bot bot: Bot
dp: Dispatcher = None dp: Dispatcher = None
modules_manager: ModulesManager = None modules_manager: ModulesManager = None
storage = dict() storage = {
"_fsm_storage": MemoryStorage(),
"_routers": [],
"_outer_message_middlewares": [],
}

View File

@ -1,6 +1,4 @@
# flake8: noqa # flake8: noqa
import asyncio
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message from aiogram.types import Message
@ -15,7 +13,7 @@ from ocab_modules.standard.database.db_api import add_message
async def answer_to_message(message: Message, bot: Bot): async def answer_to_message(message: Message, bot: Bot):
# print("answer_to_message") # print("answer_to_message")
await log("answer_to_message") log("answer_to_message")
yagpt = YandexGPT(get_yandexgpt_token(), get_yandexgpt_catalog_id()) yagpt = YandexGPT(get_yandexgpt_token(), get_yandexgpt_catalog_id())
text = message.text text = message.text
prompt = get_yandexgpt_prompt() prompt = get_yandexgpt_prompt()

View File

@ -1,5 +1,5 @@
{ {
w "id": "external.yandexgpt", "id": "external.yandexgpt",
"name": "Yandex GPT", "name": "Yandex GPT",
"description": "Модуль для работы с Yandex GPT", "description": "Модуль для работы с Yandex GPT",
"author": "OCAB Team", "author": "OCAB Team",

View File

@ -57,7 +57,7 @@ class YandexGPT:
) )
except Exception as e: # TODO: Переделать обработку ошибок except Exception as e: # TODO: Переделать обработку ошибок
# print(e) # print(e)
await log(f"Error: {e}") log(f"Error: {e}")
continue continue
if int(len(response["tokens"])) < (max_tokens - answer_token): if int(len(response["tokens"])) < (max_tokens - answer_token):
@ -262,7 +262,7 @@ class YandexGPT:
) )
elif type == "yandexgpt": elif type == "yandexgpt":
# print("yandexgpt_request") # print("yandexgpt_request")
await log("yandexgpt_request") log("yandexgpt_request")
messages = await self.collect_messages(message_id, chat_id) messages = await self.collect_messages(message_id, chat_id)
return await self.async_yandexgpt( return await self.async_yandexgpt(
system_prompt=get_yandexgpt_prompt(), system_prompt=get_yandexgpt_prompt(),

View File

@ -5,7 +5,6 @@ from aiogram.types import BotCommand, Message, TelegramObject
from ocab_core.modules_system.public_api import ( from ocab_core.modules_system.public_api import (
get_module, get_module,
log,
register_outer_message_middleware, register_outer_message_middleware,
set_my_commands, set_my_commands,
) )
@ -94,5 +93,4 @@ async def set_user_commands():
async def module_late_init(): async def module_late_init():
await log("module_late_init")
await set_user_commands() await set_user_commands()

View File

@ -30,7 +30,7 @@ async def start_report(chat_id: int, bot: Bot):
@router.message(ReportState.input_kernel_info) @router.message(ReportState.input_kernel_info)
async def kernel_version_entered(message: Message, state: FSMContext): async def kernel_version_entered(message: Message, state: FSMContext):
await state.update_data(kernel=message.text) await state.update_data(kernel=message.text)
await message.answer(text="В каком приложении " "возникла проблема?") await message.answer(text="В каком приложении возникла проблема?")
await state.set_state(ReportState.input_app_name) await state.set_state(ReportState.input_app_name)
@ -38,7 +38,7 @@ async def kernel_version_entered(message: Message, state: FSMContext):
async def app_name_entered(message: Message, state: FSMContext): async def app_name_entered(message: Message, state: FSMContext):
await state.update_data(app_name=message.text) await state.update_data(app_name=message.text)
await message.answer( await message.answer(
text="Опиши проблему пошагово, " "что ты делал, что происходило, что не так" text="Опиши проблему пошагово, что ты делал, что происходило, что не так"
) )
await state.set_state(ReportState.input_problem_step_by_step) await state.set_state(ReportState.input_problem_step_by_step)
@ -46,7 +46,7 @@ async def app_name_entered(message: Message, state: FSMContext):
@router.message(ReportState.input_problem_step_by_step) @router.message(ReportState.input_problem_step_by_step)
async def problem_step_by_step_entered(message: Message, state: FSMContext): async def problem_step_by_step_entered(message: Message, state: FSMContext):
await state.update_data(problem_step_by_step=message.text) await state.update_data(problem_step_by_step=message.text)
await message.answer(text="Вот твой отчет сообщением, " "а также файлом:") await message.answer(text="Вот твой отчет сообщением, а также файлом:")
data = await state.get_data() data = await state.get_data()
report = f"""Стенд с ошибкой: report = f"""Стенд с ошибкой:

View File

@ -1,4 +1,4 @@
from . import db_api, models from . import db_api, models, repositories
async def module_init(): async def module_init():

View File

@ -7,6 +7,7 @@ from .exceptions import MissingModuleName, NotExpectedModuleName
from .models.chat_stats import ChatStats from .models.chat_stats import ChatStats
from .models.chats import Chats from .models.chats import Chats
from .models.db import database_proxy from .models.db import database_proxy
from .models.fsm_data import FSMData
from .models.messages import Messages from .models.messages import Messages
from .models.user_stats import UserStats from .models.user_stats import UserStats
from .models.users import Users from .models.users import Users
@ -32,7 +33,7 @@ def connect_database(is_test: bool = False, module: str | None = None):
def create_tables(db: pw.SqliteDatabase): def create_tables(db: pw.SqliteDatabase):
"""Создание таблиц""" """Создание таблиц"""
for table in Chats, Messages, Users, UserStats, ChatStats: for table in Chats, Messages, Users, UserStats, ChatStats, FSMData:
if not table.table_exists(): if not table.table_exists():
db.create_tables([table]) db.create_tables([table])

View File

@ -0,0 +1 @@
from .fsm_data import FSMData

View File

@ -0,0 +1,12 @@
import peewee as pw
from .db import database_proxy
class FSMData(pw.Model):
class Meta:
database = database_proxy
key = pw.CharField(primary_key=True)
state = pw.CharField(null=True)
data = pw.CharField(null=True)

View File

@ -0,0 +1 @@
from .fsm_data import FSMDataRepository

View File

@ -0,0 +1,32 @@
from peewee import fn
from ..models import FSMData
class FSMDataRepository:
def get(self, key: str):
return FSMData.get_or_none(key=key)
def set_state(self, key: str, state: str):
FSMData.insert(
key=key,
state=state,
data=fn.COALESCE(
FSMData.select(FSMData.data).where(FSMData.key == key), None
),
).on_conflict(
conflict_target=[FSMData.key],
update={FSMData.state: state},
).execute()
def set_data(self, key: str, data: str):
FSMData.insert(
key=key,
data=data,
state=fn.COALESCE(
FSMData.select(FSMData.state).where(FSMData.key == key), None
),
).on_conflict(
conflict_target=[FSMData.key],
update={FSMData.data: data},
).execute()

View File

@ -0,0 +1 @@
from .fsm import module_init

View File

@ -0,0 +1,122 @@
import json
from typing import Any, Dict, Optional
from aiogram.fsm.state import State
from aiogram.fsm.storage.base import BaseStorage, StorageKey
from ocab_core.modules_system.public_api import get_module, log
from ocab_core.modules_system.public_api.public_api import set_fsm
FSMDataRepository = get_module("standard.database", "repositories.FSMDataRepository")
def serialize_key(key: StorageKey) -> str:
return f"{key.bot_id}:{key.chat_id}:{key.user_id}"
def serialize_object(obj: object) -> str | None:
try:
return json.dumps(obj)
except Exception as e:
log(f"Serializing error! {e}")
return None
def deserialize_object(obj):
try:
return json.loads(obj)
except Exception as e:
log(f"Deserializing error! {e}")
return None
class SQLStorage(BaseStorage):
def __init__(self):
super().__init__()
self.repo = FSMDataRepository()
async def set_state(self, key: StorageKey, state: State | None = None) -> None:
"""
Set state for specified key
:param key: storage key
:param state: new state
"""
s_key = serialize_key(key)
s_state = state.state if isinstance(state, State) else state
try:
self.repo.set_state(s_key, s_state)
except Exception as e:
log(f"FSM Storage database error: {e}")
async def get_state(self, key: StorageKey) -> Optional[str]:
"""
Get key state
:param key: storage key
:return: current state
"""
s_key = serialize_key(key)
try:
s_state = self.repo.get(s_key)
return s_state.state if s_state else None
except Exception as e:
log(f"FSM Storage database error: {e}")
return None
async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
"""
Write data (replace)
:param key: storage key
:param data: new data
"""
s_key = serialize_key(key)
s_data = serialize_object(data)
try:
self.repo.set_data(s_key, s_data)
except Exception as e:
log(f"FSM Storage database error: {e}")
async def get_data(self, key: StorageKey) -> Optional[Dict[str, Any]]:
"""
Get current data for key
:param key: storage key
:return: current data
"""
s_key = serialize_key(key)
try:
s_data = self.repo.get(s_key)
return deserialize_object(s_data.data) if s_data else None
except Exception as e:
log(f"FSM Storage database error: {e}")
return None
async def update_data(
self, key: StorageKey, data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Update data in the storage for key (like dict.update)
:param key: storage key
:param data: partial data
:return: new data
"""
current_data = await self.get_data(key=key)
if not current_data:
current_data = {}
current_data.update(data)
await self.set_data(key=key, data=current_data)
return current_data.copy()
async def close(self) -> None: # pragma: no cover
pass
async def module_init():
set_fsm(SQLStorage())

View File

@ -0,0 +1,11 @@
{
"id": "standard.fsm_database_storage",
"name": "FSM Database Storage",
"description": "Очень полезный модуль",
"author": "OCAB Team",
"version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.database": "^1.0.0"
}
}

View File

@ -32,7 +32,7 @@ async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int):
if user is None: if user is None:
await message.reply("Пользователь не найден") await message.reply("Пользователь не найден")
await log(f"Пользователь не найден: {user_id}, {user}") log(f"Пользователь не найден: {user_id}, {user}")
return return
roles = Roles() roles = Roles()
@ -69,7 +69,7 @@ async def get_user_info(message: Message, bot: Bot):
" попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение" " попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение"
) )
# print(e) # print(e)
await log(e) log(e)
async def get_chat_info(message: Message, bot: Bot): async def get_chat_info(message: Message, bot: Bot):