0
0
mirror of https://gitflic.ru/project/maks1ms/ocab.git synced 2025-01-12 01:31:05 +03:00
This commit is contained in:
Maxim Slipenko 2024-07-18 11:32:09 +03:00
parent abf8f8047c
commit e8b5f79d99
13 changed files with 1129 additions and 45 deletions

998
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
[tool.poetry] [tool.poetry]
package-mode = false
name = "ocab" name = "ocab"
version = "2.0.0" version = "2.0.0"
description = "OCAB is a modular Telegram bot" description = "OCAB is a modular Telegram bot"
@ -13,7 +12,11 @@ maintainers = [
] ]
readme = "README.md" readme = "README.md"
repository = "https://gitflic.ru/project/armatik/ocab" repository = "https://gitflic.ru/project/armatik/ocab"
packages = [{include = "scripts"}] packages = [
{ include = "scripts" },
{ include = "ocab_core", from = "src" },
{ include = "ocab_modules", from = "src" }
]
[tool.poetry.urls] [tool.poetry.urls]
"Bug Tracker" = "https://gitflic.ru/project/armatik/ocab/issue?status=OPEN" "Bug Tracker" = "https://gitflic.ru/project/armatik/ocab/issue?status=OPEN"
@ -32,6 +35,9 @@ requests = "^2.32.3"
restrictedpython = "^7.1" restrictedpython = "^7.1"
dataclasses-json = "^0.6.7" dataclasses-json = "^0.6.7"
semver = "^3.0.2" semver = "^3.0.2"
hypercorn = "^0.17.3"
flet = "^0.23.2"
fastapi = "^0.111.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
flake8 = "^7.1.0" flake8 = "^7.1.0"

View File

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

31
src/ocab_core/lib.py Normal file
View File

@ -0,0 +1,31 @@
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

@ -2,59 +2,92 @@ import asyncio
import traceback import traceback
from aiogram import Bot, Dispatcher 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 log, 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, UnsafeFSLoader
from ocab_core.modules_system.loaders.unsafe_fs_loader import UnsafeFSLoader
from ocab_core.singleton import Singleton from ocab_core.singleton import Singleton
from ocab_modules.standard.config.config import get_telegram_token from ocab_modules.standard.config.config import get_telegram_token
from service import paths
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 = [ bot_modules = [
UnsafeFSLoader(f"{paths.modules_standard}/config"), ocab_modules_loader("standard", "config", safe=False),
UnsafeFSLoader(f"{paths.modules_standard}/database"), ocab_modules_loader("standard", "database", safe=False),
UnsafeFSLoader(f"{paths.modules_standard}/fsm_database_storage"), ocab_modules_loader("standard", "fsm_database_storage", safe=False),
UnsafeFSLoader(f"{paths.modules_standard}/roles"), ocab_modules_loader("standard", "roles", safe=False),
UnsafeFSLoader(f"{paths.modules_external}/yandexgpt"), ocab_modules_loader("external", "yandexgpt", safe=False),
FSLoader(f"{paths.modules_standard}/command_helper"), ocab_modules_loader("standard", "miniapp", safe=False),
FSLoader(f"{paths.modules_standard}/info"), ocab_modules_loader("standard", "command_helper"),
FSLoader(f"{paths.modules_standard}/filters"), ocab_modules_loader("standard", "info"),
FSLoader(f"{paths.modules_external}/create_report_apps"), ocab_modules_loader("standard", "filters"),
FSLoader(f"{paths.modules_standard}/admin"), ocab_modules_loader("external", "create_report_apps"),
FSLoader(f"{paths.modules_standard}/message_processing"), ocab_modules_loader("standard", "admin"),
ocab_modules_loader("standard", "message_processing"),
] ]
async def main(): async def long_polling_mode():
bot = None singleton = Singleton()
setup_logger() await singleton.bot.delete_webhook()
await singleton.dp.start_polling(singleton.bot)
app = Singleton()
async def webhook_mode():
singleton = Singleton()
app = FastAPI()
await register_bot_webhook(app, singleton.bot, singleton.dp)
await singleton.bot.set_webhook(
"https://mackerel-pumped-foal.ngrok-free.app/webhook"
)
config = HyperConfig()
config.bind = ["0.0.0.0:9000"]
await serve(app, config)
async def init_app():
setup_logger()
singleton = Singleton()
try: try:
app.bot = Bot(token=get_telegram_token()) singleton.bot = Bot(token=get_telegram_token())
app.modules_manager = ModulesManager() singleton.modules_manager = ModulesManager()
for module_loader in bot_modules: for module_loader in bot_modules:
info = module_loader.info() info = module_loader.info()
log(f"Loading {info.name}({info.id}) module") log(f"Loading {info.name} ({info.id}) module")
await app.modules_manager.load(module_loader) await singleton.modules_manager.load(module_loader)
app.dp = Dispatcher(storage=app.storage["_fsm_storage"]) singleton.dp = Dispatcher(storage=singleton.storage["_fsm_storage"])
app.dp.include_routers(*app.storage["_routers"]) singleton.dp.include_routers(*singleton.storage["_routers"])
for middleware in app.storage["_outer_message_middlewares"]: for middleware in singleton.storage["_outer_message_middlewares"]:
app.dp.message.outer_middleware.register(middleware) singleton.dp.message.outer_middleware.register(middleware)
await singleton.modules_manager.late_init()
await app.modules_manager.late_init()
await app.dp.start_polling(app.bot)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
finally: raise
if bot is not None:
await app.bot.session.close()
async def main():
await init_app()
await webhook_mode()
# await long_polling_mode()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1 +1,2 @@
from .fs_loader import FSLoader from .fs_loader import FSLoader
from .unsafe_fs_loader import UnsafeFSLoader

