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

подготовка к публикации

This commit is contained in:
Maxim Slipenko 2024-07-28 18:48:25 +03:00
parent bfa1d13931
commit d5f6f1bb4f
121 changed files with 4093 additions and 2463 deletions

4
.gitignore vendored
View File

@ -6,6 +6,4 @@ env
venv
__pycache__
OCAB.db
src/paths.json
src/ocab_core/config.yaml
src/ocab_core/log/**/*
config.yaml

2221
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[tool.poetry]
name = "ocab"
name = "ocab-monorepo"
version = "2.0.0"
description = "OCAB is a modular Telegram bot"
license = "GPL-3.0-only"
@ -13,9 +13,7 @@ maintainers = [
readme = "README.md"
repository = "https://gitflic.ru/project/armatik/ocab"
packages = [
{ include = "scripts" },
{ include = "ocab_core", from = "src" },
{ include = "ocab_modules", from = "src" }
{ include = "scripts" }
]
[tool.poetry.urls]
@ -27,21 +25,7 @@ init = 'scripts.init:main'
module = 'scripts.module:main'
[tool.poetry.dependencies]
python = ">=3.11.6,<3.13"
aiogram = "^3.10.0"
peewee = "^3.17.6"
pyyaml = "^6.0.1"
requests = "^2.32.3"
restrictedpython = "^7.1"
dataclasses-json = "^0.6.7"
semver = "^3.0.2"
hypercorn = "^0.17.3"
flet = "^0.23.2"
fastapi = "^0.111.1"
setuptools = "^71.0.1"
dash = "^2.17.1"
dash-extensions = "^1.0.18"
dash-bootstrap-components = "^1.6.0"
python = "~3.12"
[tool.poetry.group.dev.dependencies]
flake8 = "^7.1.0"

1
src/gnomik/README.md Normal file
View File

@ -0,0 +1 @@
# Гномик

View File

@ -0,0 +1,29 @@
import asyncio
from ocab_core import OCAB
from ocab_modules import module_loader
async def main():
ocab = OCAB()
await ocab.init_app(
[
module_loader("standard", "config", safe=False),
module_loader("standard", "database", safe=False),
module_loader("standard", "fsm_database_storage", safe=False),
module_loader("standard", "roles", safe=False),
module_loader("external", "yandexgpt", safe=False),
#
module_loader("standard", "command_helper"),
module_loader("standard", "info"),
module_loader("standard", "filters"),
module_loader("external", "create_report_apps"),
module_loader("standard", "admin"),
module_loader("standard", "message_processing"),
module_loader("standard", "miniapp", safe=False),
]
)
await ocab.start()
asyncio.run(main())

2162
src/gnomik/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

2
src/gnomik/poetry.toml Normal file
View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

15
src/gnomik/pyproject.toml Normal file
View File

@ -0,0 +1,15 @@
[tool.poetry]
name = "gnomik"
version = "0.1.0"
description = ""
authors = ["Максим Слипенко <maxim@slipenko.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "~3.12"
ocab-core = { extras=["webhook"], path = "../ocab_core", develop = true }
ocab-modules = { path = "../ocab_modules", develop = true }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

1
src/ocab_core/README.md Normal file
View File

@ -0,0 +1 @@
# OCAB Core

View File

@ -1,5 +0,0 @@
import asyncio
from ocab_core.main import main
asyncio.run(main())

View File

@ -1,21 +0,0 @@
core:
token: xxxxxxxxxxxxxxxxxxxxxxxxx
mode: WEBHOOK
webhook:
public_url: https://.../webhook
miniapp:
public_url: https://.../webapp
filters:
approved_chat_id: "-123456789 | -012345678"
default_chat_tag: "@alt_gnome_chat"
yandexgpt:
token: xxxxxxxxxxxxxxxxxxxxxxxxx
token_for_request: 8000
token_for_answer: 2000
catalogid: xxxxxxxxxxxxxxxxxxxxxxxxx
prompt: "Ты чат-бот ..."
startword: "Бот| Бот, | бот | бот,"
inword: "помогите | не работает"

View File

@ -1,31 +0,0 @@
import importlib
import os
import traceback
from aiogram import Bot, Dispatcher
from aiogram.types import Update
from fastapi import FastAPI, Request
def get_module_directory(module_name):
spec = importlib.util.find_spec(module_name)
if spec is None:
raise ImportError(f"Module {module_name} not found")
module_path = spec.origin
if module_path is None:
raise ImportError(f"Module {module_name} has no origin path")
return os.path.dirname(module_path)
async def register_bot_webhook(app: FastAPI, bot: Bot, dp: Dispatcher):
async def handle_webhook(request: Request):
try:
update = Update.model_validate(await request.json(), context={"bot": bot})
await dp.feed_update(bot, update)
except Exception:
traceback.print_exc()
return {"ok": False}
return {"ok": True}
app.post("/webhook")(handle_webhook)

View File

@ -1,143 +0,0 @@
import asyncio
import traceback
from typing import TYPE_CHECKING
from aiogram import Bot, Dispatcher
from fastapi import FastAPI
from hypercorn.asyncio import serve
from hypercorn.config import Config as HyperConfig
from ocab_core.lib import get_module_directory, register_bot_webhook
from ocab_core.logger import CustomLogger, log, setup_logger
from ocab_core.modules_system import ModulesManager
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
if TYPE_CHECKING:
from ocab_modules.standard.config import IConfig
ocab_modules_path = get_module_directory("ocab_modules")
def ocab_modules_loader(namespace: str, module_name: str, safe=True):
if not safe:
return UnsafeFSLoader(f"{ocab_modules_path}/{namespace}/{module_name}")
else:
return FSLoader(f"{ocab_modules_path}/{namespace}/{module_name}")
bot_modules = [
ocab_modules_loader("standard", "config", safe=False),
ocab_modules_loader("standard", "database", safe=False),
ocab_modules_loader("standard", "fsm_database_storage", safe=False),
ocab_modules_loader("standard", "roles", safe=False),
ocab_modules_loader("external", "yandexgpt", safe=False),
#
ocab_modules_loader("standard", "command_helper"),
ocab_modules_loader("standard", "info"),
ocab_modules_loader("standard", "filters"),
ocab_modules_loader("external", "create_report_apps"),
ocab_modules_loader("standard", "admin"),
ocab_modules_loader("standard", "message_processing"),
ocab_modules_loader("standard", "miniapp", safe=False),
]
async def long_polling_mode():
singleton = Singleton()
await singleton.bot.delete_webhook()
await singleton.dp.start_polling(singleton.bot)
async def webhook_mode():
singleton = Singleton()
app = FastAPI()
config = get_module("standard.config", "config")
app.mount("/webapp", singleton.storage["webapp"])
await register_bot_webhook(app, singleton.bot, singleton.dp)
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
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: "IConfig" = 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,
)
async def init_app():
setup_logger()
singleton = Singleton()
try:
singleton.modules_manager = ModulesManager()
for module_loader in bot_modules:
info = module_loader.info()
log(f"Loading {info.name} ({info.id}) module")
await singleton.modules_manager.load(module_loader)
register_config()
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.include_routers(*singleton.storage["_routers"])
for middleware in singleton.storage["_outer_message_middlewares"]:
singleton.dp.message.outer_middleware.register(middleware)
await singleton.modules_manager.late_init()
except Exception:
traceback.print_exc()
raise
async def main():
await init_app()
config = get_module("standard.config", "config")
if config.get("core::mode") == "WEBHOOK":
await webhook_mode()
else:
await long_polling_mode()
if __name__ == "__main__":
asyncio.run(main())

View File

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

View File

@ -0,0 +1,38 @@
import importlib
import os
import traceback
from aiogram import Bot, Dispatcher
from aiogram.types import Update
def get_module_directory(module_name):
spec = importlib.util.find_spec(module_name)
if spec is None:
raise ImportError(f"Module {module_name} not found")
module_path = spec.origin
if module_path is None:
raise ImportError(f"Module {module_name} has no origin path")
return os.path.dirname(module_path)
try:
from fastapi import FastAPI, Request
async def register_bot_webhook(app: FastAPI, bot: Bot, dp: Dispatcher):
async def handle_webhook(request: Request):
try:
update = Update.model_validate(
await request.json(), context={"bot": bot}
)
await dp.feed_update(bot, update)
except Exception:
traceback.print_exc()
return {"ok": False}
return {"ok": True}
app.post("/webhook")(handle_webhook)
except ImportError:
pass

View File

@ -1,8 +1,6 @@
import logging
import traceback
from hypercorn.logging import Logger as HypercornLogger
app_logger = logging.getLogger("ocab")
log_level = logging.INFO
@ -33,10 +31,16 @@ def log(message):
app_logger.info(message)
class CustomLogger(HypercornLogger):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if self.error_logger:
patch_logger(self.error_logger)
if self.access_logger:
patch_logger(self.access_logger)
try:
from hypercorn.logging import Logger as HypercornLogger
class CustomLogger(HypercornLogger):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if self.error_logger:
patch_logger(self.error_logger)
if self.access_logger:
patch_logger(self.access_logger)
except ImportError:
pass

View File

@ -0,0 +1,118 @@
import traceback
from typing import TYPE_CHECKING
from aiogram import Bot, Dispatcher
from ocab_core.lib import register_bot_webhook
from ocab_core.logger import CustomLogger, log, setup_logger
from ocab_core.modules_system import ModulesManager
from ocab_core.modules_system.public_api import get_module
from ocab_core.singleton import Singleton
if TYPE_CHECKING:
from ocab_modules.standard.config import IConfig
class OCAB:
def __init__(self) -> None:
pass
async def init_app(self, bot_modules):
setup_logger()
singleton = Singleton()
try:
singleton.modules_manager = ModulesManager()
for module_loader in bot_modules:
info = module_loader.info()
log(f"Loading {info.name} ({info.id}) module")
await singleton.modules_manager.load(module_loader)
register_config()
config: "IConfig" = 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.include_routers(*singleton.storage["_routers"])
for middleware in singleton.storage["_outer_message_middlewares"]:
singleton.dp.message.outer_middleware.register(middleware)
await singleton.modules_manager.late_init()
except Exception:
traceback.print_exc()
raise
async def start(self):
config: "IConfig" = get_module("standard.config", "config")
if config.get("core::mode") == "WEBHOOK":
await self.start_webhook_mode()
else:
await self.start_long_polling_mode()
return
async def start_long_polling_mode(self):
singleton = Singleton()
await singleton.bot.delete_webhook()
await singleton.dp.start_polling(singleton.bot)
async def start_webhook_mode(self):
try:
from fastapi import FastAPI
from hypercorn.asyncio import serve
from hypercorn.config import Config as HyperConfig
except ImportError:
log(
"Error: FastAPI and Hypercorn are required"
"for webhook mode. Please install them."
)
return
singleton = Singleton()
app = FastAPI()
config = get_module("standard.config", "config")
app.mount("/webapp", singleton.storage["webapp"])
await register_bot_webhook(app, singleton.bot, singleton.dp)
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
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: "IConfig" = 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,
)

