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

Merged with private/new-module-system

This commit is contained in:
Maxim Slipenko 2024-07-14 17:07:46 +03:00
parent 370b4fc648
commit eecc59ca94
65 changed files with 1282 additions and 178 deletions

View File

@ -21,6 +21,7 @@ packages = [{include = "scripts"}]
[tool.poetry.scripts] [tool.poetry.scripts]
test = 'scripts.test:main' test = 'scripts.test:main'
init = 'scripts.init:main' init = 'scripts.init:main'
module = 'scripts.module:main'
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.11.6,<3.13" python = ">=3.11.6,<3.13"

View File

@ -6,12 +6,12 @@ def main():
pwd = Path().cwd() pwd = Path().cwd()
dir_core = pwd / "src" / "ocab_core" dir_core = pwd / "src" / "ocab_core"
dir_modules_standard = pwd / "src" / "ocab_modules" / "standard" dir_modules_standard = pwd / "src" / "ocab_modules" / "standard"
dir_modules_custom = pwd / "src" / "ocab_modules" / "custom" dir_modules_external = pwd / "src" / "ocab_modules" / "external"
json = { json = {
"core": str(dir_core), "core": str(dir_core),
"modules standard": str(dir_modules_standard), "modules standard": str(dir_modules_standard),
"modules custom": str(dir_modules_custom), "modules external": str(dir_modules_external),
} }
with open("src/paths.json", "w", encoding="utf8") as f: with open("src/paths.json", "w", encoding="utf8") as f:
f.write(dumps(json, indent=4)) f.write(dumps(json, indent=4))

115
scripts/module.py Normal file
View File

@ -0,0 +1,115 @@
import argparse
import json
import os
DEFAULTS = {
"description": "Очень полезный модуль",
"author": "OCAB Team",
"version": "1.0.0",
"privileged": "false",
}
def create_module(args):
module_dir = os.path.join("src/ocab_modules/standard", args.module_name)
os.makedirs(module_dir, exist_ok=True)
module_info = {
"id": args.id,
"name": args.name,
"description": args.description,
"author": args.author,
"version": args.version,
"privileged": args.privileged.lower() == "true",
"dependencies": {},
}
with open(os.path.join(module_dir, "info.json"), "w", encoding="utf-8") as f:
json.dump(module_info, f, ensure_ascii=False, indent=4)
with open(os.path.join(module_dir, "__init__.py"), "w", encoding="utf-8") as f:
f.write("# Init file for the module\n")
print(f"Module {args.module_name} created successfully.")
def interactive_mode(args):
def get_input(prompt, default=None):
if default:
value = input(f"{prompt} [{default}]: ")
return value if value else default
else:
value = input(f"{prompt}: ")
return value
module_name = get_input("Введите название модуля (папки)")
module_id = get_input("Введите ID")
name = get_input("Введите название модуля")
description = get_input(
"Введите описание модуля", args.description or DEFAULTS["description"]
)
author = get_input("Введите автора", args.author or DEFAULTS["author"])
version = get_input("Введите версию", args.version or DEFAULTS["version"])
privileged = get_input(
"Модуль привилегированный (true/false)",
args.privileged or DEFAULTS["privileged"],
)
args = argparse.Namespace(
command="create",
module_name=module_name,
id=module_id,
name=name,
description=description,
author=author,
version=version,
privileged=privileged,
dependencies="",
)
create_module(args)
def main():
parser = argparse.ArgumentParser(
description="Утилита для создания директории модуля с файлами."
)
subparsers = parser.add_subparsers(dest="command", required=True)
create_parser = subparsers.add_parser("create", help="Создать новый модуль")
create_parser.add_argument("--module_name", help="Название директории модуля")
create_parser.add_argument("--id", help="ID модуля")
create_parser.add_argument("--name", help="Название модуля")
create_parser.add_argument("--description", help="Описание модуля")
create_parser.add_argument("--author", help="Автор модуля")
create_parser.add_argument("--version", help="Версия модуля")
create_parser.add_argument(
"--privileged", help="Привилегированный модуль (true/false)"
)
create_parser.add_argument(
"--dependencies", help="Список зависимостей в формате имя:версия через запятую"
)
args = parser.parse_args()
if args.command == "create":
if not all(
[
args.module_name,
args.id,
args.name,
args.description,
args.author,
args.version,
args.privileged,
args.dependencies,
]
):
print("Переход в интерактивный режим...")
interactive_mode(args)
else:
create_module(args)
if __name__ == "__main__":
main()

View File

@ -21,18 +21,9 @@ def setup_logger():
) )
async def log(message): def log(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)
else: else:
logging.info(message) logging.info(message)
def log_new(message):
logging.info(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,35 +14,47 @@ 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"),
FSLoader(f"{paths.modules_standard}/command_helper"),
FSLoader(f"{paths.modules_standard}/info"), FSLoader(f"{paths.modules_standard}/info"),
FSLoader(f"{paths.modules_standard}/filters"),
FSLoader(f"{paths.modules_external}/create_report_apps"),
FSLoader(f"{paths.modules_standard}/admin"),
FSLoader(f"{paths.modules_standard}/message_processing"),
] ]
async def main(): async def main():
bot = None bot = None
database = None
setup_logger() setup_logger()
app = Singleton() app = Singleton()
try: try:
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:
app.modules_manager.load(module_loader) info = module_loader.info()
log(f"Loading {info.name}({info.id}) module")
await app.modules_manager.load(module_loader)
await app.dp.start_polling(bot) 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.dp.start_polling(app.bot)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
finally: finally:
if bot is not None: if bot is not None:
await bot.session.close() await app.bot.session.close()
if database is not None:
database.close()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -11,7 +11,7 @@ class ModuleInfo:
name: str name: str
description: str description: str
version: str version: str
author: str author: str | list[str]
privileged: bool privileged: bool
dependencies: dict dependencies: dict

