mirror of
https://gitflic.ru/project/alt-gnome/karkas.git
synced 2025-04-21 08:53:47 +03:00
Compare commits
No commits in common. "main" and "0.1.1" have entirely different histories.
8
.flake8
8
.flake8
@ -1,8 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
per-file-ignores =
|
|
||||||
__init__.py:F401
|
|
||||||
max-line-length = 88
|
|
||||||
count = true
|
|
||||||
extend-ignore = E203,E701
|
|
||||||
|
|
||||||
extend-select = TC010,TC200
|
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -5,7 +5,6 @@ env
|
|||||||
.venv
|
.venv
|
||||||
venv
|
venv
|
||||||
__pycache__
|
__pycache__
|
||||||
Karkas.db
|
OCAB.db
|
||||||
config.yaml
|
src/paths.json
|
||||||
dist
|
src/core/config.yaml
|
||||||
*.py[cod]
|
|
7
.mailmap
7
.mailmap
@ -1,7 +0,0 @@
|
|||||||
armatik <s.fomchenkov@yandex.ru> <57626821+armatik@users.noreply.github.com>
|
|
||||||
armatik <s.fomchenkov@yandex.ru> <57626821+Armatik@users.noreply.github.com>
|
|
||||||
armatik <s.fomchenkov@yandex.ru> Armatik <s.fomchenkov@yandex.ru>
|
|
||||||
|
|
||||||
ilyazheprog <ilyazheprog@gmail.com> <ilya_zhenetskij@vk.com>
|
|
||||||
|
|
||||||
Maxim Slipenko <maxim@slipenko.com> Максим Слипенко <maxim@slipenko.com>
|
|
@ -1,81 +0,0 @@
|
|||||||
# See https://pre-commit.com for more information
|
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/crashappsec/pre-commit-sync
|
|
||||||
rev: 04b0e02eefa7c41bedca7456ad542e60b67c16c6
|
|
||||||
hooks:
|
|
||||||
- id: pre-commit-sync
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v3.2.0
|
|
||||||
hooks:
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: check-yaml
|
|
||||||
- id: check-added-large-files
|
|
||||||
- repo: https://github.com/netromdk/vermin
|
|
||||||
rev: v1.6.0
|
|
||||||
hooks:
|
|
||||||
- id: vermin
|
|
||||||
args: ['-t=3.10-', '--violations']
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.13.2 # sync:isort:poetry.lock
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 24.4.2 # sync:black:poetry.lock
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
|
||||||
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:
|
|
||||||
- id: bandit
|
|
||||||
- repo: https://github.com/python-poetry/poetry
|
|
||||||
rev: 1.8.3
|
|
||||||
hooks:
|
|
||||||
- name: Poetry Lock (root)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["--no-update"]
|
|
||||||
- name: Poetry Check (root)
|
|
||||||
id: poetry-check
|
|
||||||
- name: Poetry Lock (gnomik)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["-C", "./src/gnomik", "--no-update"]
|
|
||||||
- name: Poetry Check (gnomik)
|
|
||||||
id: poetry-check
|
|
||||||
args: ["-C", "./src/gnomik"]
|
|
||||||
- name: Poetry Lock (altlinux)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["-C", "./src/altlinux", "--no-update"]
|
|
||||||
- name: Poetry Check (altlinux)
|
|
||||||
id: poetry-check
|
|
||||||
args: ["-C", "./src/altlinux"]
|
|
||||||
- name: Poetry Lock (karkas_core)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["-C", "./src/karkas_core", "--no-update"]
|
|
||||||
- name: Poetry Check (karkas_core)
|
|
||||||
id: poetry-check
|
|
||||||
args: ["-C", "./src/karkas_core"]
|
|
||||||
- name: Poetry Lock (karkas_blocks)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["-C", "./src/karkas_blocks", "--no-update"]
|
|
||||||
- name: Poetry Check (karkas_blocks)
|
|
||||||
id: poetry-check
|
|
||||||
args: ["-C", "./src/karkas_blocks"]
|
|
||||||
- name: Poetry Lock (karkas_piccolo)
|
|
||||||
id: poetry-lock
|
|
||||||
args: ["-C", "./src/karkas_piccolo", "--no-update"]
|
|
||||||
- name: Poetry Check (karkas_piccolo)
|
|
||||||
id: poetry-check
|
|
||||||
args: ["-C", "./src/karkas_piccolo"]
|
|
||||||
- repo: https://github.com/compilerla/conventional-pre-commit
|
|
||||||
rev: v3.4.0
|
|
||||||
hooks:
|
|
||||||
- id: conventional-pre-commit
|
|
||||||
stages: [commit-msg]
|
|
||||||
args: []
|
|
8
AUTHORS
8
AUTHORS
@ -1,8 +0,0 @@
|
|||||||
Руководитель проекта:
|
|
||||||
- Семен Фомченков (@Armatik), e-mail: armatik@alt-gnome.ru
|
|
||||||
|
|
||||||
Ведущие разработчики:
|
|
||||||
- Максим Слипенко (@Maks1m_S), e-mail: maxim@slipenko.com
|
|
||||||
|
|
||||||
Участники проекта:
|
|
||||||
- Илья Женецкий (@ilyazheprog)
|
|
@ -1,8 +0,0 @@
|
|||||||
Project manager:
|
|
||||||
- Semen Fomchenkov (@Armatik), e-mail: armatik@alt-gnome.ru
|
|
||||||
|
|
||||||
Leading developers:
|
|
||||||
- Maxim Slipenko (@Maks1m_S), e-mail: maxim@slipenko.com
|
|
||||||
|
|
||||||
Project participants:
|
|
||||||
- Ilya Zhenetsky (@ilyazheprog)
|
|
71
README.md
71
README.md
@ -1,45 +1,58 @@
|
|||||||
# Каркас
|
# OpenChatAiBot V2
|
||||||
|
|
||||||
## Что такое «Каркас»?
|
## Что такое OCAB?
|
||||||
|
|
||||||
Каркас — это платформа для разработки блочных Telegram-ботов, которая призвана упростить взаимодействие с чатами. «Каркас» предоставляет возможность расширять функциональность бота с помощью интеграции различных блоков. Код платформы и набор стандартных блоков находятся в этом монорепозитории.
|
OCAB - это бот для Telegram, который призван помочь во взаимодействии с чатом.
|
||||||
|
Бот поддерживает интеграцию модулей для расширения функционала.
|
||||||
|
Фактически бот является платформой для запуска созданных для него модулей.
|
||||||
|
Модули могут взаимодействовать друг с другом или быть полностью независимыми.
|
||||||
|
|
||||||
## Структура монорепозитория
|
## Что такое модуль?
|
||||||
|
|
||||||
Монорепозиторий Karkas включает в себя:
|
Модуль - это директория, которая содержит в себе код модуля и его конфигурацию.
|
||||||
|
|
||||||
- **Ядро Karkas (`src/karkas_core`):** Основные компоненты платформы, такие как система управления блоками, логирование и утилиты.
|
### Структура модуля
|
||||||
- **Блоки Karkas (`src/karkas_blocks`):** Содержит стандартные и дополнительные блоки, которые расширяют функциональность ботов, созданных на платформе «Каркас».
|
|
||||||
- **Бот Gnomик (`src/gnomik`):** Пример реализации бота, созданного на основе платформы «Каркас».
|
|
||||||
|
|
||||||
## Блоки
|
*Будет дополнено после закрытия [issue #17](https://gitflic.ru/project/armatik/ocab/issue/17).*
|
||||||
|
|
||||||
Блоки Karkas — это независимые компоненты, которые добавляют функциональность бота.
|
## Стандартные модули
|
||||||
|
|
||||||
### Структура блока
|
В стандартный состав бота входят следующие модули:
|
||||||
|
|
||||||
Структура блока представлена [здесь](docs/BLOCKS-SPEC.md).
|
* `admin` - модуль для модерирования чата. Позволяет удалять сообщения, банить пользователей и т.д.
|
||||||
|
* `reputation` - модуль репутации пользователей. Позволяет оценивать ответы пользователей и накапливать репутацию.
|
||||||
|
* `welcome` - модуль приветствия новых пользователей. Позволяет приветствовать новых пользователей в чате, а также
|
||||||
|
проверять пользователя капчей для предотвращения спама.
|
||||||
|
* `roles` - модуль ролей. Позволяет назначать пользователям роли и ограничивать доступ к командам бота по ролям.
|
||||||
|
Является важной частью системы прав доступа и модуля `admin`.
|
||||||
|
|
||||||
### Стандартные блоки
|
## Дополнительные официальные модули
|
||||||
|
|
||||||
Стандартные блоки предоставляют базовые функции для работы бота
|
* `gpt` - модуль для генерации ответов на основе нейросети GPT-3.5. Позволяет боту отвечать на сообщения
|
||||||
|
пользователей, используя нейросеть. Ключевой особенностью является построение линии контекста для нейросети,
|
||||||
|
которая позволяет боту отвечать на вопросы, используя контекст предыдущих сообщений. Для этого используется
|
||||||
|
модуль база данных хранящий историю сообщений.
|
||||||
|
* `bugzilla` - модуль для интеграции с BugZilla. Позволяет получать уведомления о новых багах в BugZilla, отслеживать их
|
||||||
|
статус, формировать стандартизированные сообщения для корректного описания багов. В будущем планируется интеграция с
|
||||||
|
API BugZilla для возможности создания багов из чата.
|
||||||
|
* `alt_packages` - модуль для интеграции с AltLinux Packages. Позволяет получать уведомления о новых пакетах в репозитории
|
||||||
|
AltLinux, поиска пакетов по названию, получения истории изменений, команды для установки пакета из репозитория и
|
||||||
|
прочей информации о пакете.
|
||||||
|
* `notes` - модуль заметок. Позволяет сохранять заметки для пользователей и чатов. Заметки являются ссылками на
|
||||||
|
сообщения в чате.
|
||||||
|
|
||||||
Полный перечень стандартных блоков:
|
Список модулей будет пополняться. Идеи для модулей можно оставлять в [issues](https://gitflic.ru/project/armatik/ocab/issue/create).
|
||||||
|
|
||||||
- [`admin`](src/karkas_blocks/karkas_blocks/standard/admin/README.md) — блок модерирования чата;
|
## Установка бота
|
||||||
- [`roles`](src/karkas_blocks/karkas_blocks/standard/roles/README.md) — блок управления ролями пользователей;
|
|
||||||
- [`config`](src/karkas_blocks/karkas_blocks/standard/config/README.md) — блок управления конфигурацией бота;
|
|
||||||
- [`database`](src/karkas_blocks/karkas_blocks/standard/database/README.md) — блок для работы с базой данных;
|
|
||||||
- [`fsm_database_storage`](src/karkas_blocks/karkas_blocks/standard/fsm_database_storage/README.md) — блок для хранения состояний FSM в базе данных;
|
|
||||||
- [`filters`](src/karkas_blocks/karkas_blocks/standard/filters/README.md) — блок, предоставляющий фильтры для `aiogram`;
|
|
||||||
- [`message_processing`](src/karkas_blocks/karkas_blocks/standard/message_processing/README.md) — блок обработки входящих сообщений;
|
|
||||||
- [`miniapp`](src/karkas_blocks/karkas_blocks/standard/miniapp/README.md) — блок для реализации веб-интерфейса бота;
|
|
||||||
- [`command_helper`](src/karkas_blocks/karkas_blocks/standard/command_helper/README.md) — блок для упрощения регистрации команд бота;
|
|
||||||
- [`info`](src/karkas_blocks/karkas_blocks/standard/info/README.md) — блок предоставления информации о пользователях и чатах.
|
|
||||||
|
|
||||||
### Дополнительные официальные блоки
|
### Docker
|
||||||
|
|
||||||
Дополнительные официальные блоки созданы командой разработки платформы «Каркас» и предоставляют расширенные возможности для бота:
|
### Вручную
|
||||||
|
|
||||||
- [`yandexgpt`](src/karkas_blocks/karkas_blocks/external/yandexgpt/README.md) — блок для интеграции с нейросетью YandexGPT;
|
## Технологический стек
|
||||||
- [`create_report_apps`](src/karkas_blocks/karkas_blocks/external/create_report_apps/README.md) — блок для создания отчётов об ошибках.
|
|
||||||
|
* Python 3.11.6 или выше - основной язык программирования.
|
||||||
|
* SQLite 3 - база данных для хранения информации о чате и пользователях.
|
||||||
|
* [Poetry](https://gitflic.ru/project/armatik/ocab/blob?file=how-to%20install%20deps.md&branch=OCAB-V2) - менеджер зависимостей.
|
||||||
|
* aiogram 3 - библиотека для работы с Telegram API.
|
||||||
|
* peewee - ORM для работы с базой данных.
|
@ -1,105 +0,0 @@
|
|||||||
# Спецификация блоков
|
|
||||||
|
|
||||||
> **Внимание!**
|
|
||||||
>
|
|
||||||
> Данная спецификация ещё не закончена и активно разрабатывается.
|
|
||||||
>
|
|
||||||
> Могут возникнуть изменения, которые не будут обратно совместимы (breaking changes).
|
|
||||||
|
|
||||||
Каждый блок представлен в виде папки, содержащей два обязательных файла: `info.json` и `__init__.py`.
|
|
||||||
|
|
||||||
## Метаданные блока (`info.json`)
|
|
||||||
|
|
||||||
Файл `info.json` содержит информацию о блоке в формате JSON. Пример структуры `info.json` приведён ниже:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "standard.info",
|
|
||||||
"name": "Info",
|
|
||||||
"description": "Блок с информацией",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.roles": "^1.0.0",
|
|
||||||
"standard.database": {
|
|
||||||
"version": "^1.0.0",
|
|
||||||
"uses": ["db_api"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
"external.yandexgpt": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pythonDependencies": {
|
|
||||||
"required": {
|
|
||||||
"some_package": "^1.2.3"
|
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
"another_package": "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Поле | Описание |
|
|
||||||
| :-----------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `id` | Уникальный идентификатор блока |
|
|
||||||
| `name` | Название блока |
|
|
||||||
| `description` | Описание функциональности блока |
|
|
||||||
| `author` | Автор блока |
|
|
||||||
| `version` | Версия блока в формате [SemVer](https://semver.org/) |
|
|
||||||
| `privileged` | Является ли блок привилегированным (булево значение) |
|
|
||||||
| `dependencies` | Объект, описывающий зависимости блока от других блоков |
|
|
||||||
| `dependencies.required` и `dependencies.optional` | Объекты, описывающий обязательные и необязательные зависимости соответственно. Ключ — идентификатор блока, значение — версия или объект `DependencyInfo`. |
|
|
||||||
| `pythonDependencies` | Объект, описывающий зависимости блока от внешних Python пакетов. |
|
|
||||||
| `pythonDependencies.required` и `pythonDependencies.optional` | Объекты, описывающий обязательные и необязательные зависимости соответственно. Ключ — название пакета, значение — версия. |
|
|
||||||
|
|
||||||
### DependencyInfo
|
|
||||||
|
|
||||||
Объект `DependencyInfo` позволяет указать не только версию зависимости, но и список доступных к использованию атрибутов блока (`uses`). Если `uses` не указан, то доступ к блоку целиком запрещён. Пример объекта `DependencyInfo`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "^1.0.0",
|
|
||||||
"uses": ["db_api"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Поле | Описание |
|
|
||||||
| :-------: | ----------------------------------- |
|
|
||||||
| `version` | Версия блока |
|
|
||||||
| `uses` | Список используемых атрибутов блока |
|
|
||||||
|
|
||||||
## Режимы работы блоков
|
|
||||||
|
|
||||||
### Непривилегированный режим (`privileged: false`)
|
|
||||||
|
|
||||||
- Исполнение блока происходит в доверенной среде на основе `RestrictedPython`, что накладывает ряд ограничений;
|
|
||||||
- Может импортировать только явно разрешенные блоки, указанные в `pythonDependencies`, а также несколько стандартных блоков, необходимых для работы;
|
|
||||||
- Имеет доступ к пакету `karkas_core.modules_system.public_api` для взаимодействия с ботом.
|
|
||||||
|
|
||||||
### Привилегированный режим (`privileged: true`)
|
|
||||||
|
|
||||||
- Блок исполняется без ограничений;
|
|
||||||
- Имеет полный доступ ко всем пакетам, доступным в окружении;
|
|
||||||
- Должен использоваться с осторожностью и только для блоков, требующих расширенных прав.
|
|
||||||
|
|
||||||
## Жизненный цикл блока
|
|
||||||
|
|
||||||
1. Загрузка метаданных из `info.json`;
|
|
||||||
2. Проверка зависимостей:
|
|
||||||
- Проверка всех обязательных зависимостей;
|
|
||||||
- Проверка совместимости версий зависимостей;
|
|
||||||
- Проверка зависимостей Python;
|
|
||||||
3. Загрузка кода блока из `__init__.py`;
|
|
||||||
4. Вызов функции `module_init`, если она есть;
|
|
||||||
5. После загрузки всех блоков вызывается функция `module_late_init`, если она есть.
|
|
||||||
|
|
||||||
## Межблочное взаимодейтвие
|
|
||||||
|
|
||||||
Для блоков взаимодействия друг с другом описан [API](../src/karkas_core/karkas_core/modules_system/public_api/__init__.py),
|
|
||||||
предоставляемое системой управления блоками.
|
|
||||||
|
|
||||||
Например, можно использовать функцию `get_module` для получения блока или предоставляемых им объекты по идентификатору.
|
|
42
docs/DEV.md
42
docs/DEV.md
@ -1,42 +0,0 @@
|
|||||||
# Настройка рабочего окружения
|
|
||||||
|
|
||||||
Данная инструкция поможет вам настроить рабочее окружение для разработки Karkas.
|
|
||||||
|
|
||||||
## Предварительные требования
|
|
||||||
|
|
||||||
- **Python** — платформа «Каркас» требует интерпретатор языка Python версии 3.12;
|
|
||||||
- **VSCode** — рекомендованная среда разработки;
|
|
||||||
- **Git** — инструмент контроля версий, необходим клонирования репозиториянео.
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
1. Клонируйте репозиторий с помощью утилиты `git`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
git clone https://gitflic.ru/project/alt-gnome/karkas.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Откройте папку `karkas` в VSCode. Среда разработки автоматически предложит открыть проект как Workspace, используя файл `karkas.code-workspace`. Нажмите `Открыть Workspace`, чтобы принять предложение;
|
|
||||||
|
|
||||||
3. Установите инструмент Poetry, следуя инструкциям из [официальной документации](https://python-poetry.org/docs/).
|
|
||||||
|
|
||||||
4. Выполните команду `poetry install` в корне проекта.
|
|
||||||
|
|
||||||
5. Выполните команду `soruce ./.venv/bin/activate` в корне проекта.
|
|
||||||
|
|
||||||
4. Выполните команду `pre-commit install -t commit-msg -t pre-commit` в корне проекта.
|
|
||||||
|
|
||||||
5. Для каждого пакета выполните следующую последовательность действий:
|
|
||||||
|
|
||||||
- Перейдите в папку пакета (например, `src/karkas_core`);
|
|
||||||
- Выполните команду `poetry install`, чтобы установить зависимости пакета.
|
|
||||||
|
|
||||||
6. Выполните команду `soruce ./.venv/bin/activate` в папке пакета, над которым будут производится работы, чтобы активировать виртуальное окружение.
|
|
||||||
|
|
||||||
Теперь рабочее окружение настроено!
|
|
||||||
|
|
||||||
## Дополнительная информация
|
|
||||||
|
|
||||||
- Каждый пакет в монорепозитории имеет свой собственный файл `pyproject.toml`, где указаны его зависимости;
|
|
||||||
- Poetry автоматически управляет виртуальными окружениями для каждого пакета;
|
|
||||||
- Вы можете использовать команду `poetry add <package_name>` (где `<package_name>` — имя пакета) для добавления новых зависимостей.
|
|
@ -1,48 +0,0 @@
|
|||||||
stages:
|
|
||||||
- lint
|
|
||||||
- build
|
|
||||||
|
|
||||||
lint-pre-commit:
|
|
||||||
stage: lint
|
|
||||||
image: python:3.12-bullseye
|
|
||||||
before_script:
|
|
||||||
- export PIP_CACHE_DIR=$(pwd)/.cache/pip
|
|
||||||
- export PRE_COMMIT_HOME=$(pwd)/.cache/pre-commit
|
|
||||||
- pip install pre-commit
|
|
||||||
scripts:
|
|
||||||
- pre-commit run --all-files
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .cache/
|
|
||||||
|
|
||||||
.docker-dev-build-template:
|
|
||||||
before_script:
|
|
||||||
- docker info
|
|
||||||
- docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
|
|
||||||
scripts:
|
|
||||||
- |
|
|
||||||
cd ./src/${KARKAS_PROJECT}
|
|
||||||
export APP_VERSION=$(echo "$(git show -s --format=%ad --date=format:%Y.%m.%d $CI_COMMIT_SHA) (sha-$(git rev-parse --short=8 $CI_COMMIT_SHA))")
|
|
||||||
export IMAGE_COMMIT=${IMAGE_NAME}:${CI_COMMIT_SHA}
|
|
||||||
export IMAGE_BRANCH=${IMAGE_NAME}:$(echo $CI_COMMIT_REF_NAME | sed 's/[^a-zA-Z0-9]/-/g')
|
|
||||||
docker build --build-arg APP_VERSION="$APP_VERSION" -t ${IMAGE_COMMIT} -t ${IMAGE_BRANCH} -f Dockerfile ../..
|
|
||||||
docker push ${IMAGE_COMMIT}
|
|
||||||
docker push ${IMAGE_BRANCH}
|
|
||||||
|
|
||||||
build-gnomik:
|
|
||||||
stage: build
|
|
||||||
image: docker:27.1.2
|
|
||||||
variables:
|
|
||||||
CI_REGISTRY: registry.gitflic.ru
|
|
||||||
IMAGE_NAME: registry.gitflic.ru/project/alt-gnome/karkas/gnomik
|
|
||||||
KARKAS_PROJECT: gnomik
|
|
||||||
extends: .docker-dev-build-template
|
|
||||||
|
|
||||||
build-karkas-lite:
|
|
||||||
stage: build
|
|
||||||
image: docker:27.1.2
|
|
||||||
variables:
|
|
||||||
CI_REGISTRY: registry.gitflic.ru
|
|
||||||
IMAGE_NAME: registry.gitflic.ru/project/alt-gnome/karkas/karkas-lite
|
|
||||||
KARKAS_PROJECT: karkas_lite
|
|
||||||
extends: .docker-dev-build-template
|
|
47
how-to install deps.md
Normal file
47
how-to install deps.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
## Poetry
|
||||||
|
|
||||||
|
### Установка с официального сайта
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Установка с PyPi
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -m pip install poetry
|
||||||
|
```
|
||||||
|
|
||||||
|
Доп информация:https://www.8host.com/blog/ustanovka-menedzhera-zavisimostej-poetry/
|
||||||
|
|
||||||
|
## Зависимости
|
||||||
|
|
||||||
|
### Добавление зависимости
|
||||||
|
```shell
|
||||||
|
poetry add NAME
|
||||||
|
```
|
||||||
|
`NAME` - название зависимости.
|
||||||
|
|
||||||
|
### Установка зависимостей
|
||||||
|
```shell
|
||||||
|
poetry install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обновление зависимостей
|
||||||
|
```shell
|
||||||
|
poetry update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Виртуальное окружение
|
||||||
|
|
||||||
|
### Создание/активация
|
||||||
|
```shell
|
||||||
|
poetry shell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка
|
||||||
|
|
||||||
|
Хранить окружение внутри проекта
|
||||||
|
```shell
|
||||||
|
poetry config virtualenvs.in-project true
|
||||||
|
```
|
15
init.py
Normal file
15
init.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
pwd = Path().cwd()
|
||||||
|
dir_core = pwd / "src" / "core"
|
||||||
|
dir_modules_standard = pwd / "src" / "modules" / "standard"
|
||||||
|
dir_modules_custom = pwd / "src" / "modules" / "custom"
|
||||||
|
|
||||||
|
json = {
|
||||||
|
'core': str(dir_core),
|
||||||
|
'modules standard': str(dir_modules_standard),
|
||||||
|
'modules custom': str(dir_modules_custom),
|
||||||
|
}
|
||||||
|
with open("src/paths.json", "w", encoding="utf8") as f:
|
||||||
|
f.write(dumps(json, indent=4))
|
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"name": "Karkas Monorepo Root",
|
|
||||||
"path": ".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Karkas Blocks",
|
|
||||||
"path": "src/karkas_blocks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Karkas Core",
|
|
||||||
"path": "src/karkas_core"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Karkas Piccolo",
|
|
||||||
"path": "src/karkas_piccolo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Gnomik",
|
|
||||||
"path": "src/gnomik"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Karkas Lite",
|
|
||||||
"path": "src/karkas_lite"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extensions": {
|
|
||||||
"recommendations": [
|
|
||||||
"ms-python.python"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
1046
poetry.lock
generated
1046
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
|||||||
[virtualenvs]
|
|
||||||
in-project = true
|
|
@ -1,48 +1,24 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "karkas-monorepo"
|
name = "ocab"
|
||||||
version = "2.0.0"
|
version = "0.1.0"
|
||||||
description = "Karkas is a modular Telegram bot"
|
description = ""
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
authors = ["Семён Фомченков <s.fomchenkov@yandex.ru>"]
|
authors = ["Семён Фомченков <s.fomchenkov@yandex.ru>"]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
"Илья Женецкий <ilya_zhenetskij@vk.com>",
|
"Илья Женецкий <ilya_zhenetskij@vk.com>",
|
||||||
"qualimock <qualimock@yandex.ru>",
|
"qualimock <qualimock@yandex.ru>",
|
||||||
"Кирилл Уницаев <fiersik.kouji@yandex.ru>",
|
"Кирилл Уницаев fiersik.kouji@yandex.ru",
|
||||||
"Максим Слипенко <maxim@slipenko.com>"
|
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://gitflic.ru/project/alt-gnome/karkas"
|
repository = "https://gitflic.ru/project/armatik/ocab"
|
||||||
packages = [
|
|
||||||
{ include = "scripts" }
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.poetry.urls]
|
|
||||||
"Bug Tracker" = "https://gitflic.ru/project/alt-gnome/karkas/issue?status=OPEN"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
|
||||||
test = 'scripts.test:main'
|
|
||||||
init = 'scripts.init:main'
|
|
||||||
module = 'scripts.module:main'
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.10,<3.13"
|
python = "^3.11.6"
|
||||||
|
aiogram = "^3.2.0"
|
||||||
|
peewee = "^3.17.0"
|
||||||
|
pyyaml = "^6.0.1"
|
||||||
|
requests = "^2.31.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
flake8 = "^7.1.0"
|
|
||||||
black = "^24.4.2"
|
|
||||||
isort = "^5.13.2"
|
|
||||||
bandit = "^1.7.9"
|
|
||||||
pre-commit = "^3.7.1"
|
|
||||||
semver = "^3.0.2"
|
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 88
|
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
line_length = 88
|
|
||||||
multi_line_output = 3
|
|
||||||
skip_gitignore = true
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
4
run_tests
Normal file
4
run_tests
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
cd src
|
||||||
|
python -m unittest discover -v
|
||||||
|
cd ..
|
@ -1,21 +0,0 @@
|
|||||||
from json import dumps
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
pwd = Path().cwd()
|
|
||||||
dir_core = pwd / "src" / "karkas_core"
|
|
||||||
dir_modules_standard = pwd / "src" / "karkas_blocks" / "standard"
|
|
||||||
dir_modules_external = pwd / "src" / "karkas_blocks" / "external"
|
|
||||||
|
|
||||||
json = {
|
|
||||||
"core": str(dir_core),
|
|
||||||
"modules standard": str(dir_modules_standard),
|
|
||||||
"modules external": str(dir_modules_external),
|
|
||||||
}
|
|
||||||
with open("src/paths.json", "w", encoding="utf8") as f:
|
|
||||||
f.write(dumps(json, indent=4))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,115 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
DEFAULTS = {
|
|
||||||
"description": "Очень полезный модуль",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": "false",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def create_module(args):
|
|
||||||
module_dir = os.path.join("src/karkas_blocks/standard", args.module_name)
|
|
||||||
os.makedirs(module_dir, exist_ok=True)
|
|
||||||
|
|
||||||
module_info = {
|
|
||||||
"id": args.id,
|
|
||||||
"name": args.name,
|
|
||||||
"description": args.description,
|
|
||||||
"author": args.author,
|
|
||||||
"version": args.version,
|
|
||||||
"privileged": args.privileged.lower() == "true",
|
|
||||||
"dependencies": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(os.path.join(module_dir, "info.json"), "w", encoding="utf-8") as f:
|
|
||||||
json.dump(module_info, f, ensure_ascii=False, indent=4)
|
|
||||||
|
|
||||||
with open(os.path.join(module_dir, "__init__.py"), "w", encoding="utf-8") as f:
|
|
||||||
f.write("# Init file for the module\n")
|
|
||||||
|
|
||||||
print(f"Module {args.module_name} created successfully.")
|
|
||||||
|
|
||||||
|
|
||||||
def interactive_mode(args):
|
|
||||||
def get_input(prompt, default=None):
|
|
||||||
if default:
|
|
||||||
value = input(f"{prompt} [{default}]: ")
|
|
||||||
return value if value else default
|
|
||||||
else:
|
|
||||||
value = input(f"{prompt}: ")
|
|
||||||
return value
|
|
||||||
|
|
||||||
module_name = get_input("Введите название модуля (папки)")
|
|
||||||
module_id = get_input("Введите ID")
|
|
||||||
name = get_input("Введите название модуля")
|
|
||||||
description = get_input(
|
|
||||||
"Введите описание модуля", args.description or DEFAULTS["description"]
|
|
||||||
)
|
|
||||||
author = get_input("Введите автора", args.author or DEFAULTS["author"])
|
|
||||||
version = get_input("Введите версию", args.version or DEFAULTS["version"])
|
|
||||||
privileged = get_input(
|
|
||||||
"Модуль привилегированный (true/false)",
|
|
||||||
args.privileged or DEFAULTS["privileged"],
|
|
||||||
)
|
|
||||||
|
|
||||||
args = argparse.Namespace(
|
|
||||||
command="create",
|
|
||||||
module_name=module_name,
|
|
||||||
id=module_id,
|
|
||||||
name=name,
|
|
||||||
description=description,
|
|
||||||
author=author,
|
|
||||||
version=version,
|
|
||||||
privileged=privileged,
|
|
||||||
dependencies="",
|
|
||||||
)
|
|
||||||
|
|
||||||
create_module(args)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Утилита для создания директории модуля с файлами."
|
|
||||||
)
|
|
||||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
||||||
|
|
||||||
create_parser = subparsers.add_parser("create", help="Создать новый модуль")
|
|
||||||
create_parser.add_argument("--module_name", help="Название директории модуля")
|
|
||||||
create_parser.add_argument("--id", help="ID модуля")
|
|
||||||
create_parser.add_argument("--name", help="Название модуля")
|
|
||||||
create_parser.add_argument("--description", help="Описание модуля")
|
|
||||||
create_parser.add_argument("--author", help="Автор модуля")
|
|
||||||
create_parser.add_argument("--version", help="Версия модуля")
|
|
||||||
create_parser.add_argument(
|
|
||||||
"--privileged", help="Привилегированный модуль (true/false)"
|
|
||||||
)
|
|
||||||
create_parser.add_argument(
|
|
||||||
"--dependencies", help="Список зависимостей в формате имя:версия через запятую"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.command == "create":
|
|
||||||
if not all(
|
|
||||||
[
|
|
||||||
args.module_name,
|
|
||||||
args.id,
|
|
||||||
args.name,
|
|
||||||
args.description,
|
|
||||||
args.author,
|
|
||||||
args.version,
|
|
||||||
args.privileged,
|
|
||||||
args.dependencies,
|
|
||||||
]
|
|
||||||
):
|
|
||||||
print("Переход в интерактивный режим...")
|
|
||||||
interactive_mode(args)
|
|
||||||
else:
|
|
||||||
create_module(args)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,9 +0,0 @@
|
|||||||
import subprocess # nosec
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
subprocess.run(["python", "-u", "-m", "unittest", "discover"]) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
2
src/__init__.py
Normal file
2
src/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import src.service
|
||||||
|
import src.core
|
13
src/core/config.yaml
Normal file
13
src/core/config.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
TELEGRAM:
|
||||||
|
TOKEN:
|
||||||
|
|
||||||
|
YANDEXGPT:
|
||||||
|
TOKEN:
|
||||||
|
CATALOGID:
|
||||||
|
PROMPT:
|
||||||
|
|
||||||
|
ROLES:
|
||||||
|
ADMIN: 0
|
||||||
|
MODERATOR: 1
|
||||||
|
USER: 2
|
||||||
|
BOT: 3
|
21
src/core/example_config.yaml
Normal file
21
src/core/example_config.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
TELEGRAM:
|
||||||
|
TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
APPROVED_CHAT_ID: "-123456789 | -012345678"
|
||||||
|
ADMINCHATID: -12345678
|
||||||
|
DEFAULT_CHAT_TAG: "@alt_gnome_chat"
|
||||||
|
CHECK_BOT: True
|
||||||
|
|
||||||
|
YANDEXGPT:
|
||||||
|
TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
TOKEN_FOR_REQUEST: 8000
|
||||||
|
TOKEN_FOR_ANSWER: 2000
|
||||||
|
CATALOGID: xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
PROMPT: "Ты чат-бот ..."
|
||||||
|
STARTWORD: "Бот| Бот, | бот | бот,"
|
||||||
|
INWORD: "помогите | не работает"
|
||||||
|
|
||||||
|
ROLES:
|
||||||
|
ADMIN: 2
|
||||||
|
MODERATOR: 1
|
||||||
|
USER: 0
|
||||||
|
BOT: 3
|
24
src/core/logger.py
Normal file
24
src/core/logger.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
async def check_log_file():
|
||||||
|
# Проверка наличия файла для логов в формате log-dd-mm-yyyy.log
|
||||||
|
# Если файл существует, то pass
|
||||||
|
# Если файл не существует, то создаём его. файл лежит в директории src.core.log
|
||||||
|
current_data = time.strftime("%d-%m-%Y")
|
||||||
|
log_file = os.path.join(os.path.dirname(__file__), "log/", f"log-{current_data}.log")
|
||||||
|
if not os.path.exists(log_file):
|
||||||
|
with open(log_file, 'w') as file:
|
||||||
|
file.write("Log file created\n")
|
||||||
|
file.close()
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def log(message):
|
||||||
|
await check_log_file()
|
||||||
|
current_data = time.strftime("%d-%m-%Y")
|
||||||
|
log_file = os.path.join(os.path.dirname(__file__), "log/", f"log-{current_data}.log")
|
||||||
|
# print(log_file)
|
||||||
|
with open(log_file, 'a') as file:
|
||||||
|
file.write(f"{time.strftime('%H:%M:%S')} {message}\n")
|
||||||
|
file.close()
|
26
src/core/main.py
Normal file
26
src/core/main.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from routers import include_routers
|
||||||
|
from src.modules.standard.config.config import get_telegram_token
|
||||||
|
from src.modules.standard.database.db_api import connect_database, create_tables
|
||||||
|
|
||||||
|
from asyncio import run
|
||||||
|
from aiogram import Bot, Dispatcher
|
||||||
|
|
||||||
|
|
||||||
|
async def main(bot: Bot):
|
||||||
|
try:
|
||||||
|
database, path = connect_database()
|
||||||
|
database.connect()
|
||||||
|
create_tables(database)
|
||||||
|
|
||||||
|
dp = Dispatcher()
|
||||||
|
await include_routers(dp)
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
await bot.session.close()
|
||||||
|
database.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
bot = Bot(token=get_telegram_token())
|
||||||
|
run(main(bot))
|
18
src/core/routers.py
Normal file
18
src/core/routers.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from aiogram import Dispatcher, Router, F, Bot
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
from src.modules.standard.info.routers import router as info_router
|
||||||
|
from src.modules.standard.admin.routers import router as admin_router
|
||||||
|
from src.modules.standard.message_processing.message_api import router as process_message
|
||||||
|
from src.modules.standard.welcome.routers import router as welcome_router
|
||||||
|
|
||||||
|
|
||||||
|
async def include_routers(dp: Dispatcher):
|
||||||
|
"""
|
||||||
|
Подключение роутеров в бота
|
||||||
|
dp.include_router()
|
||||||
|
"""
|
||||||
|
dp.include_router(info_router)
|
||||||
|
dp.include_router(admin_router)
|
||||||
|
dp.include_router(process_message)
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
FROM python:3.12-slim AS dependencies_installer
|
|
||||||
|
|
||||||
RUN pip install poetry
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY ./src/karkas_core/poetry* ./src/karkas_core/pyproject.toml /app/src/karkas_core/
|
|
||||||
COPY ./src/karkas_blocks/poetry* ./src/karkas_blocks/pyproject.toml /app/src/karkas_blocks/
|
|
||||||
COPY ./src/karkas_piccolo/poetry* ./src/karkas_piccolo/pyproject.toml /app/src/karkas_piccolo/
|
|
||||||
COPY ./src/gnomik/poetry* ./src/gnomik/pyproject.toml /app/src/gnomik/
|
|
||||||
|
|
||||||
WORKDIR /app/src/gnomik
|
|
||||||
|
|
||||||
RUN poetry install --no-root --no-directory
|
|
||||||
|
|
||||||
FROM python:3.12-slim AS src
|
|
||||||
|
|
||||||
COPY ./src/karkas_core /app/src/karkas_core
|
|
||||||
COPY ./src/karkas_blocks /app/src/karkas_blocks
|
|
||||||
COPY ./src/karkas_piccolo /app/src/karkas_piccolo
|
|
||||||
COPY ./src/gnomik /app/src/gnomik
|
|
||||||
|
|
||||||
FROM python:3.12-slim AS local_dependencies_installer
|
|
||||||
|
|
||||||
RUN pip install poetry
|
|
||||||
|
|
||||||
COPY --from=dependencies_installer /app/src/gnomik/.venv /app/src/gnomik/.venv
|
|
||||||
COPY --from=src /app/src/ /app/src/
|
|
||||||
WORKDIR /app/src/gnomik
|
|
||||||
RUN poetry install
|
|
||||||
|
|
||||||
FROM python:3.12-slim AS base
|
|
||||||
COPY --from=local_dependencies_installer /app/src/gnomik/.venv /app/src/gnomik/.venv
|
|
||||||
COPY --from=src /app/src/ /app/src/
|
|
||||||
WORKDIR /app/src/gnomik
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
CMD ["/bin/bash", "-c", ". .venv/bin/activate && python -m gnomik"]
|
|
@ -1,14 +0,0 @@
|
|||||||
**/Dockerfile
|
|
||||||
**/*.dockerignore
|
|
||||||
**/docker-compose.yml
|
|
||||||
|
|
||||||
**/.git
|
|
||||||
**/.gitignore
|
|
||||||
|
|
||||||
**/.venv
|
|
||||||
|
|
||||||
**/.mypy_cache
|
|
||||||
**/__pycache__/
|
|
||||||
|
|
||||||
src/gnomik/config.yaml
|
|
||||||
src/gnomik/database/*
|
|
@ -1,60 +0,0 @@
|
|||||||
# Gnomик
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Gnomик — это чат-бот для [ALT Gnome Chat](https://t.me/alt_gnome_chat), созданный на основе платформы «Каркас». Он предоставляет различные функции и возможности, помогающие членам сообщества ALT Gnome.
|
|
||||||
|
|
||||||
> ALT Gnome — открытое сообщество пользователей операционной системы ALT Regular Gnome. Платформы ALT Gnome:
|
|
||||||
>
|
|
||||||
> - [Wiki](https://alt-gnome.wiki)
|
|
||||||
> - [Telegram-канал](https://t.me/alt_gnome)
|
|
||||||
> - [Вконтакте](https://vk.com/alt_gnome)
|
|
||||||
> - [Rutube](https://rutube.ru/channel/32425669/)
|
|
||||||
> - [Платформа](https://plvideo.ru/@alt_gnome)
|
|
||||||
> - [Boosty](https://boosty.to/alt_gnome)
|
|
||||||
|
|
||||||
## Функционал
|
|
||||||
|
|
||||||
<!--
|
|
||||||
TODO: описать функционал
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Запуск
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
1. Соберите Docker-образ:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker build -t gnomik .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Запустите контейнер:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run -p 9000:9000 -v ./config.yaml:/app/config.yaml -v ./database:/app/database gnomik
|
|
||||||
```
|
|
||||||
|
|
||||||
Замените `./config.yaml` и `./database` на пути к локальным файлам конфигурации и пакам для базы данных.
|
|
||||||
|
|
||||||
### Вручную
|
|
||||||
|
|
||||||
1. Активируйте виртуальное окружение Gnomика:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
poetry shell
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Запустите бота:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
python -m gnomik
|
|
||||||
```
|
|
||||||
|
|
||||||
## Конфигурация
|
|
||||||
|
|
||||||
Конфигурация бота находится в файле `config.yaml`.
|
|
||||||
|
|
||||||
## Блоки
|
|
||||||
|
|
||||||
Список загружаемых блоков указан в файле `__main__.py`.
|
|
@ -1,18 +0,0 @@
|
|||||||
core:
|
|
||||||
mode: WEBHOOK
|
|
||||||
token: xxx
|
|
||||||
webhook:
|
|
||||||
public_url: xxx
|
|
||||||
filters:
|
|
||||||
approved_chat_id: -4128011756 | -4128011756
|
|
||||||
default_chat_tag: '@alt_gnome_chat'
|
|
||||||
miniapp:
|
|
||||||
public_url: xxx
|
|
||||||
yandexgpt:
|
|
||||||
catalogid: xxx
|
|
||||||
inword: помогите | не работает
|
|
||||||
prompt: Ты чат-бот ...
|
|
||||||
startword: Бот| Бот, | бот | бот,
|
|
||||||
token: xxx
|
|
||||||
token_for_answer: 2000
|
|
||||||
token_for_request: 8000
|
|
@ -1,12 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: ../..
|
|
||||||
dockerfile: src/gnomik/Dockerfile
|
|
||||||
ports:
|
|
||||||
- 9000:9000
|
|
||||||
volumes:
|
|
||||||
- ./config.yaml:/app/config.yaml
|
|
||||||
- ./database:/app/database
|
|
Binary file not shown.
Before Width: | Height: | Size: 61 KiB |
@ -1,33 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
|
|
||||||
from karkas_blocks import block_loader
|
|
||||||
from karkas_core import Karkas
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
ocab = Karkas()
|
|
||||||
await ocab.init_app(
|
|
||||||
[
|
|
||||||
block_loader("standard", "config", safe=False),
|
|
||||||
block_loader("standard", "filters", safe=False),
|
|
||||||
block_loader("standard", "database", safe=False),
|
|
||||||
block_loader("standard", "statistics", safe=False),
|
|
||||||
block_loader("standard", "chats", safe=False),
|
|
||||||
block_loader("standard", "users", safe=False),
|
|
||||||
block_loader("standard", "command_helper"),
|
|
||||||
block_loader("standard", "roles", safe=False),
|
|
||||||
block_loader("standard", "fsm_database_storage", safe=False),
|
|
||||||
block_loader("external", "create_report_apps"),
|
|
||||||
block_loader("standard", "info"),
|
|
||||||
block_loader("standard", "help"),
|
|
||||||
# block_loader("external", "yandexgpt", safe=False),
|
|
||||||
#
|
|
||||||
# block_loader("standard", "admin"),
|
|
||||||
# block_loader("standard", "message_processing"),
|
|
||||||
# block_loader("standard", "miniapp", safe=False),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
await ocab.start()
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
|
3134
src/gnomik/poetry.lock
generated
3134
src/gnomik/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
|||||||
[virtualenvs]
|
|
||||||
in-project = true
|
|
@ -1,21 +0,0 @@
|
|||||||
[tool.poetry]
|
|
||||||
name = "gnomik"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = ""
|
|
||||||
authors = ["Максим Слипенко <maxim@slipenko.com>"]
|
|
||||||
readme = "README.md"
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = ">=3.10,<3.13"
|
|
||||||
karkas-core = { extras=["webhook"], path = "../karkas_core", develop = true }
|
|
||||||
karkas-blocks = { path = "../karkas_blocks", develop = true }
|
|
||||||
karkas-piccolo = { path = "../karkas_piccolo", develop = true }
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
[[tool.poetry.source]]
|
|
||||||
name = "pytorch-cpu"
|
|
||||||
url = "https://download.pytorch.org/whl/cpu"
|
|
||||||
priority = "explicit"
|
|
@ -1,10 +0,0 @@
|
|||||||
# Блоки Karkas
|
|
||||||
|
|
||||||
Блоки Karkas — это набор блоков для платформы «Каркас», которые добавляют функциональность ботам.
|
|
||||||
|
|
||||||
## Типы блоков
|
|
||||||
|
|
||||||
| Тип | Описание |
|
|
||||||
| :-------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| Стандартные (`standard`) | Блоки, содержащие основной функционал: управление пользователями, ролями и настройками |
|
|
||||||
| Дополнительные (`external`) | Блоки, созданные командой разработки платформы «Каркас». Предоставляют расширенные возможности: интеграция с нейросетями, внешними сервисами и API |
|
|
@ -1 +0,0 @@
|
|||||||
from .lib import block_loader
|
|
@ -1 +0,0 @@
|
|||||||
from . import yandexgpt
|
|
@ -1,21 +0,0 @@
|
|||||||
# Блок Create Report Apps
|
|
||||||
|
|
||||||
Данный блок предназначен для помощи пользователям в создании отчётов об ошибках в приложениях.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Задаёт пользователю ряд вопросов, необходимых для составления отчёта;
|
|
||||||
- Собирает информацию о системе пользователя;
|
|
||||||
- Формирует отчёт в текстовом формате.
|
|
||||||
|
|
||||||
## Команды
|
|
||||||
|
|
||||||
| Команда | Описание |
|
|
||||||
| :-------------------: | --------------- |
|
|
||||||
| `/create_report_apps` | Создание отчёта |
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
1. Отправьте команду `/create_report_apps` боту личным сообщением или в групповом чате;
|
|
||||||
2. Ответьте на вопросы бота;
|
|
||||||
3. Бот сформирует отчёт и отправит его.
|
|
@ -1 +0,0 @@
|
|||||||
from .main import module_init
|
|
@ -1,140 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram import Bot, Router
|
|
||||||
from aiogram.enums import ParseMode
|
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
|
||||||
from aiogram.types import (
|
|
||||||
BufferedInputFile,
|
|
||||||
KeyboardButton,
|
|
||||||
Message,
|
|
||||||
ReplyKeyboardMarkup,
|
|
||||||
ReplyKeyboardRemove,
|
|
||||||
)
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import Utils, get_fsm_context
|
|
||||||
|
|
||||||
from .report import Report
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from aiogram.fsm.context import FSMContext
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
class ReportState(StatesGroup):
|
|
||||||
input_system_info = State()
|
|
||||||
input_app_name = State()
|
|
||||||
input_problem_step_by_step = State()
|
|
||||||
input_actual_result = State()
|
|
||||||
input_expected_result = State()
|
|
||||||
input_additional_info = State()
|
|
||||||
|
|
||||||
|
|
||||||
system_info_code = """echo "SESSION_TYPE: ${XDG_SESSION_TYPE:-Unknown}"
|
|
||||||
[ -f /etc/os-release ] && grep "^PRETTY_NAME=" /etc/os-release | cut -d= -f2 \
|
|
||||||
| tr -d '"' | xargs echo "OS: "
|
|
||||||
echo "Kernel: $(uname -r)"
|
|
||||||
echo "DE: ${XDG_CURRENT_DESKTOP:-Unknown}"
|
|
||||||
grep "^model name" /proc/cpuinfo | head -n1 | cut -d: -f2 \
|
|
||||||
| xargs echo "CPU: "
|
|
||||||
lspci | grep "VGA compatible controller" | cut -d: -f3 \
|
|
||||||
| xargs -I{} echo "GPU: {}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
system_info_message = """Укажите параметры свой системы.
|
|
||||||
Собрать информацию о системе можно с помощью данного скрипта:
|
|
||||||
""" + Utils.code_format(
|
|
||||||
system_info_code,
|
|
||||||
"shell",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def start_report(chat_id: int, bot: Bot):
|
|
||||||
await bot.send_message(
|
|
||||||
chat_id=chat_id,
|
|
||||||
text=system_info_message,
|
|
||||||
parse_mode=ParseMode.HTML,
|
|
||||||
reply_markup=ReplyKeyboardRemove(),
|
|
||||||
)
|
|
||||||
state = await get_fsm_context(chat_id, chat_id)
|
|
||||||
|
|
||||||
await state.set_state(ReportState.input_system_info)
|
|
||||||
|
|
||||||
|
|
||||||
app_info_message = """Укажите название и версию приложения.
|
|
||||||
Узнать можно с помощью данной команды:""" + Utils.code_format(
|
|
||||||
"rpm -qa | grep -i НАЗВАНИЕ_ПРИЛОЖЕНИЯ", "shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_system_info)
|
|
||||||
async def system_entered(message: Message, state: "FSMContext"):
|
|
||||||
await state.update_data(system=message.text)
|
|
||||||
await message.answer(
|
|
||||||
text=app_info_message,
|
|
||||||
parse_mode=ParseMode.HTML,
|
|
||||||
)
|
|
||||||
await state.set_state(ReportState.input_app_name)
|
|
||||||
|
|
||||||
|
|
||||||
step_by_step_message = (
|
|
||||||
"""Опиши проблему пошагово, что ты делал, что происходило, что не так."""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_app_name)
|
|
||||||
async def app_name_entered(message: Message, state: "FSMContext"):
|
|
||||||
await state.update_data(app=message.text)
|
|
||||||
await message.answer(text=step_by_step_message)
|
|
||||||
await state.set_state(ReportState.input_problem_step_by_step)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_problem_step_by_step)
|
|
||||||
async def problem_step_by_step_entered(message: Message, state: "FSMContext"):
|
|
||||||
await state.update_data(problem_step_by_step=message.text)
|
|
||||||
await message.answer(text="Опиши, что произошло (фактический результат).")
|
|
||||||
await state.set_state(ReportState.input_actual_result)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_actual_result)
|
|
||||||
async def actual_result_entered(message: Message, state: "FSMContext"):
|
|
||||||
await state.update_data(actual=message.text)
|
|
||||||
await message.answer(text="Опиши ожидаемый результат.")
|
|
||||||
await state.set_state(ReportState.input_expected_result)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_expected_result)
|
|
||||||
async def expected_result_entered(message: Message, state: "FSMContext"):
|
|
||||||
await state.update_data(expected=message.text)
|
|
||||||
await message.answer(
|
|
||||||
text="Если есть дополнительная информация, то напиши ее.",
|
|
||||||
reply_markup=ReplyKeyboardMarkup(
|
|
||||||
resize_keyboard=True,
|
|
||||||
keyboard=[
|
|
||||||
[KeyboardButton(text="Дополнительной информации нет")],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
await state.set_state(ReportState.input_additional_info)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReportState.input_additional_info)
|
|
||||||
async def additional_info_entered(message: Message, state: "FSMContext"):
|
|
||||||
if message.text == "Дополнительной информации нет":
|
|
||||||
additional_info = ""
|
|
||||||
else:
|
|
||||||
additional_info = message.text
|
|
||||||
await state.update_data(additional=additional_info)
|
|
||||||
await message.answer(
|
|
||||||
text="Вот твой отчет сообщением, а также файлом:",
|
|
||||||
reply_markup=ReplyKeyboardRemove(),
|
|
||||||
)
|
|
||||||
data = await state.get_data()
|
|
||||||
|
|
||||||
report = Report(data)
|
|
||||||
file_report = report.export().encode()
|
|
||||||
|
|
||||||
await message.answer(text=report.export())
|
|
||||||
await message.answer_document(document=BufferedInputFile(file_report, "report.txt"))
|
|
||||||
await state.clear()
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "external.create_report_apps",
|
|
||||||
"name": "Create Report Apps",
|
|
||||||
"description": "Модуль для создания отчетов о ошибках в приложениях",
|
|
||||||
"author": [
|
|
||||||
"OCAB Team",
|
|
||||||
"Maxim Slipenko"
|
|
||||||
],
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"standard.command_helper": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
|
|
||||||
from aiogram import Bot, F, Router
|
|
||||||
from aiogram.exceptions import TelegramForbiddenError
|
|
||||||
from aiogram.filters import BaseFilter, Command, CommandStart
|
|
||||||
from aiogram.types import (
|
|
||||||
CallbackQuery,
|
|
||||||
InlineKeyboardButton,
|
|
||||||
InlineKeyboardMarkup,
|
|
||||||
Message,
|
|
||||||
)
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module, register_router
|
|
||||||
|
|
||||||
from .create_report import router as create_report_router
|
|
||||||
from .create_report import start_report
|
|
||||||
|
|
||||||
register_command = get_module("standard.command_helper", "register_command")
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
class ChatTypeFilter(BaseFilter):
|
|
||||||
def __init__(self, chat_type: Union[str, list]):
|
|
||||||
self.chat_type = chat_type
|
|
||||||
|
|
||||||
async def __call__(self, message: Message) -> bool:
|
|
||||||
if isinstance(self.chat_type, str):
|
|
||||||
return message.chat.type == self.chat_type
|
|
||||||
return message.chat.type in self.chat_type
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(
|
|
||||||
ChatTypeFilter(chat_type=["group", "supergroup"]), Command("create_report_apps")
|
|
||||||
)
|
|
||||||
async def create_report_apps_command_group(message: Message):
|
|
||||||
keyboard = InlineKeyboardMarkup(
|
|
||||||
inline_keyboard=[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="Да", callback_data=f"create_report:{message.from_user.id}"
|
|
||||||
),
|
|
||||||
InlineKeyboardButton(
|
|
||||||
text="Нет", callback_data=f"cancel_report:{message.from_user.id}"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
await message.answer(
|
|
||||||
"Я могу отправить тебе пару вопросов "
|
|
||||||
"для помощи в составлении репорта личными "
|
|
||||||
"сообщениями.",
|
|
||||||
reply_markup=keyboard,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(
|
|
||||||
ChatTypeFilter(chat_type=["private"]),
|
|
||||||
CommandStart(deep_link=True, magic=F.args == "create_report_apps"),
|
|
||||||
)
|
|
||||||
@router.message(ChatTypeFilter(chat_type=["private"]), Command("create_report_apps"))
|
|
||||||
async def create_report_apps_command(message: Message, bot: Bot):
|
|
||||||
await start_report(message.from_user.id, bot)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("cancel_report"))
|
|
||||||
async def cancel_report_callback(callback_query: CallbackQuery):
|
|
||||||
callback_user_id = int(callback_query.data.split(":")[1])
|
|
||||||
if callback_query.from_user.id != callback_user_id:
|
|
||||||
await callback_query.answer("Эта кнопка не для вас.", show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
await callback_query.message.delete()
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("create_report"))
|
|
||||||
async def create_report_callback(callback_query: CallbackQuery, bot: Bot):
|
|
||||||
callback_user_id = int(callback_query.data.split(":")[1])
|
|
||||||
if callback_query.from_user.id != callback_user_id:
|
|
||||||
await callback_query.answer("Эта кнопка не для вас.", show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
|
|
||||||
async def on_chat_unavailable():
|
|
||||||
await callback_query.message.edit_text(
|
|
||||||
"Я в личных сообщениях задам тебе вопросы "
|
|
||||||
"для помощи в составлении репорта. "
|
|
||||||
'Но перед этим ты должен нажать кнопку "Запустить"'
|
|
||||||
)
|
|
||||||
info = await bot.get_me()
|
|
||||||
await callback_query.answer(
|
|
||||||
url=f"https://t.me/{info.username}?start=create_report_apps"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
chat_member = await bot.get_chat_member(chat_id=user_id, user_id=user_id)
|
|
||||||
if chat_member.status != "left":
|
|
||||||
await start_report(user_id, bot)
|
|
||||||
await callback_query.message.edit_text(
|
|
||||||
"Я в личных сообщениях задам тебе "
|
|
||||||
"вопросы для помощи в составлении "
|
|
||||||
"репорта."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await on_chat_unavailable()
|
|
||||||
except TelegramForbiddenError:
|
|
||||||
await on_chat_unavailable()
|
|
||||||
|
|
||||||
|
|
||||||
async def module_init():
|
|
||||||
router.include_router(create_report_router)
|
|
||||||
|
|
||||||
register_router(router)
|
|
||||||
register_command("create_report_apps", "Написать репорт о приложении")
|
|
@ -1,59 +0,0 @@
|
|||||||
import aiogram
|
|
||||||
|
|
||||||
|
|
||||||
class ReportFormatter:
|
|
||||||
def __init__(self, html=True):
|
|
||||||
self.html = html
|
|
||||||
|
|
||||||
def bold(self, string):
|
|
||||||
if self.html:
|
|
||||||
return f"<b>{self.text(string)}</b>"
|
|
||||||
return self.text(string)
|
|
||||||
|
|
||||||
def text(self, string):
|
|
||||||
if self.html:
|
|
||||||
return aiogram.html.quote(string)
|
|
||||||
return string
|
|
||||||
|
|
||||||
|
|
||||||
class Report:
|
|
||||||
def __init__(self, data: dict):
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def export(self):
|
|
||||||
data = self.data
|
|
||||||
|
|
||||||
report = f"""
|
|
||||||
Стенд с ошибкой:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['system']}
|
|
||||||
|
|
||||||
Пакет:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['app']}
|
|
||||||
|
|
||||||
Шаги, приводящие к ошибке:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['problem_step_by_step']}
|
|
||||||
|
|
||||||
Фактический результат:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['actual']}
|
|
||||||
|
|
||||||
Ожидаемый результат:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['expected']}
|
|
||||||
"""
|
|
||||||
if data["additional"] != "":
|
|
||||||
report += f"""
|
|
||||||
Дополнительно:
|
|
||||||
==============================
|
|
||||||
|
|
||||||
{data['additional']}
|
|
||||||
"""
|
|
||||||
return report
|
|
@ -1,24 +0,0 @@
|
|||||||
# Блок YandexGPT
|
|
||||||
|
|
||||||
Данный блок интегрирует в бота нейросеть YandexGPT.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Позволяет боту отвечать на сообщения пользователей, используя YandexGPT;
|
|
||||||
- Строит линию контекста для нейросети, используя историю сообщений.
|
|
||||||
|
|
||||||
## Конфигурация
|
|
||||||
|
|
||||||
| Параметр | Описание |
|
|
||||||
| :--------------------: | --------------------------------------------------------------- |
|
|
||||||
| `yandexgpt::token` | API-ключ для доступа к YandexGPT |
|
|
||||||
| `yandexgpt::catalogid` | Идентификатор каталога YandexGPT |
|
|
||||||
| `yandexgpt::prompt` | Системная подсказка для YandexGPT |
|
|
||||||
| `yandexgpt::startword` | Слова, с которых должно начинаться сообщение, чтобы бот ответил |
|
|
||||||
| `yandexgpt::inword` | Слова, которые должны быть в сообщении, чтобы бот ответил |
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
1. Настройте конфигурационные параметры блока;
|
|
||||||
2. Отправьте боту сообщение, начинающееся со слов или содержащее слова, указанные в параметрах `startword` и `inword` соответственно;
|
|
||||||
3. Бот ответит на сообщение, генерируя текст с помощью YandexGPT.
|
|
@ -1,2 +0,0 @@
|
|||||||
from .handlers import answer_to_message
|
|
||||||
from .main import module_init
|
|
@ -1,47 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram import Bot
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module, log
|
|
||||||
|
|
||||||
from .yandexgpt import YandexGPT
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from karkas_blocks.standard.config import IConfig
|
|
||||||
from karkas_blocks.standard.database.db_api import add_message as IAddMessage
|
|
||||||
|
|
||||||
config: "IConfig" = get_module(
|
|
||||||
"standard.config",
|
|
||||||
"config",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_yandexgpt_catalog_id():
|
|
||||||
return config.get("yandexgpt::catalogid")
|
|
||||||
|
|
||||||
|
|
||||||
def get_yandexgpt_token():
|
|
||||||
return config.get("yandexgpt::token")
|
|
||||||
|
|
||||||
|
|
||||||
def get_yandexgpt_prompt():
|
|
||||||
return config.get("yandexgpt::prompt")
|
|
||||||
|
|
||||||
|
|
||||||
add_message: "IAddMessage" = get_module("standard.database", "db_api.add_message")
|
|
||||||
|
|
||||||
|
|
||||||
async def answer_to_message(message: Message, bot: Bot):
|
|
||||||
# print("answer_to_message")
|
|
||||||
log("answer_to_message")
|
|
||||||
yagpt = YandexGPT(get_yandexgpt_token(), get_yandexgpt_catalog_id())
|
|
||||||
text = message.text
|
|
||||||
prompt = get_yandexgpt_prompt()
|
|
||||||
# response = await yagpt.async_yandexgpt(system_prompt=prompt, input_messages=text)
|
|
||||||
response = await yagpt.yandexgpt_request(
|
|
||||||
chat_id=message.chat.id, message_id=message.message_id, type="yandexgpt"
|
|
||||||
)
|
|
||||||
reply = await message.reply(response, parse_mode="Markdown")
|
|
||||||
add_message(reply, message_ai_model="yandexgpt")
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "external.yandexgpt",
|
|
||||||
"name": "Yandex GPT",
|
|
||||||
"description": "Модуль для работы с Yandex GPT",
|
|
||||||
"author": "OCAB Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.config": "^1.0.0",
|
|
||||||
"standard.database": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pythonDependencies": {
|
|
||||||
"required": {
|
|
||||||
"aiohttp": "*",
|
|
||||||
"requests": "*",
|
|
||||||
"json": "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from karkas_blocks.standard.config import IConfig
|
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
|
||||||
|
|
||||||
|
|
||||||
def module_init():
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::token",
|
|
||||||
"password",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::token_for_request",
|
|
||||||
"int",
|
|
||||||
default_value=8000,
|
|
||||||
)
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::token_for_answer",
|
|
||||||
"int",
|
|
||||||
default_value=2000,
|
|
||||||
)
|
|
||||||
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::catalogid",
|
|
||||||
"password",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::prompt",
|
|
||||||
"string",
|
|
||||||
default_value="Ты чат-бот ...",
|
|
||||||
)
|
|
||||||
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::startword",
|
|
||||||
"string",
|
|
||||||
default_value="Бот| Бот, | бот | бот,",
|
|
||||||
)
|
|
||||||
|
|
||||||
config.register(
|
|
||||||
"yandexgpt::inword",
|
|
||||||
"string",
|
|
||||||
default_value="помогите | не работает",
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
from aiogram import F, Router
|
|
||||||
|
|
||||||
from .handlers import answer_to_message
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
# If the message starts with the word "Гномик" ("гномик") or responds to a bot message, `answer_to_message` is called
|
|
||||||
router.message.register(
|
|
||||||
answer_to_message, F.text.startswith("Гномик") | F.text.startswith("гномик")
|
|
||||||
)
|
|
@ -1,25 +0,0 @@
|
|||||||
import importlib
|
|
||||||
import os
|
|
||||||
|
|
||||||
from karkas_core.modules_system.loaders.fs_loader import FSLoader
|
|
||||||
from karkas_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)
|
|
||||||
|
|
||||||
|
|
||||||
karkas_blocks_path = get_module_directory("karkas_blocks")
|
|
||||||
|
|
||||||
|
|
||||||
def block_loader(namespace: str, module_name: str, safe=True):
|
|
||||||
if not safe:
|
|
||||||
return UnsafeFSLoader(f"{karkas_blocks_path}/{namespace}/{module_name}")
|
|
||||||
else:
|
|
||||||
return FSLoader(f"{karkas_blocks_path}/{namespace}/{module_name}")
|
|
@ -1,26 +0,0 @@
|
|||||||
# Блок Admin
|
|
||||||
|
|
||||||
Данный блок предоставляет администраторам и модераторам чата инструменты для управления.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Удаление сообщений;
|
|
||||||
- Получение ID чата.
|
|
||||||
|
|
||||||
## Команды
|
|
||||||
|
|
||||||
| Команда | Описание |
|
|
||||||
| :-------: | --------------------------------------------------------- |
|
|
||||||
| `/rm` | Удаление сообщения, ответом на которое отправлена команда |
|
|
||||||
| `/chatID` | Получение ID текущего чата |
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
Удаление сообщений:
|
|
||||||
|
|
||||||
1. Ответьте на сообщение, которое нужно удалить;
|
|
||||||
2. Отправьте команду `/rm`.
|
|
||||||
|
|
||||||
Получение ID чата:
|
|
||||||
|
|
||||||
1. Отправьте команду `/chatID`
|
|
@ -1 +0,0 @@
|
|||||||
from .main import module_init
|
|
@ -1,65 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram import Bot
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from karkas_blocks.standard.config import IConfig
|
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_chat_tag():
|
|
||||||
return config.get("filters::default_chat_tag")
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_message(message: Message, bot: Bot):
|
|
||||||
reply_message_id = message.reply_to_message.message_id
|
|
||||||
await bot.delete_message(message.chat.id, reply_message_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def error_access(message: Message, bot: Bot):
|
|
||||||
await message.reply("Вы не админ/модератор")
|
|
||||||
|
|
||||||
|
|
||||||
async def get_chat_id(message: Message, bot: Bot):
|
|
||||||
await message.reply(
|
|
||||||
f"ID данного чата: `{message.chat.id}`", parse_mode="MarkdownV2"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def chat_not_in_approve_list(message: Message, bot: Bot):
|
|
||||||
await message.reply(
|
|
||||||
f"Бот недоступен в данном чате, пожалуйста,"
|
|
||||||
f" обратитесь к администратору для добавления чата в список доступных или перейдите в чат "
|
|
||||||
f"{get_default_chat_tag()}"
|
|
||||||
)
|
|
||||||
await get_chat_id(message, bot)
|
|
||||||
|
|
||||||
|
|
||||||
async def mute_user(chat_id: int, user_id: int, time: int, bot: Bot):
|
|
||||||
# TODO: type variable using `typing`
|
|
||||||
mutePermissions = {
|
|
||||||
"can_send_messages": False, # bool | None
|
|
||||||
"can_send_audios": False, # bool | None
|
|
||||||
"can_send_documents": False, # bool | None
|
|
||||||
"can_send_photos": False, # bool | None
|
|
||||||
"can_send_videos": False, # bool | None
|
|
||||||
"can_send_video_notes": False, # bool | None
|
|
||||||
"can_send_voice_notes": False, # bool | None
|
|
||||||
"can_send_polls": False, # bool | None
|
|
||||||
"can_send_other_messages": False, # bool | None
|
|
||||||
"can_add_web_page_previews": False, # bool | None
|
|
||||||
"can_change_info": False, # bool | None
|
|
||||||
"can_invite_users": False, # bool | None
|
|
||||||
"can_pin_messages": False, # bool | None
|
|
||||||
"can_manage_topics": False, # bool | None
|
|
||||||
# **extra_data: Any
|
|
||||||
}
|
|
||||||
end_time = time + int(time.time())
|
|
||||||
await bot.restrict_chat_member(
|
|
||||||
chat_id, user_id, until_date=end_time, **mutePermissions
|
|
||||||
)
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.admin",
|
|
||||||
"name": "Admin",
|
|
||||||
"description": "Модуль для работы с админкой",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.filters": "^1.0.0",
|
|
||||||
"standard.roles": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
from karkas_core.modules_system.public_api import register_router
|
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
async def module_init():
|
|
||||||
register_router(router)
|
|
@ -1,27 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
from aiogram import F, Router
|
|
||||||
from aiogram.filters import Command
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module, log
|
|
||||||
|
|
||||||
from .handlers import (
|
|
||||||
chat_not_in_approve_list,
|
|
||||||
delete_message,
|
|
||||||
error_access,
|
|
||||||
get_chat_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
(ChatModerOrAdminFilter, ChatNotInApproveFilter) = get_module(
|
|
||||||
"standard.filters", ["ChatModerOrAdminFilter", "ChatNotInApproveFilter"]
|
|
||||||
)
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
# If message is not empty and the `ChatNotInApproveFilter` is applied, then the `chat_not_in_approve_list` function is called
|
|
||||||
router.message.register(chat_not_in_approve_list, ChatNotInApproveFilter(), F.text)
|
|
||||||
|
|
||||||
router.message.register(get_chat_id, ChatModerOrAdminFilter(), Command("chatID"))
|
|
||||||
|
|
||||||
router.message.register(delete_message, ChatModerOrAdminFilter(), Command("rm"))
|
|
||||||
router.message.register(error_access, Command("rm"))
|
|
||||||
router.message.register(error_access, Command("chatID"))
|
|
@ -1,15 +0,0 @@
|
|||||||
from karkas_core.modules_system.public_api import (
|
|
||||||
get_module,
|
|
||||||
register_outer_message_middleware,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .main import ChatsMiddleware
|
|
||||||
|
|
||||||
|
|
||||||
def module_init():
|
|
||||||
register_app_config = get_module("standard.database", "register_app_config")
|
|
||||||
from .db import APP_CONFIG
|
|
||||||
|
|
||||||
register_app_config(APP_CONFIG)
|
|
||||||
|
|
||||||
register_outer_message_middleware(ChatsMiddleware())
|
|
@ -1 +0,0 @@
|
|||||||
from .piccolo_app import APP_CONFIG
|
|
@ -1,15 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from karkas_piccolo.conf.apps import AppConfig
|
|
||||||
|
|
||||||
from .tables import ChatInfo
|
|
||||||
|
|
||||||
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
APP_CONFIG = AppConfig(
|
|
||||||
app_name="standard.chats",
|
|
||||||
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
|
|
||||||
table_classes=[ChatInfo],
|
|
||||||
migration_dependencies=[],
|
|
||||||
commands=[],
|
|
||||||
)
|
|
@ -1,104 +0,0 @@
|
|||||||
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
|
|
||||||
from piccolo.columns.column_types import Date, Integer, Text
|
|
||||||
from piccolo.columns.defaults.date import DateNow
|
|
||||||
from piccolo.columns.indexes import IndexMethod
|
|
||||||
|
|
||||||
ID = "2024-08-20T17:25:21:296396"
|
|
||||||
VERSION = "1.16.0"
|
|
||||||
DESCRIPTION = ""
|
|
||||||
|
|
||||||
|
|
||||||
async def forwards():
|
|
||||||
manager = MigrationManager(
|
|
||||||
migration_id=ID, app_name="standard.chats", description=DESCRIPTION
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_table(
|
|
||||||
class_name="ChatInfo", tablename="chat_info", schema=None, columns=None
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="ChatInfo",
|
|
||||||
tablename="chat_info",
|
|
||||||
column_name="chat_id",
|
|
||||||
db_column_name="chat_id",
|
|
||||||
column_class_name="Integer",
|
|
||||||
column_class=Integer,
|
|
||||||
params={
|
|
||||||
"default": 0,
|
|
||||||
"null": False,
|
|
||||||
"primary_key": True,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="ChatInfo",
|
|
||||||
tablename="chat_info",
|
|
||||||
column_name="chat_name",
|
|
||||||
db_column_name="chat_name",
|
|
||||||
column_class_name="Text",
|
|
||||||
column_class=Text,
|
|
||||||
params={
|
|
||||||
"default": "",
|
|
||||||
"null": False,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="ChatInfo",
|
|
||||||
tablename="chat_info",
|
|
||||||
column_name="chat_type",
|
|
||||||
db_column_name="chat_type",
|
|
||||||
column_class_name="Integer",
|
|
||||||
column_class=Integer,
|
|
||||||
params={
|
|
||||||
"default": 10,
|
|
||||||
"null": False,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="ChatInfo",
|
|
||||||
tablename="chat_info",
|
|
||||||
column_name="created_at",
|
|
||||||
db_column_name="created_at",
|
|
||||||
column_class_name="Date",
|
|
||||||
column_class=Date,
|
|
||||||
params={
|
|
||||||
"default": DateNow(),
|
|
||||||
"null": False,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
return manager
|
|
@ -1,9 +0,0 @@
|
|||||||
from piccolo.columns import Date, Integer, Text
|
|
||||||
from piccolo.table import Table
|
|
||||||
|
|
||||||
|
|
||||||
class ChatInfo(Table):
|
|
||||||
chat_id = Integer(primary_key=True)
|
|
||||||
chat_name = Text()
|
|
||||||
chat_type = Integer(default=10)
|
|
||||||
created_at = Date()
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.chats",
|
|
||||||
"name": "Чаты",
|
|
||||||
"description": "Очень полезный модуль",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": true,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.database": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
|
|
||||||
|
|
||||||
from aiogram import BaseMiddleware
|
|
||||||
|
|
||||||
from .db.tables import ChatInfo
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from aiogram.types import Chat, TelegramObject
|
|
||||||
|
|
||||||
|
|
||||||
async def update_chat_info(chat: "Chat"):
|
|
||||||
chat_name = chat.title if chat.type != "private" else ""
|
|
||||||
|
|
||||||
await ChatInfo.insert(
|
|
||||||
ChatInfo(
|
|
||||||
chat_name=chat_name,
|
|
||||||
)
|
|
||||||
).on_conflict(
|
|
||||||
action="DO UPDATE",
|
|
||||||
values=[
|
|
||||||
ChatInfo.chat_name,
|
|
||||||
],
|
|
||||||
).run()
|
|
||||||
|
|
||||||
|
|
||||||
class ChatsMiddleware(BaseMiddleware):
|
|
||||||
async def __call__(
|
|
||||||
self,
|
|
||||||
handler: Callable[["TelegramObject", Dict[str, Any]], Awaitable[Any]],
|
|
||||||
event: "TelegramObject",
|
|
||||||
data: Dict[str, Any],
|
|
||||||
) -> Any:
|
|
||||||
chat = event.chat
|
|
||||||
await update_chat_info(chat)
|
|
||||||
result = await handler(event, data)
|
|
||||||
return result
|
|
@ -1,23 +0,0 @@
|
|||||||
# Блок Command Helper
|
|
||||||
|
|
||||||
Данный блок упрощает регистрации команд бота и управления ими.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Регистрация команд бота;
|
|
||||||
- Установка команд для пользователей в зависимости от их роли.
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
1. Импортируйте функцию `register_command`;
|
|
||||||
2. Вызовите функцию `register_command`, передав ей название команды, её описание и роль пользователей, которым доступна она будет доступна.
|
|
||||||
|
|
||||||
## Пример
|
|
||||||
|
|
||||||
```python
|
|
||||||
from karkas_core.modules_system.public_api import get_module
|
|
||||||
|
|
||||||
register_command = get_module("standard.command_helper", "register_command")
|
|
||||||
|
|
||||||
register_command("my_command", "Описание моей команды", role="ADMIN")
|
|
||||||
```
|
|
@ -1 +0,0 @@
|
|||||||
from .main import get_user_commands, module_late_init, register_command
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.command_helper",
|
|
||||||
"name": "Command helper",
|
|
||||||
"description": "Модуль для отображения команд при вводе '/'",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
from aiogram.types import BotCommand
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import set_my_commands
|
|
||||||
|
|
||||||
commands = dict()
|
|
||||||
|
|
||||||
|
|
||||||
def register_command(command, description, long_description=None, role="USER"):
|
|
||||||
if long_description is None:
|
|
||||||
long_description = description
|
|
||||||
|
|
||||||
if role not in commands:
|
|
||||||
commands[role] = dict()
|
|
||||||
commands[role][command] = {
|
|
||||||
"description": description,
|
|
||||||
"long_description": long_description,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def set_commands(role="USER"):
|
|
||||||
bot_commands = []
|
|
||||||
if role in commands:
|
|
||||||
user_commands = commands[role]
|
|
||||||
for command in user_commands:
|
|
||||||
bot_commands.append(
|
|
||||||
BotCommand(
|
|
||||||
command=command,
|
|
||||||
description=user_commands[command]["description"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
await set_my_commands(
|
|
||||||
bot_commands,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_commands(role="USER"):
|
|
||||||
if role in commands:
|
|
||||||
return commands[role].copy()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
async def set_user_commands():
|
|
||||||
await set_commands("USER")
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_commands():
|
|
||||||
return get_commands("USER")
|
|
||||||
|
|
||||||
|
|
||||||
async def module_late_init():
|
|
||||||
await set_user_commands()
|
|
@ -1,28 +0,0 @@
|
|||||||
# Блок Config
|
|
||||||
|
|
||||||
Данный блок позволяет управляет конфигурацией бота.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Загрузка конфигурации из файла `config.yaml`;
|
|
||||||
- Сохранение конфигурации в файл;
|
|
||||||
- Регистрация параметров конфигурации;
|
|
||||||
- Получение значений параметров.
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
1. Импортируйте объект `config`;
|
|
||||||
2. Вызовите метод `register`, чтобы зарегистрировать параметр конфигурации;
|
|
||||||
3. Вызовите метод `get`, чтобы получить значение параметра.
|
|
||||||
|
|
||||||
## Пример
|
|
||||||
|
|
||||||
```python
|
|
||||||
from karkas_core.modules_system.public_api import get_module
|
|
||||||
|
|
||||||
config = get_module("standard.config", "config")
|
|
||||||
|
|
||||||
config.register("my_parameter", "string", default_value="default")
|
|
||||||
|
|
||||||
value = config.get("my_parameter")
|
|
||||||
```
|
|
@ -1,2 +0,0 @@
|
|||||||
from .config import IConfig, config
|
|
||||||
from .main import module_late_init
|
|
@ -1,7 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
|
|
||||||
from .config_manager import ConfigManager
|
|
||||||
|
|
||||||
IConfig = ConfigManager
|
|
||||||
|
|
||||||
config: ConfigManager = ConfigManager(config_path="config.yaml")
|
|
@ -1,115 +0,0 @@
|
|||||||
import inspect
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager:
|
|
||||||
def __init__(self, config_path: str):
|
|
||||||
self.config_path = config_path
|
|
||||||
|
|
||||||
self._config: Dict[str, Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
self._metadata: Dict[str, Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
def load(self, file_path: str = ""):
|
|
||||||
if not file_path:
|
|
||||||
file_path = self.config_path
|
|
||||||
|
|
||||||
def build_key(prev, next):
|
|
||||||
if prev:
|
|
||||||
return f"{prev}::{next}"
|
|
||||||
return next
|
|
||||||
|
|
||||||
def recurse_set(value, key=""):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
for k, v in value.items():
|
|
||||||
recurse_set(v, build_key(key, k))
|
|
||||||
return
|
|
||||||
if key in self._metadata:
|
|
||||||
self._config[key] = value
|
|
||||||
|
|
||||||
with open(file_path, "r", encoding="utf-8") as file:
|
|
||||||
data = yaml.safe_load(file)
|
|
||||||
recurse_set(data)
|
|
||||||
|
|
||||||
def save(self, file_path: str = ""):
|
|
||||||
if not file_path:
|
|
||||||
file_path = self.config_path
|
|
||||||
|
|
||||||
def nested_dict(flat_dict):
|
|
||||||
result = {}
|
|
||||||
for key, value in flat_dict.items():
|
|
||||||
keys = key.split("::")
|
|
||||||
d = result
|
|
||||||
for k in keys[:-1]:
|
|
||||||
d = d.setdefault(k, {})
|
|
||||||
d[keys[-1]] = value
|
|
||||||
return result
|
|
||||||
|
|
||||||
with open(file_path, "w", encoding="utf-8") as file:
|
|
||||||
yaml.dump(nested_dict(self._config), file, allow_unicode=True)
|
|
||||||
|
|
||||||
def _check_rights(self, key, module_id, access_type="get"):
|
|
||||||
return
|
|
||||||
|
|
||||||
def get(self, key: str):
|
|
||||||
module_id = self._get_module_id()
|
|
||||||
self._check_rights(key, module_id)
|
|
||||||
return self._config.get(key, self._metadata.get(key).get("default_value"))
|
|
||||||
|
|
||||||
def get_meta(self, key: str):
|
|
||||||
module_id = self._get_module_id()
|
|
||||||
self._check_rights(key, module_id, "get_meta")
|
|
||||||
return self._metadata.get(key)
|
|
||||||
|
|
||||||
def _get_module_id(self):
|
|
||||||
caller_frame = inspect.currentframe().f_back.f_back
|
|
||||||
caller_globals = caller_frame.f_globals
|
|
||||||
module_id = caller_globals.get("__karkas_block_id__")
|
|
||||||
return module_id
|
|
||||||
|
|
||||||
def mass_set(self, updates: Dict[str, Any]):
|
|
||||||
module_id = self._get_module_id()
|
|
||||||
for key, value in updates.items():
|
|
||||||
self._check_rights(key, module_id, "set")
|
|
||||||
if key in self._metadata:
|
|
||||||
# TODO: add metadata-based type and value validation
|
|
||||||
self._config[key] = value
|
|
||||||
else:
|
|
||||||
raise KeyError(f"Key {key} is not registered.")
|
|
||||||
|
|
||||||
def register(
|
|
||||||
self,
|
|
||||||
key: str,
|
|
||||||
value_type: str,
|
|
||||||
options: List[Any] = None,
|
|
||||||
multiple: bool = False,
|
|
||||||
default_value=None,
|
|
||||||
editable: bool = True,
|
|
||||||
shared: bool = False,
|
|
||||||
required: bool = False,
|
|
||||||
visible: bool = True,
|
|
||||||
pretty_name: str = "",
|
|
||||||
description: str = "",
|
|
||||||
):
|
|
||||||
module_id = self._get_module_id()
|
|
||||||
|
|
||||||
self._check_rights(key, module_id, "register")
|
|
||||||
|
|
||||||
if key in self._metadata:
|
|
||||||
raise ValueError("ERROR")
|
|
||||||
|
|
||||||
self._metadata[key] = {
|
|
||||||
"type": value_type,
|
|
||||||
"multiple": multiple,
|
|
||||||
"options": options,
|
|
||||||
"default_value": default_value,
|
|
||||||
"visible": visible,
|
|
||||||
"editable": editable,
|
|
||||||
"shared": shared,
|
|
||||||
"required": required,
|
|
||||||
"pretty_name": pretty_name,
|
|
||||||
"description": description,
|
|
||||||
"module_id": module_id,
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.config",
|
|
||||||
"name": "Config YAML",
|
|
||||||
"description": "Модуль для работы с конфигурационным файлом бота (YAML)",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": true,
|
|
||||||
"dependencies": {
|
|
||||||
"optional": {
|
|
||||||
"standard.miniapp": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pythonDependencies": {
|
|
||||||
"optional": {
|
|
||||||
"flet": "^0.23.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
from karkas_core.modules_system.public_api import get_module, log
|
|
||||||
|
|
||||||
from .config import config
|
|
||||||
from .miniapp_ui import get_miniapp_blueprint
|
|
||||||
|
|
||||||
|
|
||||||
def register_settings_page():
|
|
||||||
try:
|
|
||||||
register_page = get_module("standard.miniapp", "register_page")
|
|
||||||
|
|
||||||
prefix = "settings"
|
|
||||||
|
|
||||||
register_page(
|
|
||||||
name="Настройки",
|
|
||||||
path="/settings",
|
|
||||||
blueprint=get_miniapp_blueprint(config, prefix),
|
|
||||||
prefix=prefix,
|
|
||||||
role="ADMIN",
|
|
||||||
)
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log(str(e))
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def module_late_init():
|
|
||||||
register_settings_page()
|
|
||||||
|
|
||||||
pass
|
|
@ -1,267 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
try:
|
|
||||||
import dash_bootstrap_components as dbc
|
|
||||||
import flask
|
|
||||||
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
|
||||||
|
|
||||||
DASH_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
DASH_AVAILABLE = False
|
|
||||||
|
|
||||||
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"):
|
|
||||||
value = config.get(key)
|
|
||||||
meta = config.get_meta(key)
|
|
||||||
|
|
||||||
component_id = {
|
|
||||||
"type": "setting",
|
|
||||||
"key": key,
|
|
||||||
}
|
|
||||||
|
|
||||||
label_text = meta.get("pretty_name") or key
|
|
||||||
|
|
||||||
if meta.get("type") in ["string", "int", "float", "password"]:
|
|
||||||
input_type = {
|
|
||||||
"string": "text",
|
|
||||||
"int": "number",
|
|
||||||
"float": "number",
|
|
||||||
"password": "password",
|
|
||||||
}.get(meta.get("type"), "text")
|
|
||||||
|
|
||||||
input_props = {
|
|
||||||
"id": component_id,
|
|
||||||
"type": input_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if meta.get("type") != "password":
|
|
||||||
input_props["value"] = value
|
|
||||||
|
|
||||||
if meta.get("type") == "int":
|
|
||||||
input_props["step"] = 1
|
|
||||||
input_props["pattern"] = r"\d+"
|
|
||||||
elif meta.get("type") == "float":
|
|
||||||
input_props["step"] = "any"
|
|
||||||
|
|
||||||
component = dbc.Input(**input_props, invalid=False)
|
|
||||||
|
|
||||||
elif meta.get("type") == "select":
|
|
||||||
options = [{"label": opt, "value": opt} for opt in meta.get("options", [])]
|
|
||||||
component = dcc.Dropdown(
|
|
||||||
id=component_id,
|
|
||||||
options=options,
|
|
||||||
value=value,
|
|
||||||
style={
|
|
||||||
"padding-left": 0,
|
|
||||||
"padding-right": 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
elif meta.get("type") == "checkbox":
|
|
||||||
component = dbc.Checkbox(
|
|
||||||
id=component_id,
|
|
||||||
checked=value,
|
|
||||||
label=label_text,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
row = []
|
|
||||||
if meta.get("type") != "checkbox":
|
|
||||||
row.append(dbc.Label(label_text))
|
|
||||||
|
|
||||||
row.append(component)
|
|
||||||
|
|
||||||
if meta.get("description"):
|
|
||||||
row.append(dbc.FormText(meta.get("description")))
|
|
||||||
|
|
||||||
return dbc.Row(row, className="mb-3 mx-1")
|
|
||||||
|
|
||||||
|
|
||||||
def build_settings_tree(config: "ConfigManager"):
|
|
||||||
tree = {}
|
|
||||||
|
|
||||||
for key, value in config._metadata.items():
|
|
||||||
if not value["visible"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parts = key.split("::")
|
|
||||||
control = create_control(key, config)
|
|
||||||
|
|
||||||
current = tree
|
|
||||||
for i, part in enumerate(parts[:-1]):
|
|
||||||
if part not in current:
|
|
||||||
current[part] = {"__controls": []}
|
|
||||||
current = current[part]
|
|
||||||
|
|
||||||
current["__controls"].append(control)
|
|
||||||
|
|
||||||
return tree
|
|
||||||
|
|
||||||
|
|
||||||
def create_card(category, controls):
|
|
||||||
return dbc.Card(
|
|
||||||
[
|
|
||||||
dbc.CardHeader(html.H3(category, className="mb-0")),
|
|
||||||
dbc.CardBody(controls),
|
|
||||||
],
|
|
||||||
className="mb-3",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_settings_components(tree, level=0):
|
|
||||||
components = []
|
|
||||||
|
|
||||||
for category, subtree in tree.items():
|
|
||||||
if category == "__controls":
|
|
||||||
continue
|
|
||||||
|
|
||||||
controls = subtree.get("__controls", [])
|
|
||||||
subcomponents = create_settings_components(subtree, level + 1)
|
|
||||||
|
|
||||||
if controls or subcomponents:
|
|
||||||
card_content = controls + subcomponents
|
|
||||||
card = create_card(category, card_content)
|
|
||||||
components.append(card)
|
|
||||||
|
|
||||||
return components
|
|
||||||
|
|
||||||
|
|
||||||
def get_miniapp_blueprint(config: "ConfigManager", prefix: str):
|
|
||||||
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
|
||||||
|
|
||||||
roles = Roles()
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from dash_extensions.enrich import DashBlueprint
|
|
||||||
|
|
||||||
bp = DashBlueprint()
|
|
||||||
|
|
||||||
def create_layout():
|
|
||||||
settings_tree = build_settings_tree(config)
|
|
||||||
settings_components = create_settings_components(settings_tree)
|
|
||||||
|
|
||||||
layout = html.Div(
|
|
||||||
[
|
|
||||||
html.Script(),
|
|
||||||
html.H1("Настройки"),
|
|
||||||
dbc.Form(settings_components),
|
|
||||||
html.Div(id="save-confirmation"),
|
|
||||||
dbc.Button(
|
|
||||||
"Сохранить",
|
|
||||||
id="save-settings",
|
|
||||||
color="primary",
|
|
||||||
className="mt-3 w-100",
|
|
||||||
n_clicks=0,
|
|
||||||
),
|
|
||||||
html.Div(id="settings-update-trigger", style={"display": "none"}),
|
|
||||||
dcc.Store(id="settings-store"),
|
|
||||||
],
|
|
||||||
style={
|
|
||||||
"padding": "20px",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return layout
|
|
||||||
|
|
||||||
bp.layout = create_layout
|
|
||||||
|
|
||||||
@bp.callback(
|
|
||||||
Output("save-confirmation", "children"),
|
|
||||||
Output("settings-store", "data"),
|
|
||||||
Input("save-settings", "n_clicks"),
|
|
||||||
State({"type": "setting", "key": ALL}, "value"),
|
|
||||||
State({"type": "setting", "key": ALL}, "id"),
|
|
||||||
prevent_initial_call=True,
|
|
||||||
allow_duplicate=True,
|
|
||||||
)
|
|
||||||
def save_settings(n_clicks, values, keys):
|
|
||||||
if n_clicks > 0:
|
|
||||||
user = getattr(flask.g, "user", None)
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
return (
|
|
||||||
dbc.Alert(
|
|
||||||
"Вы не авторизованы!",
|
|
||||||
color="danger",
|
|
||||||
duration=10000,
|
|
||||||
),
|
|
||||||
"-",
|
|
||||||
)
|
|
||||||
|
|
||||||
if not asyncio.run(roles.check_admin_permission(user["id"])):
|
|
||||||
return (
|
|
||||||
dbc.Alert(
|
|
||||||
"Вы не администратор!",
|
|
||||||
color="danger",
|
|
||||||
duration=10000,
|
|
||||||
),
|
|
||||||
"-",
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: add values validation
|
|
||||||
|
|
||||||
updated_settings = {}
|
|
||||||
for value, id_dict in zip(values, keys):
|
|
||||||
key: str = id_dict["key"]
|
|
||||||
if prefix:
|
|
||||||
key = key.removeprefix(f"{prefix}-")
|
|
||||||
|
|
||||||
meta = config.get_meta(key)
|
|
||||||
|
|
||||||
if meta["type"] == "password":
|
|
||||||
# Is updated only if new value is specified
|
|
||||||
if value:
|
|
||||||
updated_settings[key] = value
|
|
||||||
else:
|
|
||||||
updated_settings[key] = value
|
|
||||||
|
|
||||||
config.mass_set(updated_settings)
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
|
||||||
date_str = now.strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
return (
|
|
||||||
dbc.Alert(
|
|
||||||
f"Настройки сохранены в {date_str}",
|
|
||||||
color="success",
|
|
||||||
duration=10000,
|
|
||||||
),
|
|
||||||
date_str,
|
|
||||||
)
|
|
||||||
|
|
||||||
bp.clientside_callback(
|
|
||||||
"""
|
|
||||||
function(n_clicks) {
|
|
||||||
const buttonSelector = '#%s-save-settings';
|
|
||||||
if (n_clicks > 0) {
|
|
||||||
document.querySelector(buttonSelector).disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
% (prefix),
|
|
||||||
Input("save-settings", "n_clicks"),
|
|
||||||
)
|
|
||||||
|
|
||||||
bp.clientside_callback(
|
|
||||||
"""
|
|
||||||
function(data) {
|
|
||||||
const buttonSelector = '#%s-save-settings';
|
|
||||||
if (data) {
|
|
||||||
document.querySelector(buttonSelector).disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
% (prefix),
|
|
||||||
Input("settings-store", "data"),
|
|
||||||
)
|
|
||||||
|
|
||||||
return bp
|
|
@ -1 +0,0 @@
|
|||||||
from .main import module_init, module_late_init, register_app_config
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.database",
|
|
||||||
"name": "Database",
|
|
||||||
"description": "Модуль для работы с БД",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": true,
|
|
||||||
"dependencies": {}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
from karkas_piccolo.karkas import migrate_forward # isort:skip
|
|
||||||
from karkas_piccolo.conf.apps import AppConfig, AppRegistry # isort:skip
|
|
||||||
|
|
||||||
from piccolo.conf.apps import PiccoloConfModule
|
|
||||||
from piccolo.engine.sqlite import SQLiteEngine
|
|
||||||
|
|
||||||
from karkas_core.singleton import Singleton
|
|
||||||
|
|
||||||
# from karkas_piccolo.conf.apps import A
|
|
||||||
|
|
||||||
|
|
||||||
APPS_CONFIGS = dict()
|
|
||||||
|
|
||||||
|
|
||||||
def register_app_config(app_config: AppConfig):
|
|
||||||
APPS_CONFIGS[app_config.app_name] = app_config
|
|
||||||
|
|
||||||
|
|
||||||
def module_init():
|
|
||||||
singleton = Singleton()
|
|
||||||
singleton.storage["_database"] = dict()
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def module_late_init():
|
|
||||||
singleton = Singleton()
|
|
||||||
DB = SQLiteEngine(path="./db.sqlite3")
|
|
||||||
|
|
||||||
APP_REGISTRY = AppRegistry(apps_configs=APPS_CONFIGS)
|
|
||||||
|
|
||||||
module = PiccoloConfModule(name="standard.database")
|
|
||||||
module.DB = DB
|
|
||||||
module.APP_REGISTRY = APP_REGISTRY
|
|
||||||
|
|
||||||
singleton.storage["_database"]["conf"] = module
|
|
||||||
|
|
||||||
await migrate_forward()
|
|
||||||
|
|
||||||
pass
|
|
@ -1,31 +0,0 @@
|
|||||||
# Блок Filters
|
|
||||||
|
|
||||||
Данный блок предоставляет фильтры для `aiogram`, которые используются для ограничения доступа к командам
|
|
||||||
и обработчикам событий.
|
|
||||||
|
|
||||||
## Фильтры
|
|
||||||
|
|
||||||
| Фильтр | Описание |
|
|
||||||
| :----------------------: | ---------------------------------------------------------------------- |
|
|
||||||
| `ChatModerOrAdminFilter` | Пропускает сообщения только от модераторов и администраторов чата |
|
|
||||||
| `ChatNotInApproveFilter` | Пропускает сообщения только из чатов, не входящих в список разрешенных |
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
Фильтры можно использовать в декораторах `@router.message` и `@router.callback_query`.
|
|
||||||
|
|
||||||
## Пример
|
|
||||||
|
|
||||||
```python
|
|
||||||
from aiogram import Router
|
|
||||||
from karkas_core.modules_system.public_api import get_module
|
|
||||||
|
|
||||||
ChatModerOrAdminFilter = get_module("standard.filters", "ChatModerOrAdminFilter")
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
@router.message(ChatModerOrAdminFilter())
|
|
||||||
async def admin_command(message: Message):
|
|
||||||
# Обработка команды, доступной только администраторам и модераторам.
|
|
||||||
pass
|
|
||||||
```
|
|
@ -1,8 +0,0 @@
|
|||||||
from .filters import (
|
|
||||||
ChatIDFilter,
|
|
||||||
ChatModerOrAdminFilter,
|
|
||||||
ChatNotInApproveFilter,
|
|
||||||
SimpleAdminFilter,
|
|
||||||
chat_not_in_approve,
|
|
||||||
module_init,
|
|
||||||
)
|
|
@ -1,130 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
|
|
||||||
|
|
||||||
from aiogram import BaseMiddleware, Bot
|
|
||||||
from aiogram.filters import BaseFilter
|
|
||||||
from aiogram.types import Message, TelegramObject
|
|
||||||
from aiogram.utils.chat_member import ADMINS
|
|
||||||
from typing_extensions import deprecated
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import (
|
|
||||||
get_module,
|
|
||||||
register_outer_message_middleware,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from karkas_blocks.standard.config import IConfig
|
|
||||||
from karkas_blocks.standard.roles import Roles as IRoles
|
|
||||||
|
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
|
||||||
|
|
||||||
try:
|
|
||||||
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
|
||||||
ROLES_MODULE_LOADED = True
|
|
||||||
except Exception:
|
|
||||||
ROLES_MODULE_LOADED = False
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalFilter(BaseMiddleware):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.filter = ChatIDFilter()
|
|
||||||
|
|
||||||
async def __call__(
|
|
||||||
self,
|
|
||||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
|
||||||
event: TelegramObject,
|
|
||||||
data: Dict[str, Any],
|
|
||||||
) -> Any:
|
|
||||||
if not isinstance(event, Message):
|
|
||||||
return await handler(event, data)
|
|
||||||
|
|
||||||
if not config.get("filters::global::enabled"):
|
|
||||||
return await handler(event, data)
|
|
||||||
|
|
||||||
if await self.filter(event, None):
|
|
||||||
return await handler(event, data)
|
|
||||||
|
|
||||||
if event.chat.type == "private":
|
|
||||||
if config.get("filters::global::private_allowed"):
|
|
||||||
return await handler(event, data)
|
|
||||||
|
|
||||||
return await event.answer(config.get("filters::global::forbidden_message"))
|
|
||||||
|
|
||||||
|
|
||||||
def module_init():
|
|
||||||
config.register(
|
|
||||||
"filters::approved_chat_id", "int", multiple=True, shared=True, default_value=[]
|
|
||||||
)
|
|
||||||
config.register("filters::default_chat_tag", "string", shared=True)
|
|
||||||
config.register(
|
|
||||||
"filters::global::forbidden_message",
|
|
||||||
"string",
|
|
||||||
default_value="Вы не имеете доступа!",
|
|
||||||
)
|
|
||||||
config.register("filters::global::enabled", "boolean", default_value=False)
|
|
||||||
config.register("filters::global::private_allowed", "boolean", default_value=False)
|
|
||||||
|
|
||||||
register_outer_message_middleware(GlobalFilter())
|
|
||||||
|
|
||||||
|
|
||||||
def get_approved_chat_id() -> list:
|
|
||||||
return config.get("filters::approved_chat_id")
|
|
||||||
|
|
||||||
|
|
||||||
@deprecated("Use ChatIDFilter or own implementation")
|
|
||||||
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}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# log(f"Chat not in approve list: {chat_id}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class ChatIDFilter(BaseFilter):
|
|
||||||
def __init__(self, blacklist=False, approved_chats=None) -> None:
|
|
||||||
self.blacklist = blacklist
|
|
||||||
self.approved_chats = approved_chats
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
|
||||||
chat_id = message.chat.id
|
|
||||||
|
|
||||||
approved_chats = self.approved_chats or get_approved_chat_id()
|
|
||||||
|
|
||||||
# If filtering list is empty, allow anything
|
|
||||||
if len(approved_chats) == 0:
|
|
||||||
return True
|
|
||||||
|
|
||||||
res = chat_id in approved_chats
|
|
||||||
|
|
||||||
return res ^ (self.blacklist)
|
|
||||||
|
|
||||||
|
|
||||||
class ChatNotInApproveFilter(ChatIDFilter):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__(allow=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ChatModerOrAdminFilter(BaseFilter):
|
|
||||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
|
||||||
if not ROLES_MODULE_LOADED:
|
|
||||||
raise Exception("Roles module not loaded")
|
|
||||||
|
|
||||||
user_id = message.from_user.id
|
|
||||||
roles = Roles()
|
|
||||||
admins = await bot.get_chat_administrators(message.chat.id)
|
|
||||||
return (
|
|
||||||
await roles.check_admin_permission(user_id)
|
|
||||||
or await roles.check_moderator_permission(user_id)
|
|
||||||
or any(user_id == admin.user.id for admin in admins)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleAdminFilter(BaseFilter):
|
|
||||||
async def __call__(self, message: Message, bot: Bot) -> bool:
|
|
||||||
member = await bot.get_chat_member(message.chat.id, message.from_user.id)
|
|
||||||
return isinstance(member, ADMINS)
|
|
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.filters",
|
|
||||||
"name": "Filters",
|
|
||||||
"description": "Модуль с фильтрами",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": true,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.config": "^1.0.0"
|
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
"standard.roles": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
# Блок FSM Database Storage
|
|
||||||
|
|
||||||
Данный блок реализует хранение состояний FSM (Finite State Machine) в базе данных.
|
|
||||||
|
|
||||||
## Функциональность
|
|
||||||
|
|
||||||
- Сохранение состояния FSM в базу данных;
|
|
||||||
- Получение состояния FSM из базы данных;
|
|
||||||
- Обновление данных состояния FSM.
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
Блок автоматически регистрирует хранилище состояний FSM при инициализации.
|
|
@ -1 +0,0 @@
|
|||||||
from .fsm import module_init
|
|
@ -1 +0,0 @@
|
|||||||
from .piccolo_app import APP_CONFIG
|
|
@ -1,15 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from karkas_piccolo.conf.apps import AppConfig
|
|
||||||
|
|
||||||
from .tables import FSMData
|
|
||||||
|
|
||||||
CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
APP_CONFIG = AppConfig(
|
|
||||||
app_name="standard.fsm_database_storage",
|
|
||||||
migrations_folder_path=os.path.join(CURRENT_DIRECTORY, "piccolo_migrations"),
|
|
||||||
table_classes=[FSMData],
|
|
||||||
migration_dependencies=[],
|
|
||||||
commands=[],
|
|
||||||
)
|
|
@ -1,85 +0,0 @@
|
|||||||
from piccolo.apps.migrations.auto.migration_manager import MigrationManager
|
|
||||||
from piccolo.columns.column_types import Text
|
|
||||||
from piccolo.columns.indexes import IndexMethod
|
|
||||||
|
|
||||||
ID = "2024-08-20T15:07:25:276545"
|
|
||||||
VERSION = "1.16.0"
|
|
||||||
DESCRIPTION = ""
|
|
||||||
|
|
||||||
|
|
||||||
async def forwards():
|
|
||||||
manager = MigrationManager(
|
|
||||||
migration_id=ID,
|
|
||||||
app_name="standard.fsm_database_storage",
|
|
||||||
description=DESCRIPTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_table(
|
|
||||||
class_name="FSMData", tablename="fsm_data", schema=None, columns=None
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="FSMData",
|
|
||||||
tablename="fsm_data",
|
|
||||||
column_name="key",
|
|
||||||
db_column_name="key",
|
|
||||||
column_class_name="Text",
|
|
||||||
column_class=Text,
|
|
||||||
params={
|
|
||||||
"primary": True,
|
|
||||||
"default": "",
|
|
||||||
"null": False,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="FSMData",
|
|
||||||
tablename="fsm_data",
|
|
||||||
column_name="state",
|
|
||||||
db_column_name="state",
|
|
||||||
column_class_name="Text",
|
|
||||||
column_class=Text,
|
|
||||||
params={
|
|
||||||
"default": "",
|
|
||||||
"null": True,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
manager.add_column(
|
|
||||||
table_class_name="FSMData",
|
|
||||||
tablename="fsm_data",
|
|
||||||
column_name="data",
|
|
||||||
db_column_name="data",
|
|
||||||
column_class_name="Text",
|
|
||||||
column_class=Text,
|
|
||||||
params={
|
|
||||||
"default": "",
|
|
||||||
"null": True,
|
|
||||||
"primary_key": False,
|
|
||||||
"unique": False,
|
|
||||||
"index": False,
|
|
||||||
"index_method": IndexMethod.btree,
|
|
||||||
"choices": None,
|
|
||||||
"db_column_name": None,
|
|
||||||
"secret": False,
|
|
||||||
},
|
|
||||||
schema=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
return manager
|
|
@ -1,8 +0,0 @@
|
|||||||
from piccolo.columns import Text
|
|
||||||
from piccolo.table import Table
|
|
||||||
|
|
||||||
|
|
||||||
class FSMData(Table):
|
|
||||||
key = Text(primary=True)
|
|
||||||
state = Text(null=True)
|
|
||||||
data = Text(null=True)
|
|
@ -1,174 +0,0 @@
|
|||||||
import json
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
from aiogram.fsm.state import State
|
|
||||||
from aiogram.fsm.storage.base import BaseStorage, StorageKey
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module, log
|
|
||||||
from karkas_core.modules_system.public_api.public_api import set_fsm
|
|
||||||
|
|
||||||
from .db.tables import FSMData
|
|
||||||
|
|
||||||
|
|
||||||
class FSMDataRepository:
|
|
||||||
def get(self, key: str):
|
|
||||||
return FSMData.select().where(FSMData.key == key).first().run_sync()
|
|
||||||
|
|
||||||
def set_state(self, key: str, state: str):
|
|
||||||
returning = (
|
|
||||||
FSMData.update(
|
|
||||||
{
|
|
||||||
FSMData.state: state,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.where(FSMData.key == key)
|
|
||||||
.returning(FSMData.key)
|
|
||||||
.run_sync()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(returning) == 0:
|
|
||||||
FSMData.insert(
|
|
||||||
FSMData(
|
|
||||||
key=key,
|
|
||||||
state=state,
|
|
||||||
data=None,
|
|
||||||
)
|
|
||||||
).run_sync()
|
|
||||||
|
|
||||||
def set_data(self, key: str, data: str):
|
|
||||||
returning = (
|
|
||||||
FSMData.update(
|
|
||||||
{
|
|
||||||
FSMData.data: data,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.where(FSMData.key == key)
|
|
||||||
.returning(FSMData.key)
|
|
||||||
.run_sync()
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(returning) == 0:
|
|
||||||
FSMData.insert(
|
|
||||||
FSMData(
|
|
||||||
key=key,
|
|
||||||
data=data,
|
|
||||||
state=None,
|
|
||||||
)
|
|
||||||
).run_sync()
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_key(key: StorageKey) -> str:
|
|
||||||
return f"{key.bot_id}:{key.chat_id}:{key.user_id}"
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_object(obj: object) -> str | None:
|
|
||||||
try:
|
|
||||||
return json.dumps(obj)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"Serializing error! {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def deserialize_object(obj):
|
|
||||||
try:
|
|
||||||
return json.loads(obj)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"Deserializing error! {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class SQLStorage(BaseStorage):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.repo = FSMDataRepository()
|
|
||||||
|
|
||||||
async def set_state(self, key: StorageKey, state: State | None = None) -> None:
|
|
||||||
"""
|
|
||||||
Set state for specified key
|
|
||||||
|
|
||||||
:param key: storage key
|
|
||||||
:param state: new state
|
|
||||||
"""
|
|
||||||
s_key = serialize_key(key)
|
|
||||||
s_state = state.state if isinstance(state, State) else state
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.repo.set_state(s_key, s_state)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"FSM Storage database error: {e}")
|
|
||||||
|
|
||||||
async def get_state(self, key: StorageKey) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Get key state
|
|
||||||
|
|
||||||
:param key: storage key
|
|
||||||
:return: current state
|
|
||||||
"""
|
|
||||||
s_key = serialize_key(key)
|
|
||||||
|
|
||||||
try:
|
|
||||||
s_state = self.repo.get(s_key)
|
|
||||||
return s_state["state"] if s_state else None
|
|
||||||
except Exception as e:
|
|
||||||
log(f"FSM Storage database error: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
Write data (replace)
|
|
||||||
|
|
||||||
:param key: storage key
|
|
||||||
:param data: new data
|
|
||||||
"""
|
|
||||||
s_key = serialize_key(key)
|
|
||||||
s_data = serialize_object(data)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.repo.set_data(s_key, s_data)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"FSM Storage database error: {e}")
|
|
||||||
|
|
||||||
async def get_data(self, key: StorageKey) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Get current data for key
|
|
||||||
|
|
||||||
:param key: storage key
|
|
||||||
:return: current data
|
|
||||||
"""
|
|
||||||
s_key = serialize_key(key)
|
|
||||||
|
|
||||||
try:
|
|
||||||
s_data = self.repo.get(s_key)
|
|
||||||
return deserialize_object(s_data["data"]) if s_data else None
|
|
||||||
except Exception as e:
|
|
||||||
log(f"FSM Storage database error: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def update_data(
|
|
||||||
self, key: StorageKey, data: Dict[str, Any]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Update data in the storage for key (like dict.update)
|
|
||||||
|
|
||||||
:param key: storage key
|
|
||||||
:param data: partial data
|
|
||||||
:return: new data
|
|
||||||
"""
|
|
||||||
current_data = await self.get_data(key=key)
|
|
||||||
if not current_data:
|
|
||||||
current_data = {}
|
|
||||||
current_data.update(data)
|
|
||||||
await self.set_data(key=key, data=current_data)
|
|
||||||
return current_data.copy()
|
|
||||||
|
|
||||||
async def close(self) -> None: # pragma: no cover
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def module_init():
|
|
||||||
register_app_config = get_module("standard.database", "register_app_config")
|
|
||||||
from .db import APP_CONFIG
|
|
||||||
|
|
||||||
register_app_config(APP_CONFIG)
|
|
||||||
|
|
||||||
set_fsm(SQLStorage())
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.fsm_database_storage",
|
|
||||||
"name": "FSM Database Storage",
|
|
||||||
"description": "Очень полезный модуль",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.database": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
from .main import module_init
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.help",
|
|
||||||
"name": "Help",
|
|
||||||
"description": "Модуль для вывода /help сообщения",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.config": "^1.0.0"
|
|
||||||
},
|
|
||||||
"optional": {
|
|
||||||
"standard.command_helper": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pythonDependencies": {
|
|
||||||
"required": {
|
|
||||||
"string": "*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
import string
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram import Router
|
|
||||||
from aiogram.filters import Command
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import (
|
|
||||||
get_metainfo,
|
|
||||||
get_module,
|
|
||||||
register_router,
|
|
||||||
)
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from karkas_blocks.standard.config import IConfig
|
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
|
||||||
|
|
||||||
try:
|
|
||||||
(register_command, get_user_commands) = get_module(
|
|
||||||
"standard.command_helper", ["register_command", "get_user_commands"]
|
|
||||||
)
|
|
||||||
COMMAND_HELPER_MODULE_LOADED = True
|
|
||||||
except Exception:
|
|
||||||
COMMAND_HELPER_MODULE_LOADED = False
|
|
||||||
pass
|
|
||||||
|
|
||||||
# We kindly ask you not to delete or change the following text without
|
|
||||||
# the consent of the «Karkas» project team.
|
|
||||||
FOOTER = """===============
|
|
||||||
Разработано командой ALT Gnome Infrastructure в рамках проекта Каркас.
|
|
||||||
Исходный код: https://gitflic.ru/project/alt-gnome/karkas
|
|
||||||
Оставить репорт: https://gitflic.ru/project/alt-gnome/karkas/issue/create
|
|
||||||
Руководитель проекта: Семен Фомченков
|
|
||||||
Ведущий разработчик: Максим Слипенко
|
|
||||||
Почта для связи: help@karkas.chat
|
|
||||||
Версия: $version
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def format_commands(commands_dict):
|
|
||||||
formatted_commands = []
|
|
||||||
for command, details in commands_dict.items():
|
|
||||||
formatted_commands.append(f"/{command} - {details['long_description']}")
|
|
||||||
return "\n".join(formatted_commands)
|
|
||||||
|
|
||||||
|
|
||||||
async def help(message: "Message"):
|
|
||||||
commands = ""
|
|
||||||
version = ""
|
|
||||||
|
|
||||||
if COMMAND_HELPER_MODULE_LOADED:
|
|
||||||
commands = format_commands(get_user_commands())
|
|
||||||
|
|
||||||
metainfo = get_metainfo()
|
|
||||||
if "app_version" in metainfo:
|
|
||||||
version = metainfo["app_version"]
|
|
||||||
|
|
||||||
await message.reply(
|
|
||||||
string.Template(config.get("help::message") + "\n\n" + FOOTER).substitute(
|
|
||||||
commands=commands, version=version or "не указана"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def module_init():
|
|
||||||
config.register(
|
|
||||||
"help::message",
|
|
||||||
"string",
|
|
||||||
default_value="$commands",
|
|
||||||
)
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
router.message.register(help, Command("help"))
|
|
||||||
|
|
||||||
register_router(router)
|
|
||||||
|
|
||||||
if COMMAND_HELPER_MODULE_LOADED:
|
|
||||||
register_command("help", "Cправка")
|
|
@ -1,20 +0,0 @@
|
|||||||
# Блок Info
|
|
||||||
|
|
||||||
Данный блок предоставляет информацию о пользователях и чатах.
|
|
||||||
|
|
||||||
## Команды
|
|
||||||
|
|
||||||
| Команда | Описание |
|
|
||||||
| :---------: | ----------------------------------- |
|
|
||||||
| `/info` | Получение информации о пользователе |
|
|
||||||
| `/chatinfo` | Получение информации о чате |
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
Информация о пользователе:
|
|
||||||
|
|
||||||
1. Отправьте команду `/info`, ответив на сообщение пользователя или указав его тег.
|
|
||||||
|
|
||||||
Информация о чате:
|
|
||||||
|
|
||||||
1. Отправьте команду `/chatinfo`.
|
|
@ -1,2 +0,0 @@
|
|||||||
from .handlers import get_user_info
|
|
||||||
from .main import module_init
|
|
@ -1,54 +0,0 @@
|
|||||||
# flake8: noqa
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from aiogram import Bot
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from karkas_core.modules_system.public_api import get_module, log
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from karkas_blocks.standard.database import db_api as IDbApi
|
|
||||||
from karkas_blocks.standard.roles import Roles as IRoles
|
|
||||||
|
|
||||||
db_api: "IDbApi" = get_module(
|
|
||||||
"standard.database",
|
|
||||||
"db_api",
|
|
||||||
)
|
|
||||||
|
|
||||||
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
|
||||||
|
|
||||||
|
|
||||||
async def get_info_answer_by_id(message: Message, bot: Bot, user_id: int):
|
|
||||||
roles = Roles()
|
|
||||||
role = roles.get_user_role(user_id)
|
|
||||||
|
|
||||||
answer = f"Имя: {user_id}\n" f"Роль: {role}\n"
|
|
||||||
await message.reply(answer)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_user_info(message: Message, bot: Bot):
|
|
||||||
try:
|
|
||||||
if message.reply_to_message:
|
|
||||||
user_id = message.reply_to_message.from_user.id
|
|
||||||
await get_info_answer_by_id(message, bot, user_id)
|
|
||||||
else:
|
|
||||||
await get_info_answer_by_id(message, bot, message.from_user.id)
|
|
||||||
except Exception as e:
|
|
||||||
await message.reply(
|
|
||||||
"В вашем запросе что-то пошло не так,"
|
|
||||||
" попробуйте запросить информацию о пользователе по его тегу или ответив на его сообщение"
|
|
||||||
)
|
|
||||||
# print(e)
|
|
||||||
log(e)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_chat_info(message: Message, bot: Bot):
|
|
||||||
answer = (
|
|
||||||
f"*Название чата:* {message.chat.title}\n"
|
|
||||||
f"*ID чата:* `{message.chat.id}`\n \n"
|
|
||||||
f"*Суммарное количество сообщений в чате:* {db_api.get_chat_all_stat(message.chat.id)}\n"
|
|
||||||
f"*Количество пользователей в чате:* {await bot.get_chat_member_count(message.chat.id)}\n"
|
|
||||||
f"*Количество администраторов в чате:* {len(await bot.get_chat_administrators(message.chat.id))}"
|
|
||||||
)
|
|
||||||
await message.reply(answer, parse_mode="MarkdownV2")
|
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "standard.info",
|
|
||||||
"name": "Info",
|
|
||||||
"description": "Модуль с информацией",
|
|
||||||
"author": "Karkas Team",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"privileged": false,
|
|
||||||
"dependencies": {
|
|
||||||
"required": {
|
|
||||||
"standard.roles": "^1.0.0",
|
|
||||||
"standard.database": "^1.0.0",
|
|
||||||
"standard.command_helper": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user