mirror of
				https://gitflic.ru/project/maks1ms/ocab.git
				synced 2025-11-03 22:51:25 +03:00 
			
		
		
		
	подготовка к публикации
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -6,6 +6,4 @@ env
 | 
				
			|||||||
venv
 | 
					venv
 | 
				
			||||||
__pycache__
 | 
					__pycache__
 | 
				
			||||||
OCAB.db
 | 
					OCAB.db
 | 
				
			||||||
src/paths.json
 | 
					config.yaml
 | 
				
			||||||
src/ocab_core/config.yaml
 | 
					 | 
				
			||||||
src/ocab_core/log/**/*
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2221
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2221
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
[tool.poetry]
 | 
					[tool.poetry]
 | 
				
			||||||
name = "ocab"
 | 
					name = "ocab-monorepo"
 | 
				
			||||||
version = "2.0.0"
 | 
					version = "2.0.0"
 | 
				
			||||||
description = "OCAB is a modular Telegram bot"
 | 
					description = "OCAB is a modular Telegram bot"
 | 
				
			||||||
license = "GPL-3.0-only"
 | 
					license = "GPL-3.0-only"
 | 
				
			||||||
@@ -13,9 +13,7 @@ maintainers = [
 | 
				
			|||||||
readme = "README.md"
 | 
					readme = "README.md"
 | 
				
			||||||
repository = "https://gitflic.ru/project/armatik/ocab"
 | 
					repository = "https://gitflic.ru/project/armatik/ocab"
 | 
				
			||||||
packages = [
 | 
					packages = [
 | 
				
			||||||
    { include = "scripts" },
 | 
					    { include = "scripts" }
 | 
				
			||||||
    { include = "ocab_core", from = "src" },
 | 
					 | 
				
			||||||
    { include = "ocab_modules", from = "src" }
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.urls]
 | 
					[tool.poetry.urls]
 | 
				
			||||||
@@ -27,21 +25,7 @@ init = 'scripts.init:main'
 | 
				
			|||||||
module = 'scripts.module:main'
 | 
					module = 'scripts.module:main'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.dependencies]
 | 
					[tool.poetry.dependencies]
 | 
				
			||||||
python = ">=3.11.6,<3.13"
 | 
					python = "~3.12"
 | 
				
			||||||
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"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.group.dev.dependencies]
 | 
					[tool.poetry.group.dev.dependencies]
 | 
				
			||||||
flake8 = "^7.1.0"
 | 
					flake8 = "^7.1.0"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/gnomik/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/gnomik/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					# Гномик
 | 
				
			||||||
							
								
								
									
										29
									
								
								src/gnomik/gnomik/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/gnomik/gnomik/__main__.py
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										2
									
								
								src/gnomik/poetry.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					[virtualenvs]
 | 
				
			||||||
 | 
					in-project = true
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/gnomik/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/gnomik/pyproject.toml
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										1
									
								
								src/ocab_core/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					# OCAB Core
 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from ocab_core.main import main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
asyncio.run(main())
 | 
					 | 
				
			||||||
@@ -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: "помогите | не работает"
 | 
					 | 
				
			||||||
@@ -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)
 | 
					 | 
				
			||||||
@@ -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())
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								src/ocab_core/ocab_core/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/ocab_core/ocab_core/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					from .main import OCAB
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/ocab_core/ocab_core/lib.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/ocab_core/ocab_core/lib.py
									
									
									
									
									
										Normal 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
 | 
				
			||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from hypercorn.logging import Logger as HypercornLogger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app_logger = logging.getLogger("ocab")
 | 
					app_logger = logging.getLogger("ocab")
 | 
				
			||||||
log_level = logging.INFO
 | 
					log_level = logging.INFO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,10 +31,16 @@ def log(message):
 | 
				
			|||||||
        app_logger.info(message)
 | 
					        app_logger.info(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomLogger(HypercornLogger):
 | 
					try:
 | 
				
			||||||
    def __init__(self, *args, **kwargs) -> None:
 | 
					    from hypercorn.logging import Logger as HypercornLogger
 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					
 | 
				
			||||||
        if self.error_logger:
 | 
					    class CustomLogger(HypercornLogger):
 | 
				
			||||||
            patch_logger(self.error_logger)
 | 
					        def __init__(self, *args, **kwargs) -> None:
 | 
				
			||||||
        if self.access_logger:
 | 
					            super().__init__(*args, **kwargs)
 | 
				
			||||||
            patch_logger(self.access_logger)
 | 
					            if self.error_logger:
 | 
				
			||||||
 | 
					                patch_logger(self.error_logger)
 | 
				
			||||||
 | 
					            if self.access_logger:
 | 
				
			||||||
 | 
					                patch_logger(self.access_logger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
							
								
								
									
										118
									
								
								src/ocab_core/ocab_core/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/ocab_core/ocab_core/main.py
									
									
									
									
									
										Normal 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,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
@@ -2,7 +2,6 @@ import types
 | 
				
			|||||||
from _ast import AnnAssign
 | 
					from _ast import AnnAssign
 | 
				
			||||||
from typing import Any
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import flet as ft
 | 
					 | 
				
			||||||
from aiogram import Bot
 | 
					from aiogram import Bot
 | 
				
			||||||
from RestrictedPython import (
 | 
					from RestrictedPython import (
 | 
				
			||||||
    RestrictingNodeTransformer,
 | 
					    RestrictingNodeTransformer,
 | 
				
			||||||
@@ -97,7 +96,7 @@ def safes_getattr(object, name, default=None, getattr=safer_getattr):
 | 
				
			|||||||
    return getattr(object, name, default)
 | 
					    return getattr(object, name, default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trusted_settters_classes = [ft.Page, ft.View]
 | 
					trusted_settters_classes = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def safes_setattr(self, key, value):
 | 
					def safes_setattr(self, key, value):
 | 
				
			||||||
							
								
								
									
										1612
									
								
								src/ocab_core/poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1612
									
								
								src/ocab_core/poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								src/ocab_core/poetry.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/ocab_core/poetry.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					[virtualenvs]
 | 
				
			||||||
 | 
					in-project = true
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/ocab_core/pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/ocab_core/pyproject.toml
									
									
									
									
									
										Normal 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"
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/ocab_modules/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/ocab_modules/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					# OCAB Modules
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/ocab_modules/ocab_modules/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/ocab_modules/ocab_modules/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					from .lib import module_loader
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/ocab_modules/ocab_modules/lib.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/ocab_modules/ocab_modules/lib.py
									
									
									
									
									
										Normal 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
		Reference in New Issue
	
	Block a user