View File

@ -3,12 +3,12 @@ from pathlib import Path
from RestrictedPython import compile_restricted_exec from RestrictedPython import compile_restricted_exec
from ocab_core.modules_system.loaders.fs_loader.policy import ( from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
from ocab_core.modules_system.safe.policy import (
ALLOWED_IMPORTS, ALLOWED_IMPORTS,
BUILTINS, BUILTINS,
RestrictedPythonPolicy, RestrictedPythonPolicy,
) )
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
class FSLoader(UnsafeFSLoader): class FSLoader(UnsafeFSLoader):

View File

@ -49,9 +49,6 @@ class UnsafeFSLoader(AbstractLoader):
# Добавляем директорию модуля в sys.path # Добавляем директорию модуля в sys.path
sys.path.insert(0, str(path)) sys.path.insert(0, str(path))
print(full_path.parent.absolute())
print(module_name)
# Загружаем спецификацию модуля # Загружаем спецификацию модуля
spec = importlib.util.spec_from_file_location(module_name, full_path) spec = importlib.util.spec_from_file_location(module_name, full_path)

View File

@ -23,20 +23,25 @@ def is_version_compatible(version, requirement):
class ModulesManager: class ModulesManager:
def __init__(self): def __init__(self):
self.modules = {} self.modules = []
def load(self, loader: AbstractLoader): async def load(self, loader: AbstractLoader):
info = loader.info() info = loader.info()
if info.id in self.modules: # Check if the module is already loaded
if any(mod["info"].id == info.id for mod in self.modules):
return return
# Check dependencies
for dependency, version in info.dependencies.items(): for dependency, version in info.dependencies.items():
if dependency not in self.modules: loaded_dependency = next(
(mod for mod in self.modules if mod["info"].id == dependency), None
)
if not loaded_dependency:
raise Exception( raise Exception(
f"Module {info.id} depends on {dependency}, but it is not loaded" f"Module {info.id} depends on {dependency}, but it is not loaded"
) )
loaded_dependency_info = self.modules[dependency]["info"] loaded_dependency_info = loaded_dependency["info"]
if not is_version_compatible(loaded_dependency_info.version, version): if not is_version_compatible(loaded_dependency_info.version, version):
raise Exception( raise Exception(
f"Module {info.id} depends on {dependency}, " f"Module {info.id} depends on {dependency}, "
@ -45,22 +50,36 @@ class ModulesManager:
module = loader.load() module = loader.load()
self.modules[info.id] = { self.modules.append(
{
"info": info, "info": info,
"module": module, "module": module,
} }
)
if hasattr(module, "module_init"): if hasattr(module, "module_init"):
module.module_init() await module.module_init()
async def late_init(self):
for m in self.modules:
module = m["module"]
if hasattr(module, "module_late_init"):
await module.module_late_init()
def get_by_id(self, module_id: str): def get_by_id(self, module_id: str):
if module_id not in self.modules: module = next(
(mod for mod in self.modules if mod["info"].id == module_id), None
)
if not module:
raise Exception(f"Module with id {module_id} not loaded") raise Exception(f"Module with id {module_id} not loaded")
return self.modules[module_id]["module"] return module["module"]
def get_info_by_id(self, module_id: str): def get_info_by_id(self, module_id: str):
if module_id not in self.modules: module = next(
(mod for mod in self.modules if mod["info"].id == module_id), None
)
if not module:
raise Exception(f"Module with id {module_id} not loaded") raise Exception(f"Module with id {module_id} not loaded")
return self.modules[module_id]["info"] return module["info"]

View File

@ -1,3 +1,10 @@
from ocab_core.logger import log # noqa from .public_api import (
Storage,
from .public_api import Storage, get_module, register_router get_fsm_context,
get_module,
log,
register_outer_message_middleware,
register_router,
set_my_commands,
)
from .utils import Utils

View File

@ -1,14 +1,47 @@
import types import types
from typing import Any, Tuple, Union from typing import Any, Tuple, Union
from aiogram import Router from aiogram import BaseMiddleware, Router
from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.base import StorageKey
from ocab_core.logger import log
from ocab_core.singleton import Singleton from ocab_core.singleton import Singleton
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):
app = Singleton()
app.storage["_outer_message_middlewares"].append(middleware)
async def set_my_commands(commands):
app = Singleton()
await app.bot.set_my_commands(commands)
async def get_fsm_context(chat_id: int, user_id: int) -> FSMContext:
dp = Singleton().dp
bot = Singleton().bot
return FSMContext(
storage=dp.storage,
key=StorageKey(
chat_id=chat_id,
user_id=user_id,
bot_id=bot.id,
),
)
def set_fsm(storage):
app = Singleton()
log(storage)
app.storage["_fsm_storage"] = storage
def get_module( def get_module(

View File

@ -0,0 +1,12 @@
import re
CLEAN_HTML = re.compile("<.*?>")
class Utils:
@staticmethod
def code_format(code: str, lang: str):
if lang:
return f'<pre><code class="language-{lang}">{code}</code></pre>'
else:
return f"<pre>{code}</pre>"

View File

@ -1,13 +1,22 @@
from _ast import AnnAssign from _ast import AnnAssign
from typing import Any from typing import Any
from aiogram import Bot
from RestrictedPython import ( from RestrictedPython import (
RestrictingNodeTransformer, RestrictingNodeTransformer,
limited_builtins, limited_builtins,
safe_builtins, safe_builtins,
utility_builtins, utility_builtins,
) )
from RestrictedPython.Eval import default_guarded_getitem from RestrictedPython.Eval import default_guarded_getitem, default_guarded_getiter
from RestrictedPython.Guards import (
full_write_guard,
guarded_unpack_sequence,
safer_getattr,
)
from ocab_core.logger import log
from ocab_core.modules_system.safe.zope_guards import extra_safe_builtins
class RestrictedPythonPolicy(RestrictingNodeTransformer): class RestrictedPythonPolicy(RestrictingNodeTransformer):
@ -77,22 +86,24 @@ ALLOWED_IMPORTS = [
"warnings", "warnings",
] ]
def safes_getattr(object, name, default=None, getattr=safer_getattr):
if isinstance(object, Bot) and name == "token":
log("Bot.token is not allowed")
raise Exception("Bot.token is not allowed")
return getattr(object, name, default)
BUILTINS = safe_builtins.copy() BUILTINS = safe_builtins.copy()
BUILTINS.update(utility_builtins) BUILTINS.update(utility_builtins)
BUILTINS.update(limited_builtins) BUILTINS.update(limited_builtins)
BUILTINS.update(extra_safe_builtins)
BUILTINS["__metaclass__"] = _metaclass BUILTINS["__metaclass__"] = _metaclass
BUILTINS["_getitem_"] = default_guarded_getitem BUILTINS["_getitem_"] = default_guarded_getitem
# BUILTINS["_write_"] = full_write_guard BUILTINS["_getattr_"] = safes_getattr
BUILTINS["_getiter_"] = default_guarded_getiter
BUILTINS["_write_"] = full_write_guard
BUILTINS["_unpack_sequence_"] = guarded_unpack_sequence
BUILTINS["staticmethod"] = staticmethod BUILTINS["staticmethod"] = staticmethod
BUILTINS["tuple"] = tuple
class GuardedDictType:
def __call__(self, *args, **kwargs):
return dict(*args, **kwargs)
@staticmethod
def fromkeys(iterable, value=None):
return dict.fromkeys(iterable, value)
BUILTINS["dict"] = GuardedDictType()

View File

@ -0,0 +1,225 @@
#############################################################################
#
# Copyright (c) 2024 OCAB Team
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software includes a function derived from the software subject to the
# provisions of the Zope Public License, Version 2.1 (ZPL). A copy of the ZPL
# should accompany this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
# AND ALL EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST
# INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
#
#
##############################################################################
extra_safe_builtins = {}
class GuardedDictType:
def __call__(self, *args, **kwargs):
return dict(*args, **kwargs)
def fromkeys(self, S, v=None):
return dict.fromkeys(S, v)
extra_safe_builtins["dict"] = GuardedDictType()
ContainerAssertions = {
type(()): 1,
bytes: 1,
str: 1,
range: 1,
}
Containers = ContainerAssertions.get
def _error(index):
raise Exception("unauthorized access to element")
def guard(container, value, index=None):
# if Containers(type(container)) and Containers(type(value)):
# # Simple type. Short circuit.
# return
# I don't know how to do this.
# if getSecurityManager().validate(container, container, index, value):
# return
# _error(index)
return
class SafeIter:
__allow_access_to_unprotected_subobjects__ = 1
def __init__(self, ob, container=None):
self._iter = iter(ob)
if container is None:
container = ob
self.container = container
def __iter__(self):
return self
def __next__(self):
ob = next(self._iter)
guard(self.container, ob)
return ob
next = __next__
class NullIter(SafeIter):
def __init__(self, ob):
self._iter = ob
def __next__(self):
return next(self._iter)
next = __next__
def guarded_iter(*args):
if len(args) == 1:
i = args[0]
# Don't double-wrap
if isinstance(i, SafeIter):
return i
if not isinstance(i, range):
return SafeIter(i)
# Other call styles / targets don't need to be guarded
return NullIter(iter(*args))
extra_safe_builtins["iter"] = guarded_iter
def guarded_any(seq):
return any(guarded_iter(seq))
extra_safe_builtins["any"] = guarded_any
def guarded_all(seq):
return all(guarded_iter(seq))
extra_safe_builtins["all"] = guarded_all
valid_inplace_types = (list, set)
inplace_slots = {
"+=": "__iadd__",
"-=": "__isub__",
"*=": "__imul__",
"/=": (1 / 2 == 0) and "__idiv__" or "__itruediv__",
"//=": "__ifloordiv__",
"%=": "__imod__",
"**=": "__ipow__",
"<<=": "__ilshift__",
">>=": "__irshift__",
"&=": "__iand__",
"^=": "__ixor__",
"|=": "__ior__",
}
def __iadd__(x, y):
x += y
return x
def __isub__(x, y):
x -= y
return x
def __imul__(x, y):
x *= y
return x
def __idiv__(x, y):
x /= y
return x
def __ifloordiv__(x, y):
x //= y
return x
def __imod__(x, y):
x %= y
return x
def __ipow__(x, y):
x **= y
return x
def __ilshift__(x, y):
x <<= y
return x
def __irshift__(x, y):
x >>= y
return x
def __iand__(x, y):
x &= y
return x
def __ixor__(x, y):
x ^= y
return x
def __ior__(x, y):
x |= y
return x
inplace_ops = {
"+=": __iadd__,
"-=": __isub__,
"*=": __imul__,
"/=": __idiv__,
"//=": __ifloordiv__,
"%=": __imod__,
"**=": __ipow__,
"<<=": __ilshift__,
">>=": __irshift__,
"&=": __iand__,
"^=": __ixor__,
"|=": __ior__,
}
def protected_inplacevar(op, var, expr):
"""Do an inplace operation
If the var has an inplace slot, then disallow the operation
unless the var an instance of ``valid_inplace_types``.
"""
if hasattr(var, inplace_slots[op]) and not isinstance(var, valid_inplace_types):
try:
cls = var.__class__
except AttributeError:
cls = type(var)
raise TypeError(
"Augmented assignment to %s objects is not allowed"
" in untrusted code" % cls.__name__
)
return inplace_ops[op](var, expr)
extra_safe_builtins["_inplacevar_"] = protected_inplacevar

View File

@ -1,18 +0,0 @@
from aiogram import Dispatcher
from src.ocab_modules.standard.admin.routers import router as admin_router
# from src.modules.standard.info.routers import router as info_router
from src.ocab_modules.standard.message_processing.message_api import (
router as process_message,
)
async def include_routers(dp: Dispatcher):
"""
Подключение роутеров в бота
dp.include_router()
"""
# dp.include_router(info_router)
dp.include_router(admin_router)
dp.include_router(process_message)

View File

@ -1,4 +1,5 @@
from aiogram import 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
@ -14,6 +15,11 @@ class SingletonMeta(type):
class Singleton(metaclass=SingletonMeta): class Singleton(metaclass=SingletonMeta):
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

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

View File

@ -0,0 +1,136 @@
from aiogram import Bot, Router
from aiogram.enums import ParseMode
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import (
BufferedInputFile,
KeyboardButton,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from ocab_core.modules_system.public_api import Utils, get_fsm_context
from .report import Report
router = Router()
class ReportState(StatesGroup):
input_system_info = State()
input_app_name = State()
input_problem_step_by_step = State()
input_actual_result = State()
input_expected_result = State()
input_additional_info = State()
system_info_code = """echo "SESSION_TYPE: ${XDG_SESSION_TYPE:-Unknown}"
[ -f /etc/os-release ] && grep "^PRETTY_NAME=" /etc/os-release | cut -d= -f2 \
| tr -d '"' | xargs echo "OS: "
echo "Kernel: $(uname -r)"
echo "DE: ${XDG_CURRENT_DESKTOP:-Unknown}"
grep "^model name" /proc/cpuinfo | head -n1 | cut -d: -f2 \
| xargs echo "CPU: "
lspci | grep "VGA compatible controller" | cut -d: -f3 \
| xargs -I{} echo "GPU: {}"
"""
system_info_message = """Укажите параметры свой системы.
Собрать информацию о системе можно с помощью данного скрипта:
""" + Utils.code_format(
system_info_code,
"shell",
)
async def start_report(chat_id: int, bot: Bot):
await bot.send_message(
chat_id=chat_id,
text=system_info_message,
parse_mode=ParseMode.HTML,
reply_markup=ReplyKeyboardRemove(),
)
state = await get_fsm_context(chat_id, chat_id)
await state.set_state(ReportState.input_system_info)
app_info_message = """Укажите название и версию приложения.
Узнать можно с помощью данной команды:""" + Utils.code_format(
"rpm -qa | grep -i НАЗВАНИЕРИЛОЖЕНИЯ", "shell"
)
@router.message(ReportState.input_system_info)
async def system_entered(message: Message, state: FSMContext):
await state.update_data(system=message.text)
await message.answer(
text=app_info_message,
parse_mode=ParseMode.HTML,
)
await state.set_state(ReportState.input_app_name)
step_by_step_message = (
"""Опиши проблему пошагово, что ты делал, что происходило, что не так."""
)
@router.message(ReportState.input_app_name)
async def app_name_entered(message: Message, state: FSMContext):
await state.update_data(app=message.text)
await message.answer(text=step_by_step_message)
await state.set_state(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):
await state.update_data(problem_step_by_step=message.text)
await message.answer(text="Опиши, что произошло (фактический результат).")
await state.set_state(ReportState.input_actual_result)
@router.message(ReportState.input_actual_result)
async def actual_result_entered(message: Message, state: FSMContext):
await state.update_data(actual=message.text)
await message.answer(text="Опиши ожидаемый результат.")
await state.set_state(ReportState.input_expected_result)
@router.message(ReportState.input_expected_result)
async def expected_result_entered(message: Message, state: FSMContext):
await state.update_data(expected=message.text)
await message.answer(
text="Если есть дополнительная информация, то напиши ее.",
reply_markup=ReplyKeyboardMarkup(
resize_keyboard=True,
keyboard=[
[KeyboardButton(text="Дополнительной информации нет")],
],
),
)
await state.set_state(ReportState.input_additional_info)
@router.message(ReportState.input_additional_info)
async def additional_info_entered(message: Message, state: FSMContext):
if message.text == "Дополнительной информации нет":
additional_info = ""
else:
additional_info = message.text
await state.update_data(additional=additional_info)
await message.answer(
text="Вот твой отчет сообщением, а также файлом:",
reply_markup=ReplyKeyboardRemove(),
)
data = await state.get_data()
report = Report(data)
file_report = report.export().encode()
await message.answer(text=report.export())
await message.answer_document(document=BufferedInputFile(file_report, "report.txt"))
await state.clear()

View File

@ -0,0 +1,14 @@
{
"id": "external.create_report_apps",
"name": "Create Report Apps",
"description": "Модуль для создания отчетов о ошибках в приложениях",
"author": [
"OCAB Team",
"Maxim Slipenko"
],
"version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.command_helper": "^1.0.0"
}
}

View File

@ -0,0 +1,115 @@
from typing import Union
from aiogram import Bot, F, Router
from aiogram.exceptions import TelegramForbiddenError
from aiogram.filters import BaseFilter, Command, CommandStart
from aiogram.types import (
CallbackQuery,
InlineKeyboardButton,
InlineKeyboardMarkup,
Message,
)
from ocab_core.modules_system.public_api import get_module, register_router
from .create_report import router as create_report_router
from .create_report import start_report
register_command = get_module("standard.command_helper", "register_command")
router = Router()
class ChatTypeFilter(BaseFilter):
def __init__(self, chat_type: Union[str, list]):
self.chat_type = chat_type
async def __call__(self, message: Message) -> bool:
if isinstance(self.chat_type, str):
return message.chat.type == self.chat_type
return message.chat.type in self.chat_type
@router.message(
ChatTypeFilter(chat_type=["group", "supergroup"]), Command("create_report_apps")
)
async def create_report_apps_command_group(message: Message):
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="Да", callback_data=f"create_report:{message.from_user.id}"
),
InlineKeyboardButton(
text="Нет", callback_data=f"cancel_report:{message.from_user.id}"
),
]
]
)
await message.answer(
"Я могу отправить тебе пару вопросов "
"для помощи в составлении репорта личными "
"сообщениями.",
reply_markup=keyboard,
)
@router.message(
ChatTypeFilter(chat_type=["private"]),
CommandStart(deep_link=True, magic=F.args == "create_report_apps"),
)
@router.message(ChatTypeFilter(chat_type=["private"]), Command("create_report_apps"))
async def create_report_apps_command(message: Message, bot: Bot):
await start_report(message.from_user.id, bot)
@router.callback_query(F.data.startswith("cancel_report"))
async def cancel_report_callback(callback_query: CallbackQuery):
callback_user_id = int(callback_query.data.split(":")[1])
if callback_query.from_user.id != callback_user_id:
await callback_query.answer("Эта кнопка не для вас.", show_alert=True)
return
await callback_query.message.delete()
@router.callback_query(F.data.startswith("create_report"))
async def create_report_callback(callback_query: CallbackQuery, bot: Bot):
callback_user_id = int(callback_query.data.split(":")[1])
if callback_query.from_user.id != callback_user_id:
await callback_query.answer("Эта кнопка не для вас.", show_alert=True)
return
user_id = callback_query.from_user.id
async def on_chat_unavailable():
await callback_query.message.edit_text(
"Я в личных сообщениях задам тебе вопросы "
"для помощи в составлении репорта. "
'Но перед этим ты должен нажать кнопку "Запустить"'
)
info = await bot.get_me()
await callback_query.answer(
url=f"https://t.me/{info.username}?start=create_report_apps"
)
try:
chat_member = await bot.get_chat_member(chat_id=user_id, user_id=user_id)
if chat_member.status != "left":
await start_report(user_id, bot)
await callback_query.message.edit_text(
"Я в личных сообщениях задам тебе "
"вопросы для помощи в составлении "
"репорта."
)
else:
await on_chat_unavailable()
except TelegramForbiddenError:
await on_chat_unavailable()
async def module_init():
router.include_router(create_report_router)
register_router(router)
register_command("create_report_apps", "Написать репорт о приложении")