View File

@ -1,3 +1,5 @@
import inspect
import semver import semver
from ocab_core.modules_system.loaders.base import AbstractLoader from ocab_core.modules_system.loaders.base import AbstractLoader
@ -21,6 +23,15 @@ def is_version_compatible(version, requirement):
return True return True
async def await_if_async(module, method_name):
if hasattr(module, method_name):
method = getattr(module, method_name)
if inspect.iscoroutinefunction(method):
await method()
else:
method()
class ModulesManager: class ModulesManager:
def __init__(self): def __init__(self):
self.modules = [] self.modules = []
@ -57,14 +68,12 @@ class ModulesManager:
} }
) )
if hasattr(module, "module_init"): await await_if_async(module, "module_init")
await module.module_init()
async def late_init(self): async def late_init(self):
for m in self.modules: for m in self.modules:
module = m["module"] module = m["module"]
if hasattr(module, "module_late_init"): await await_if_async(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):
module = next( module = next(

View File

@ -1,8 +1,9 @@
from ocab_core.logger import log
from .public_api import ( from .public_api import (
Storage, Storage,
get_fsm_context, get_fsm_context,
get_module, get_module,
log,
register_outer_message_middleware, register_outer_message_middleware,
register_router, register_router,
set_my_commands, set_my_commands,

View File

@ -5,7 +5,6 @@ from aiogram import BaseMiddleware, Router
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.base import StorageKey from aiogram.fsm.storage.base import StorageKey
from ocab_core.logger import log
from ocab_core.singleton import Singleton from ocab_core.singleton import Singleton
@ -40,7 +39,6 @@ async def get_fsm_context(chat_id: int, user_id: int) -> FSMContext:
def set_fsm(storage): def set_fsm(storage):
app = Singleton() app = Singleton()
log(storage)
app.storage["_fsm_storage"] = storage app.storage["_fsm_storage"] = storage

View File

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

View File

@ -0,0 +1,9 @@
{
"id": "standard.miniapp",
"name": "Miniapp",
"description": "Очень полезный модуль",
"author": "OCAB Team",
"version": "1.0.0",
"privileged": false,
"dependencies": {}
}

View File

@ -0,0 +1,2 @@
def module_init():
pass