demon-cry-base
Базовый класс для OSINT-плагинов demon-cry
Установка
pip install demon-cry-base
Использование
Плагин без конфига
Для простых Stateless-плагинов можно использовать PluginConfig напрямую:
from demon_cry_base import BasePlugin, PluginConfig, PluginParameters
async def ping_run(config: PluginConfig, params: PluginParameters) -> dict:
return {"status": "ok"}
class PingPlugin(BasePlugin):
name = "ping"
description = "Check if host is alive"
category = "utility"
parameters_model = PluginParameters
execute_func = "my_package.ping:ping_run"
execute_func — путь к раннеру в формате путь.до.модуля:имя_функции.
Раннер — отдельная async-функция (config, params) -> dict, ядро импортирует её через importlib.
Создание плагина
from demon_cry_base import BasePlugin, PluginConfig, PluginParameters
class MyPluginParams(PluginParameters):
target: str
query: str | None = None
class MyPluginConfig(PluginConfig):
timeout: int = 30
class MyPlugin(BasePlugin):
name = "my_plugin"
description = "My OSINT plugin"
category = "custom"
config_model = MyPluginConfig
parameters_model = MyPluginParams
execute_func = "my_package.my_plugin:my_plugin_run"
async def my_plugin_run(config: MyPluginConfig, params: MyPluginParams) -> dict:
return {"result": f"Scanned {params.target}"}
Два источника данных
Каждый плагин работает с двумя потоками данных:
| Config | Parameters | |
|---|---|---|
| Откуда | БД / ядро | Запрос / пользователь |
| Формат | Pydantic-модель (PluginConfig) |
Pydantic-модель (PluginParameters) |
| Зачем | Настройки плагина | Входные данные |
| Передаётся | config в функцию из execute_func |
params в функцию из execute_func |
Config — настройки из БД
PluginConfig — это Pydantic-модель. Наследуйте её и добавляйте поля:
class ReconConfig(PluginConfig):
deep: bool = False
ports: list[int] = [80, 443]
class ApiConfig(PluginConfig):
api_key: str
rate_limit: int = 100
timeout: int = 30
Затем укажите config_model в плагине. Ядро загрузит конфиг из БД и передаст в функцию из execute_func:
class ReconParams(PluginParameters):
target: str
class ReconConfig(PluginConfig):
deep: bool = False
ports: list[int] = [80, 443]
class ReconPlugin(BasePlugin):
name = "recon"
description = "Network reconnaissance"
category = "osint"
config_model = ReconConfig
parameters_model = ReconParams
execute_func = "my_package.recon:recon_run"
async def recon_run(config: ReconConfig, params: ReconParams) -> dict:
if config.deep:
...
Parameters — входные данные
PluginParameters — это Pydantic-модель. Наследуйте её и добавляйте поля:
from typing import Literal
from pydantic import Field
from demon_cry_base import PluginParameters
class SearchParams(PluginParameters):
query: str
category: Literal["general", "images", "files", "it", "social media", "news"] = (
"general"
)
time_range: Literal["day", "week", "month", "year", "all"] = "all"
max_results: int = Field(
default=10, ge=1, le=100, description="Max results to return"
)
Затем укажите parameters_model в плагине:
class SearchPlugin(BasePlugin):
name = "search"
description = "Web search"
category = "osint"
parameters_model = SearchParams
execute_func = "my_package.search:search_run"
async def search_run(config: PluginConfig, params: SearchParams) -> dict:
# params.query — строка
# params.category — enum
# params.max_results — int с валидацией
...
Паттерны параметров
| Паттерн | Пример |
|---|---|
| Обязательное поле | target: str |
| Optional поле | query: str | None = None |
| Дефолт | timeout: int = 30 |
| Enum-like | category: Literal["a", "b", "c"] = "a" |
| Валидация | max_results: int = Field(default=10, ge=1, le=100) |
| List поле | ports: list[int] = [80, 443] |
Полный пример: плагин с обоими источниками
import asyncio
import aiodns
from typing import Literal
from demon_cry_base import BasePlugin, PluginConfig, PluginParameters
class DnsLookupParams(PluginParameters):
domain: str
record_type: list[
Literal["A", "AAAA", "MX", "NS", "TXT", "CNAME", "SOA", "PTR"]
] = ["A"]
class DnsLookupConfig(PluginConfig):
name_servers: list[str] = ["1.1.1.1", "8.8.8.8"]
class DnsLookup(BasePlugin):
name = "dns_lookup"
description = "Finds DNS records"
category = "network"
config_model = DnsLookupConfig
parameters_model = DnsLookupParams
execute_func = "my_package.dns:dns_lookup_run"
async def dns_lookup_run(config: DnsLookupConfig, params: DnsLookupParams) -> dict:
resolver = aiodns.DNSResolver(nameservers=config.name_servers)
# config — настройки из БД (name_servers)
# params.domain — домен из параметров
# params.record_type — типы записей
...
Подключение к ядру (entry points)
Ядро находит плагины через importlib.metadata.entry_points(group="demon_cry.plugins").
Укажите entry-point в pyproject.toml вашего плагина:
[project.entry-points."demon_cry.plugins"]
my_plugin = "my_package.my_plugin:MyPlugin"
- Ключ слева (
my_plugin) — имя для логов/отладки. Регистрирует ядро поinstance.name, а не по ключу — держите их одинаковыми, чтобы не путаться. - Значение справа —
импорт-путь:КлассПлагина(напримерmy_package.my_plugin:MyPlugin). Класс должен наследоватьBasePluginи задаватьname,description,category,execute_func.
Не путайте два похожих формата модуль:объект:
- entry-point (
my_package.my_plugin:MyPlugin) — указывает на класс плагина; execute_func(my_package.my_plugin:my_plugin_run) — указывает на async-функцию-раннер(config, params) -> dict.
Как это работает на стороне ядра при discover():
ep.load()→ инстанцирует класс без аргументов, поэтому конструктор должен быть без обязательных аргументов.- Кладёт инстанс в реестр по
instance.name. - Берёт дефолты из
instance.config_model().model_dump()и создаёт запись в БД сenabled=False.
Требования к плагину: уникальное name, конструктор без аргументов, дефолтный конфиг должен быть валидным без секретов в коде.
Metadata
Release files for demon-cry-base 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| demon_cry_base-0.5.0.tar.gz | 6.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| demon_cry_base-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 12.3 kB
Release files / demon_cry_base-0.5.0.tar.gz
| Download URL | demon_cry_base-0.5.0.tar.gz |
|---|---|
| Size | 6.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dc0307123394a97f5dd6195dc61d60e462f989e316001947a133f980f174d492
|
|
BLAKE2b-256 checksum How to use checksums |
0b068ec53a7ce7dd3bd94e43cf50490ede1531dfe10dc227881fbc1a77e61d25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / demon_cry_base-0.5.0-py3-none-any.whl
| Download URL | demon_cry_base-0.5.0-py3-none-any.whl |
|---|---|
| Size | 5.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d1898339031c0be695d5a619017c319458ec75184c9dadf51d20b75bf103aad
|
|
BLAKE2b-256 checksum How to use checksums |
6578e7a60ca269247829a62768b370ae72f9f877c4140cf46b6db759d6472470
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log