View File

@ -0,0 +1,59 @@
import aiogram
class ReportFormatter:
def __init__(self, html=True):
self.html = html
def bold(self, string):
if self.html:
return f"<b>{self.text(string)}</b>"
return self.text(string)
def text(self, string):
if self.html:
return aiogram.html.quote(string)
return string
class Report:
def __init__(self, data: dict):
self.data = data
def export(self):
data = self.data
report = f"""
Стенд с ошибкой:
==============================
{data['system']}
Пакет:
==============================
{data['app']}
Шаги, приводящие к ошибке:
==============================
{data['problem_step_by_step']}
Фактический результат:
==============================
{data['actual']}
Ожидаемый результат:
==============================
{data['expected']}
"""
if data["additional"] != "":
report += f"""
Дополнительно:
==============================
{data['additional']}
"""
return report

View File

@ -0,0 +1 @@
from .handlers import answer_to_message

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,6 +1,9 @@
{ {
"name": "YandexGPT", "id": "external.yandexgpt",
"name": "Yandex GPT",
"description": "Модуль для работы с Yandex GPT", "description": "Модуль для работы с Yandex GPT",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0.0",
"privileged": true,
"dependencies": {}
} }

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

@ -0,0 +1 @@
from .moderation import ban_user, unmute_user

View File

