mirror of
https://gitflic.ru/project/alt-gnome/karkas.git
synced 2025-01-11 17:28:13 +03:00
Merged with chore/small-fixes-KarkasLite
This commit is contained in:
commit
da2a5c21c1
2
.flake8
2
.flake8
@ -4,3 +4,5 @@ per-file-ignores =
|
||||
max-line-length = 88
|
||||
count = true
|
||||
extend-ignore = E203,E701
|
||||
|
||||
extend-select = TC010,TC200
|
||||
|
@ -29,6 +29,8 @@ repos:
|
||||
rev: 7.1.0 # sync:flake8:poetry.lock
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-type-checking
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.7.9 # sync:bandit:poetry.lock
|
||||
hooks:
|
||||
|
@ -12,10 +12,6 @@
|
||||
"name": "Karkas Core",
|
||||
"path": "src/karkas_core"
|
||||
},
|
||||
{
|
||||
"name": "Gnomik",
|
||||
"path": "src/gnomik"
|
||||
},
|
||||
{
|
||||
"name": "ALT Linux",
|
||||
"path": "src/altlinux"
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
## Описание
|
||||
|
||||
Подготовленная версия Karkas Lite для интеграции в чат [Альт Линукс](https://t.me/alt_linux)
|
||||
Подготовленная версия Karkas для интеграции в чат [Альт Линукс](https://t.me/alt_linux)
|
||||
|
||||
## Функционал
|
||||
Список OCAB-модулей используемых в боте:
|
||||
|
||||
* report - Вызов администрации чата одной командой
|
||||
* welcome - Автоматическая вариативная проверка пользователей на признаки бота или другой автоматической рекламной системы
|
||||
* help - Получение информации об Karkas Lite
|
||||
* help - Получение справки о боте
|
||||
|
||||
## Запуск
|
||||
|
||||
@ -17,29 +17,31 @@
|
||||
|
||||
1. Соберите Docker-образ:
|
||||
```bash
|
||||
docker build -t gnomik .
|
||||
docker build -t altlinux -f Dockerfile ../..
|
||||
```
|
||||
2. Запустите контейнер:
|
||||
```bash
|
||||
docker run -p 9000:9000 -v ./config.yaml:/app/config.yaml -v ./database:/app/database gnomik
|
||||
docker run -v ./config.yaml:/app/config.yaml altlinux
|
||||
```
|
||||
|
||||
Замените `./config.yaml` и `./database` на пути к вашим локальным файлам конфигурации и паки для базы данных.
|
||||
Замените `./config.yaml` на путь к вашему локальному файлу конфигурации.
|
||||
|
||||
### Вручную
|
||||
|
||||
1. Активируйте виртуальное окружение Gnomика:
|
||||
1. Активируйте виртуальное окружение:
|
||||
```bash
|
||||
poetry shell
|
||||
```
|
||||
2. Запустите бота:
|
||||
```bash
|
||||
python -m gnomik
|
||||
python -m altlinux
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Конфигурация бота находится в файле `config.yaml`.
|
||||
Конфигурация хранится в файле `config.yaml`.
|
||||
|
||||
Пример конфигурации бота находится в файле `config-example.yaml`.
|
||||
|
||||
## Модули
|
||||
|
||||
|
@ -2,6 +2,45 @@ core:
|
||||
mode: LONG_POLLING
|
||||
token: xxx
|
||||
|
||||
filters:
|
||||
approved_chat_id:
|
||||
- -111111
|
||||
#
|
||||
# filters:
|
||||
# approved_chat_id:
|
||||
# - -100000000000
|
||||
#
|
||||
|
||||
#
|
||||
# welcome:
|
||||
# show_success_message: True
|
||||
# max_attempts: 5
|
||||
# timeout: 60
|
||||
# tasks:
|
||||
# math_buttons:
|
||||
# enabled: False
|
||||
# math_poll:
|
||||
# enabled: True
|
||||
# question_buttons:
|
||||
# enabled: False
|
||||
# question_poll:
|
||||
# enabled: False
|
||||
#
|
||||
|
||||
#
|
||||
# report:
|
||||
# errors:
|
||||
# no_reply_message: "Пожалуйста, используйте команду в ответ на сообщение"
|
||||
# mention:
|
||||
# text: "⚠️ Внимание, жалоба на спам! $mention"
|
||||
# limit: 5
|
||||
# list:
|
||||
# - "@test1"
|
||||
# - "@test2"
|
||||
# - "@test3"
|
||||
# - "@test4"
|
||||
# - "@test5"
|
||||
# - "@test6"
|
||||
#
|
||||
|
||||
#
|
||||
# help:
|
||||
# message: "$commands"
|
||||
#
|
||||
|
@ -1,5 +1,3 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
|
@ -1,8 +1,6 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .config_manager import ConfigManager
|
||||
|
||||
try:
|
||||
import dash_bootstrap_components as dbc
|
||||
import flask
|
||||
@ -17,8 +15,10 @@ from karkas_core.modules_system.public_api import get_module
|
||||
if TYPE_CHECKING:
|
||||
from karkas_blocks.standard.roles import Roles as IRoles
|
||||
|
||||
from .config_manager import ConfigManager
|
||||
|
||||
def create_control(key: str, config: ConfigManager):
|
||||
|
||||
def create_control(key: str, config: "ConfigManager"):
|
||||
value = config.get(key)
|
||||
meta = config.get_meta(key)
|
||||
|
||||
@ -85,7 +85,7 @@ def create_control(key: str, config: ConfigManager):
|
||||
return dbc.Row(row, className="mb-3 mx-1")
|
||||
|
||||
|
||||
def build_settings_tree(config: ConfigManager):
|
||||
def build_settings_tree(config: "ConfigManager"):
|
||||
tree = {}
|
||||
|
||||
for key, value in config._metadata.items():
|
||||
@ -134,7 +134,7 @@ def create_settings_components(tree, level=0):
|
||||
return components
|
||||
|
||||
|
||||
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
||||
def get_miniapp_blueprint(config: "ConfigManager", prefix: str):
|
||||
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
||||
|
||||
roles = Roles()
|
||||
|
@ -1,13 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import Message
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from karkas_core.modules_system.public_api import get_module
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
from aiogram.types import Message
|
||||
|
||||
from karkas_blocks.standard.config import IConfig
|
||||
from karkas_blocks.standard.roles import Roles as IRoles
|
||||
|
||||
@ -34,7 +35,7 @@ def get_approved_chat_id() -> list:
|
||||
|
||||
|
||||
@deprecated("Use ChatIDFilter or own implementation")
|
||||
def chat_not_in_approve(message: Message) -> bool:
|
||||
def chat_not_in_approve(message: "Message") -> bool:
|
||||
chat_id = message.chat.id
|
||||
if chat_id in get_approved_chat_id():
|
||||
# log(f"Chat in approve list: {chat_id}")
|
||||
@ -50,7 +51,7 @@ class ChatIDFilter(BaseFilter):
|
||||
self.approved_chats = approved_chats
|
||||
super().__init__()
|
||||
|
||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
||||
async def __call__(self, message: "Message", bot: "Bot") -> bool:
|
||||
chat_id = message.chat.id
|
||||
|
||||
approved_chats = self.approved_chats or get_approved_chat_id()
|
||||
@ -70,7 +71,7 @@ class ChatNotInApproveFilter(ChatIDFilter):
|
||||
|
||||
|
||||
class ChatModerOrAdminFilter(BaseFilter):
|
||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
||||
async def __call__(self, message: "Message", bot: "Bot") -> bool:
|
||||
if not ROLES_MODULE_LOADED:
|
||||
raise Exception("Roles module not loaded")
|
||||
|
||||
|
@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
|
||||
from karkas_core.modules_system.public_api import (
|
||||
get_metainfo,
|
||||
@ -12,6 +11,8 @@ from karkas_core.modules_system.public_api import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.types import Message
|
||||
|
||||
from karkas_blocks.standard.config import IConfig
|
||||
|
||||
config: "IConfig" = get_module("standard.config", "config")
|
||||
@ -46,7 +47,7 @@ def format_commands(commands_dict):
|
||||
return "\n".join(formatted_commands)
|
||||
|
||||
|
||||
async def help(message: Message):
|
||||
async def help(message: "Message"):
|
||||
commands = ""
|
||||
version = ""
|
||||
|
||||
|
@ -4,11 +4,13 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import ChatMemberAdministrator, ChatMemberOwner, Message
|
||||
from aiogram.types import ChatMemberOwner
|
||||
|
||||
from karkas_core.modules_system.public_api import get_module, log, register_router
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.types import ChatMemberAdministrator, Message
|
||||
|
||||
from karkas_blocks.standard.config import IConfig
|
||||
from karkas_blocks.standard.filters import ChatIDFilter as IChatIDFilter
|
||||
|
||||
@ -29,7 +31,7 @@ except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def can_moderate(admin: ChatMemberOwner | ChatMemberAdministrator) -> bool:
|
||||
def can_moderate(admin: "ChatMemberOwner | ChatMemberAdministrator") -> bool:
|
||||
if isinstance(admin, ChatMemberOwner):
|
||||
return True
|
||||
|
||||
@ -38,7 +40,7 @@ def can_moderate(admin: ChatMemberOwner | ChatMemberAdministrator) -> bool:
|
||||
)
|
||||
|
||||
|
||||
async def report(message: Message):
|
||||
async def report(message: "Message"):
|
||||
try:
|
||||
if message.reply_to_message is None:
|
||||
await message.reply(config.get("report::errors::no_reply_message"))
|
||||
|
@ -7,7 +7,6 @@ from aiogram import Bot, Router, types
|
||||
from aiogram.enums import ChatMemberStatus, ParseMode
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.filters import JOIN_TRANSITION, LEAVE_TRANSITION, ChatMemberUpdatedFilter
|
||||
from aiogram.types import ChatMemberUpdated, PollAnswer
|
||||
|
||||
from karkas_core.modules_system.public_api import get_module, log, register_router
|
||||
|
||||
@ -22,6 +21,8 @@ from .verifications_methods.simple import (
|
||||
from .verifications_methods.utils import user_mention
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.types import ChatMemberUpdated, PollAnswer
|
||||
|
||||
from karkas_blocks.standard.config import IConfig
|
||||
from karkas_blocks.standard.filters import ChatIDFilter as IChatIDFilter
|
||||
|
||||
@ -78,7 +79,7 @@ verification_tasks = MultiKeyDict()
|
||||
last_success = {}
|
||||
|
||||
|
||||
async def new_member_handler(event: ChatMemberUpdated, bot: Bot):
|
||||
async def new_member_handler(event: "ChatMemberUpdated", bot: Bot):
|
||||
# НЕ СРАБОТАЕТ, ЕСЛИ ЧЕЛОВЕК УЖЕ ОГРАНИЧЕН В ПРАВАХ (RESTRICTED)
|
||||
if event.new_chat_member.status == ChatMemberStatus.MEMBER:
|
||||
task = task_manager.build_random_task(event, bot)
|
||||
@ -87,7 +88,7 @@ async def new_member_handler(event: ChatMemberUpdated, bot: Bot):
|
||||
verification_tasks.add(task, keys)
|
||||
|
||||
|
||||
async def left_member_handler(event: ChatMemberUpdated, bot: Bot):
|
||||
async def left_member_handler(event: "ChatMemberUpdated", bot: Bot):
|
||||
user_id = event.from_user.id
|
||||
chat_id = event.chat.id
|
||||
|
||||
@ -144,7 +145,7 @@ async def success_end(task: BaseTask):
|
||||
last_success[task.from_chat_id] = message.message_id
|
||||
|
||||
|
||||
async def handle_poll_verification(answer: PollAnswer, bot: Bot):
|
||||
async def handle_poll_verification(answer: "PollAnswer", bot: Bot):
|
||||
key = key_from_poll(answer.poll_id)
|
||||
if not verification_tasks.exists(key):
|
||||
return
|
||||
|
@ -1,21 +1,24 @@
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from aiogram.types import ChatMemberUpdated
|
||||
|
||||
from karkas_core.modules_system.public_api import log
|
||||
|
||||
from .utils import mute_user, unmute_user
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
from aiogram.types import ChatMemberUpdated
|
||||
|
||||
|
||||
class BaseTask:
|
||||
def __init__(
|
||||
self,
|
||||
event: ChatMemberUpdated,
|
||||
bot: Bot,
|
||||
event: "ChatMemberUpdated",
|
||||
bot: "Bot",
|
||||
timeout_func=None,
|
||||
attempt_number=1,
|
||||
max_attempts=1,
|
||||
|
@ -1,13 +1,17 @@
|
||||
from string import Template
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.enums import ParseMode, PollType
|
||||
from aiogram.types import ChatMemberUpdated, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
|
||||
from ..utils import get_plural_form, key_from_poll, key_from_user_chat
|
||||
from .base import BaseTask, VerificationCallback, mute_while_task
|
||||
from .utils import user_mention
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
from aiogram.types import ChatMemberUpdated
|
||||
|
||||
|
||||
class SimpleBaseTask(BaseTask):
|
||||
pass
|
||||
@ -119,8 +123,8 @@ class SimpleInlineButtonsTask(SimpleVariantsBaseTask):
|
||||
class SimplePollTask(SimpleVariantsBaseTask):
|
||||
def __init__(
|
||||
self,
|
||||
event: ChatMemberUpdated,
|
||||
bot: Bot,
|
||||
event: "ChatMemberUpdated",
|
||||
bot: "Bot",
|
||||
timeout_func=None,
|
||||
attempt_number=1,
|
||||
max_attempts=1,
|
||||
|
@ -1,9 +1,12 @@
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.types import ChatPermissions, User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot
|
||||
|
||||
|
||||
def user_mention(user: User, mode=ParseMode.HTML):
|
||||
if mode == ParseMode.HTML:
|
||||
@ -14,7 +17,7 @@ def user_mention(user: User, mode=ParseMode.HTML):
|
||||
raise ValueError(f"Unknown parse mode {mode}")
|
||||
|
||||
|
||||
async def mute_user(chat_id, user_id, until, bot: Bot):
|
||||
async def mute_user(chat_id, user_id, until, bot: "Bot"):
|
||||
end_time = until + int(time.time())
|
||||
await bot.restrict_chat_member(
|
||||
chat_id,
|
||||
@ -40,7 +43,7 @@ async def mute_user(chat_id, user_id, until, bot: Bot):
|
||||
)
|
||||
|
||||
|
||||
async def unmute_user(chat_id, user_id, bot: Bot):
|
||||
async def unmute_user(chat_id, user_id, bot: "Bot"):
|
||||
await bot.restrict_chat_member(
|
||||
chat_id,
|
||||
user_id,
|
||||
|
@ -1,10 +1,14 @@
|
||||
import importlib
|
||||
import os
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.types import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot, Dispatcher
|
||||
from fastapi import FastAPI, Request
|
||||
|
||||
|
||||
def get_module_directory(module_name):
|
||||
spec = importlib.util.find_spec(module_name)
|
||||
@ -16,23 +20,15 @@ def get_module_directory(module_name):
|
||||
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}
|
||||
|
||||
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}
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
app.post("/webhook")(handle_webhook)
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
app.post("/webhook")(handle_webhook)
|
||||
|
@ -84,7 +84,7 @@ class Karkas:
|
||||
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.bind = [f"0.0.0.0:{config.get('core::webhook::port')}"]
|
||||
hyperConfig.logger_class = CustomLogger
|
||||
await serve(app, hyperConfig)
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import types
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
|
||||
from dataclasses_json import dataclass_json
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import types
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
@ -39,5 +41,5 @@ class AbstractLoader:
|
||||
def info(self) -> ModuleInfo:
|
||||
raise NotImplementedError
|
||||
|
||||
def load(self) -> types.ModuleType:
|
||||
def load(self) -> "types.ModuleType":
|
||||
raise NotImplementedError
|
||||
|
@ -1,8 +1,7 @@
|
||||
import inspect
|
||||
import types
|
||||
from typing import Any, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Any, Tuple, Union
|
||||
|
||||
from aiogram import BaseMiddleware, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.storage.base import StorageKey
|
||||
|
||||
@ -10,18 +9,21 @@ from aiogram.fsm.storage.base import StorageKey
|
||||
from karkas_core.modules_system.loaders.base import DependencyInfo
|
||||
from karkas_core.singleton import Singleton
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import BaseMiddleware, Router
|
||||
|
||||
|
||||
async def set_chat_menu_button(menu_button):
|
||||
app = Singleton()
|
||||
await app.bot.set_chat_menu_button(menu_button=menu_button)
|
||||
|
||||
|
||||
def register_router(router: Router):
|
||||
def register_router(router: "Router"):
|
||||
app = Singleton()
|
||||
app.storage["_routers"].append(router)
|
||||
|
||||
|
||||
def register_outer_message_middleware(middleware: BaseMiddleware):
|
||||
def register_outer_message_middleware(middleware: "BaseMiddleware"):
|
||||
app = Singleton()
|
||||
app.storage["_outer_message_middlewares"].append(middleware)
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import types
|
||||
from _ast import AnnAssign
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from aiogram import Bot
|
||||
from RestrictedPython import (
|
||||
@ -20,6 +19,9 @@ from RestrictedPython.Guards import ( # guarded_setattr,; full_write_guard,
|
||||
from karkas_core.logger import log
|
||||
from karkas_core.modules_system.safe.zope_guards import extra_safe_builtins
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _ast import AnnAssign
|
||||
|
||||
|
||||
class RestrictedPythonPolicy(RestrictingNodeTransformer):
|
||||
def visit_AsyncFunctionDef(self, node):
|
||||
@ -50,7 +52,7 @@ class RestrictedPythonPolicy(RestrictingNodeTransformer):
|
||||
return self.node_contents_visit(node)
|
||||
"""
|
||||
|
||||
def visit_AnnAssign(self, node: AnnAssign) -> Any:
|
||||
def visit_AnnAssign(self, node: "AnnAssign") -> Any:
|
||||
# missing in RestrictingNodeTransformer
|
||||
# this doesn't need the logic that is in visit_Assign
|
||||
# because it doesn't have a "targets" attribute,
|
||||
|
@ -1,7 +1,11 @@
|
||||
from aiogram import Bot, Dispatcher
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
|
||||
from karkas_core.modules_system import ModulesManager
|
||||
if TYPE_CHECKING:
|
||||
from aiogram import Bot, Dispatcher
|
||||
|
||||
from karkas_core.modules_system import ModulesManager
|
||||
|
||||
|
||||
class SingletonMeta(type):
|
||||
@ -15,9 +19,9 @@ class SingletonMeta(type):
|
||||
|
||||
|
||||
class Singleton(metaclass=SingletonMeta):
|
||||
bot: Bot
|
||||
dp: Dispatcher = None
|
||||
modules_manager: ModulesManager = None
|
||||
bot: "Bot"
|
||||
dp: "Dispatcher" = None
|
||||
modules_manager: "ModulesManager" = None
|
||||
storage = {
|
||||
"_fsm_storage": MemoryStorage(),
|
||||
"_routers": [],
|
||||
|
Loading…
Reference in New Issue
Block a user