View File

@ -2,7 +2,6 @@ import types
from _ast import AnnAssign
from typing import Any
import flet as ft
from aiogram import Bot
from RestrictedPython import (
RestrictingNodeTransformer,
@ -97,7 +96,7 @@ def safes_getattr(object, name, default=None, getattr=safer_getattr):
return getattr(object, name, default)
trusted_settters_classes = [ft.Page, ft.View]
trusted_settters_classes = []
def safes_setattr(self, key, value):

1612
src/ocab_core/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
[virtualenvs]
in-project = true

View File

@ -0,0 +1,26 @@
[tool.poetry]
name = "ocab-core"
version = "0.1.0"
description = ""
authors = ["Максим Слипенко <maxim@slipenko.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "~3.12"
aiogram = "^3.10.0"
setuptools = "^71.0.1"
restrictedpython = "^7.1"
semver = "^3.0.2"
dataclasses-json = "^0.6.7"
fastapi = { version = "^0.111.1", optional = true }
hypercorn = { version = "^0.17.3", optional = true }
[tool.poetry.group.dev.dependencies]
ocab-modules = { path = "../ocab_modules", develop = true }
[tool.poetry.extras]
webhook = ["fastapi", "hypercorn"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -0,0 +1 @@
# OCAB Modules

View File

@ -0,0 +1 @@
from .lib import module_loader

View File

@ -0,0 +1,25 @@
import importlib
import os
from ocab_core.modules_system.loaders.fs_loader import FSLoader
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
def get_module_directory(module_name):
spec = importlib.util.find_spec(module_name)
if spec is None:
raise ImportError(f"Module {module_name} not found")
module_path = spec.origin
if module_path is None:
raise ImportError(f"Module {module_name} has no origin path")
return os.path.dirname(module_path)
ocab_modules_path = get_module_directory("ocab_modules")
def module_loader(namespace: str, module_name: str, safe=True):
if not safe:
return UnsafeFSLoader(f"{ocab_modules_path}/{namespace}/{module_name}")
else:
return FSLoader(f"{ocab_modules_path}/{namespace}/{module_name}")

Some files were not shown because too many files have changed in this diff Show More