@ -6,8 +6,8 @@ import time
import aiogram import aiogram
import aiohttp import aiohttp
from ...standard.config.config import * from ocab_modules.standard.config.config import *
from ...standard.roles.roles import * from ocab_modules.standard.roles.roles import *
class Moderation: class Moderation:

View File

@ -5,18 +5,12 @@ import random
from threading import Thread from threading import Thread
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message
from aiogram.types import inline_keyboard_button as types from aiogram.types import inline_keyboard_button as types
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder
from ocab_modules.legacy.moderation import ban_user, unmute_user
from src.ocab_modules.standard.config.config import get_telegram_check_bot from src.ocab_modules.standard.config.config import get_telegram_check_bot
from src.ocab_modules.standard.database.db_api import * from src.ocab_modules.standard.database.db_api import *
from src.ocab_modules.standard.moderation.moderation import (
ban_user,
mute_user,
unmute_user,
)
from src.ocab_modules.standard.roles.roles import Roles
async def create_math_task(): async def create_math_task():

View File

@ -1,6 +1,6 @@
from aiogram import F, Router from aiogram import F, Router
from src.ocab_modules.standard.welcome.handlers import check_new_user from .handlers import check_new_user
router = Router() router = Router()

View File

@ -1 +1 @@
from . import routers from .main import module_init

View File

@ -1,10 +1,10 @@
# flake8: noqa # flake8: noqa
import time
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message from aiogram.types import Message
from src.ocab_modules.standard.config.config import get_default_chat_tag from ocab_core.modules_system.public_api import get_module
get_default_chat_tag = get_module("standard.config", "get_default_chat_tag")
async def delete_message(message: Message, bot: Bot): async def delete_message(message: Message, bot: Bot):

View File

@ -1,6 +1,11 @@
{ {
"id": "standard.admin",
"name": "Admin", "name": "Admin",
"description": "Модуль для работы с админкой", "description": "Модуль для работы с админкой",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.filters": "^1.0.0"
}
} }

View File

@ -0,0 +1,7 @@
from ocab_core.modules_system.public_api import register_router
from .routers import router
async def module_init():
register_router(router)

View File

@ -1,15 +1,18 @@
# flake8: noqa # flake8: noqa
from aiogram import F, Router from aiogram import F, Router
from aiogram.filters import Command
from src.ocab_modules.standard.admin.handlers import ( from ocab_core.modules_system.public_api import get_module, log
from .handlers import (
chat_not_in_approve_list, chat_not_in_approve_list,
delete_message, delete_message,
error_access, error_access,
get_chat_id, get_chat_id,
) )
from src.ocab_modules.standard.filters.filters import (
ChatModerOrAdminFilter, (ChatModerOrAdminFilter, ChatNotInApproveFilter) = get_module(
ChatNotInApproveFilter, "standard.filters", ["ChatModerOrAdminFilter", "ChatNotInApproveFilter"]
) )
router = Router() router = Router()
@ -17,8 +20,8 @@ router = Router()
# Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list # Если сообщение содержит какой либо текст и выполняется фильтр ChatNotInApproveFilter, то вызывается функция chat_not_in_approve_list
router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text) router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text)
router.message.register(get_chat_id, ChatModerOrAdminFilter(), F.text == "/chatID") router.message.register(get_chat_id, ChatModerOrAdminFilter(), Command("chatID"))
router.message.register(delete_message, ChatModerOrAdminFilter(), F.text == "/rm") router.message.register(delete_message, ChatModerOrAdminFilter(), Command("rm"))
router.message.register(error_access, F.text == "/rm") router.message.register(error_access, Command("rm"))
router.message.register(error_access, F.text == "/chatID") router.message.register(error_access, Command("chatID"))

View File

@ -0,0 +1 @@
from .main import module_init, register_command

View File

@ -0,0 +1,12 @@
{
"id": "standard.command_helper",
"name": "Command helper",
"description": "Модуль для отображения команд при вводе '/'",
"author": "OCAB Team",
"version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.roles": "^1.0.0",
"standard.database": "^1.0.0"
}
}

View File

@ -0,0 +1,96 @@
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import BotCommand, TelegramObject
from ocab_core.modules_system.public_api import (
get_module,
register_outer_message_middleware,
set_my_commands,
)
commands = dict()
db_api = get_module(
"standard.database",
"db_api",
)
Roles = get_module("standard.roles", "Roles")
def register_command(command, description, role="USER"):
if role not in commands:
commands[role] = dict()
commands[role][command] = {
"description": description,
}
class OuterMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
):
# if not isinstance(event, Message):
# return await handler(event, data)
#
# user = db_api.get_user(event.from_user.id)
#
# if user is None:
# return
#
# roles = Roles()
# role_name = await roles.get_role_name(role_id=user.user_role)
#
# if role_name not in commands:
# return await handler(event, data)
# bot_commands = []
# for role_command in commands[role_name]:
# bot_commands.append(
# BotCommand(
# command=role_command,
# description=commands[role_name][role_command]["description"],
# )
# )
# await event.bot.set_my_commands(
# bot_commands,
# BotCommandScopeChatMember(
# chat_id=event.chat.id,
# user_id=event.from_user.id,
# ),
# )
return await handler(event, data)
async def module_init():
register_outer_message_middleware(OuterMiddleware())
async def set_user_commands():
bot_commands = []
if "USER" in commands:
user_commands = commands["USER"]
for command in user_commands:
bot_commands.append(
BotCommand(
command=command,
description=user_commands[command]["description"],
)
)
await set_my_commands(
bot_commands,
)
async def module_late_init():
await set_user_commands()

View File

@ -1 +1,7 @@
from .config import config from .config import (
get_approved_chat_id,
get_default_chat_tag,
get_roles,
get_yandexgpt_in_words,
get_yandexgpt_start_words,
)

View File

@ -27,13 +27,17 @@ def get_telegram_check_bot() -> bool:
return config["TELEGRAM"]["CHECK_BOT"] return config["TELEGRAM"]["CHECK_BOT"]
def get_aproved_chat_id() -> list: def get_approved_chat_id() -> list:
# Возваращем сплитованный список id чатов в формате int # Возваращем сплитованный список id чатов в формате int
return [ return [
int(chat_id) for chat_id in config["TELEGRAM"]["APPROVED_CHAT_ID"].split(" | ") int(chat_id) for chat_id in config["TELEGRAM"]["APPROVED_CHAT_ID"].split(" | ")
] ]
def get_roles():
return config["ROLES"]
def get_user_role_name(role_number) -> dict: def get_user_role_name(role_number) -> dict:
# Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно # Возвращаем название роли пользвателя по номеру роли, если такой роли нет, возвращаем неизвестно
return config["ROLES"].get(role_number, "Неизвестно") return config["ROLES"].get(role_number, "Неизвестно")

View File

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

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 .filters import ChatModerOrAdminFilter, ChatNotInApproveFilter

View File

@ -2,9 +2,10 @@ from aiogram import Bot
from aiogram.filters import BaseFilter from aiogram.filters import BaseFilter
from aiogram.types import Message from aiogram.types import Message
from ocab_core.logger import log from ocab_core.modules_system.public_api import get_module, log
from ocab_modules.standard.config.config import get_aproved_chat_id
from ocab_modules.standard.roles.roles import Roles get_approved_chat_id = get_module("standard.config", "get_approved_chat_id")
Roles = get_module("standard.roles", "Roles")
class ChatModerOrAdminFilter(BaseFilter): class ChatModerOrAdminFilter(BaseFilter):
@ -21,14 +22,11 @@ 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:
# print("chat_check") log("chat_check")
await log("chat_check")
chat_id = message.chat.id chat_id = message.chat.id
if chat_id in get_aproved_chat_id(): if chat_id in get_approved_chat_id():
# print(f"Chat in approve list: {chat_id}") log(f"Chat in approve list: {chat_id}")
await log(f"Chat in approve list: {chat_id}")
return False return False
else: else:
# print(f"Chat not in approve list: {chat_id}") log(f"Chat not in approve list: {chat_id}")
await log(f"Chat not in approve list: {chat_id}")
return True return True

View File

@ -1,6 +1,12 @@
{ {
"id": "standard.filters",
"name": "Filters", "name": "Filters",
"description": "Модуль с фильтрами", "description": "Модуль с фильтрами",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0" "version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.roles": "^1.0.0",
"standard.config": "^1.0.0"
}
} }

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

@ -1,14 +1,2 @@
from aiogram import F, Router
from ocab_core.modules_system.public_api import register_router
from .handlers import get_chat_info, get_user_info from .handlers import get_chat_info, get_user_info
from .main import module_init
def module_init():
router = Router()
router.message.register(get_user_info, F.text.startswith("/info"))
router.message.register(get_chat_info, F.text.startswith("/chatinfo"))
register_router(router)

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):

View File

@ -7,6 +7,7 @@
"privileged": false, "privileged": false,
"dependencies": { "dependencies": {
"standard.roles": "^1.0.0", "standard.roles": "^1.0.0",
"standard.database": "^1.0.0" "standard.database": "^1.0.0",
"standard.command_helper": "^1.0.0"
} }
} }

View File

@ -0,0 +1,20 @@
from aiogram import Router
from aiogram.filters import Command
from ocab_core.modules_system.public_api import get_module, register_router
from .handlers import get_chat_info, get_user_info
register_command = get_module("standard.command_helper", "register_command")
async def module_init():
router = Router()
router.message.register(get_user_info, Command("info"))
router.message.register(get_chat_info, Command("chatinfo"))
register_router(router)
register_command("info", "Информация о пользователе")
register_command("chatinfo", "Информация о чате")

View File

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

View File

@ -0,0 +1,13 @@
{
"id": "standard.message_processing",
"name": "Info",
"description": "Модуль с информацией",
"author": "OCAB Team",
"version": "1.0.0",
"privileged": false,
"dependencies": {
"standard.roles": "^1.0.0",
"standard.database": "^1.0.0",
"standard.command_helper": "^1.0.0"
}
}

View File

@ -2,14 +2,45 @@
from aiogram import Bot, F, Router, types from aiogram import Bot, F, Router, types
from ocab_core.logger import log from ocab_core.modules_system.public_api import get_module, log, register_router
from ocab_modules.external.yandexgpt.handlers import answer_to_message
from ocab_modules.standard.config.config import ( # from ocab_modules.standard.database.db_api import *
get_aproved_chat_id,
get_yandexgpt_in_words, (get_approved_chat_id, get_yandexgpt_in_words, get_yandexgpt_start_words) = get_module(
get_yandexgpt_start_words, "standard.config",
["get_approved_chat_id", "get_yandexgpt_in_words", "get_yandexgpt_start_words"],
)
answer_to_message = get_module("external.yandexgpt", "answer_to_message")
(
get_chat,
add_chat,
get_user,
add_user,
get_user_name,
change_user_name,
get_user_tag,
change_user_tag,
update_chat_all_stat,
update_user_all_stat,
add_message,
) = get_module(
"standard.database",
[
"db_api.get_chat",
"db_api.add_chat",
"db_api.get_user",
"db_api.add_user",
"db_api.get_user_name",
"db_api.change_user_name",
"db_api.get_user_tag",
"db_api.change_user_tag",
"db_api.update_chat_all_stat",
"db_api.update_user_all_stat",
"db_api.add_message",
],
) )
from ocab_modules.standard.database.db_api import *
async def chat_check(message: types.Message): async def chat_check(message: types.Message):
@ -17,17 +48,15 @@ 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_aproved_chat_id(): if message.chat.id in get_approved_chat_id():
# print(f"Chat in approve list: {message.chat.id} {message.chat.title}") # print(f"Chat in approve list: {message.chat.id} {message.chat.title}")
await 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)
# print(f"Chat added: {message.chat.id} {message.chat.title}") # print(f"Chat added: {message.chat.id} {message.chat.title}")
await log(f"Chat added: {message.chat.id} {message.chat.title}") log(f"Chat added: {message.chat.id} {message.chat.title}")
else: else:
# print(f"Chat not in approve list: {message.chat.id} {message.chat.title}") # print(f"Chat not in approve list: {message.chat.id} {message.chat.title}")
await log( log(f"Chat not in approve list: {message.chat.id} {message.chat.title}")
f"Chat not in approve list: {message.chat.id} {message.chat.title}"
)
pass pass
else: else:
# Проверяем обновление названия чата # Проверяем обновление названия чата
@ -36,10 +65,10 @@ async def chat_check(message: types.Message):
chat.chat_name = message.chat.title chat.chat_name = message.chat.title
chat.save() chat.save()
# print(f"Chat updated: {message.chat.id} {message.chat.title}") # print(f"Chat updated: {message.chat.id} {message.chat.title}")
await log(f"Chat updated: {message.chat.id} {message.chat.title}") log(f"Chat updated: {message.chat.id} {message.chat.title}")
else: else:
# print(f"Chat already exists: {message.chat.id} {message.chat.title}") # print(f"Chat already exists: {message.chat.id} {message.chat.title}")
await log(f"Chat already exists: {message.chat.id} {message.chat.title}") log(f"Chat already exists: {message.chat.id} {message.chat.title}")
pass pass
@ -62,23 +91,19 @@ async def user_check(message: types.Message):
message.from_user.last_name, message.from_user.last_name,
message.from_user.username, message.from_user.username,
) )
# print(f"User added: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name}") log(f"User added: {message.from_user.id} {current_user_name}")
await log(f"User added: {message.from_user.id} {current_user_name}")
else: else:
# print(f"User already exists: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name} {message.from_user.username}") log(
await log(
f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}" f"User already exists: {message.from_user.id} {current_user_name} {message.from_user.username}"
) )
# Проверяем обновление имени пользователя # Проверяем обновление имени пользователя
if get_user_name(message.from_user.id) != current_user_name: if get_user_name(message.from_user.id) != current_user_name:
change_user_name(message.from_user.id, current_user_name) change_user_name(message.from_user.id, current_user_name)
# print(f"User updated: {message.from_user.id} {message.from_user.first_name} {message.from_user.last_name}") log(f"User name updated: {message.from_user.id} {current_user_name}")
await log(f"User name updated: {message.from_user.id} {current_user_name}")
# Проверяем обновление username пользователя # Проверяем обновление username пользователя
if get_user_tag(message.from_user.id) != message.from_user.username: if get_user_tag(message.from_user.id) != message.from_user.username:
change_user_tag(message.from_user.id, message.from_user.username) change_user_tag(message.from_user.id, message.from_user.username)
# print(f"User updated: {message.from_user.id} {message.from_user.username}") log(
await log(
f"User tag updated: {message.from_user.id} {message.from_user.username}" f"User tag updated: {message.from_user.id} {message.from_user.username}"
) )
pass pass
@ -101,17 +126,19 @@ async def message_processing(message: types.Message, bot: Bot):
if (message.text.split(" ")[0] in get_yandexgpt_start_words()) or ( if (message.text.split(" ")[0] in get_yandexgpt_start_words()) or (
any(word in message.text for word in get_yandexgpt_in_words()) any(word in message.text for word in get_yandexgpt_in_words())
): ):
# print("message_processing") log("message_processing")
await log("message_processing")
await answer_to_message(message, bot) await answer_to_message(message, bot)
elif message.reply_to_message is not None: elif message.reply_to_message is not None:
if message.reply_to_message.from_user.is_bot: if message.reply_to_message.from_user.is_bot:
# print("message_processing") log("message_processing")
await log("message_processing")
await answer_to_message(message, bot) await answer_to_message(message, bot)
router = Router() router = Router()
# Если сообщение содержит текст то вызывается функция message_processing # Если сообщение содержит текст то вызывается функция message_processing
router.message.register(message_processing, F.text) router.message.register(message_processing, F.text)
async def module_init():
register_router(router)

View File

@ -1,7 +1,9 @@
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")
config: dict = get_module("standard.config", "config") get_roles = get_module("standard.config", "get_roles")
roles = get_roles()
class Roles: class Roles:
@ -9,13 +11,12 @@ class Roles:
moderator = "MODERATOR" moderator = "MODERATOR"
admin = "ADMIN" admin = "ADMIN"
bot = "BOT" bot = "BOT"
__roles = config["ROLES"]
def __init__(self): def __init__(self):
self.user_role_id = self.__roles[self.user] self.user_role_id = roles[self.user]
self.moderator_role_id = self.__roles[self.moderator] self.moderator_role_id = roles[self.moderator]
self.admin_role_id = self.__roles[self.admin] self.admin_role_id = roles[self.admin]
self.bot_role_id = self.__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):
match get_user_role(user_id): match get_user_role(user_id):

View File

@ -7,7 +7,7 @@ from json import loads
class Path: class Path:
core: str core: str
modules_standard: str modules_standard: str
modules_custom: str modules_external: str
def _get_paths(path_to_json: str): def _get_paths(path_to_json: str):
@ -16,7 +16,7 @@ def _get_paths(path_to_json: str):
return Path( return Path(
core=paths["core"], core=paths["core"],
modules_standard=paths["modules standard"], modules_standard=paths["modules standard"],
modules_custom=paths["modules custom"], modules_external=paths["modules external"],
) )