mirror of
https://gitflic.ru/project/maks1ms/ocab.git
synced 2024-12-24 00:33:05 +03:00
Compare commits
2 Commits
63b321a500
...
2becd33774
Author | SHA1 | Date | |
---|---|---|---|
2becd33774 | |||
9fa23f18b9 |
80
README.md
80
README.md
@ -1,60 +1,44 @@
|
|||||||
# OpenChatAiBot V2
|
# OCAB - Open Chat Ai Bot
|
||||||
|
|
||||||
## Что такое OCAB?
|
## Что такое OCAB?
|
||||||
|
|
||||||
OCAB - это бот для Telegram, который призван помочь во взаимодействии с чатом.
|
OCAB - это платформа для разработки модульных Telegram-ботов, которая призвана упростить взаимодействие с чатами.
|
||||||
Бот поддерживает интеграцию модулей для расширения функционала.
|
OCAB предоставляет возможность расширять функциональность бота с помощью интеграции различных модулей.
|
||||||
Фактически бот является платформой для запуска созданных для него модулей.
|
Код платформы и набор стандартных модулей находятся в этом монорепозитории.
|
||||||
Модули могут взаимодействовать друг с другом или быть полностью независимыми.
|
|
||||||
|
|
||||||
## Что такое модуль?
|
## Структура монорепозитория
|
||||||
|
|
||||||
Модуль - это директория, которая содержит в себе код модуля и его конфигурацию.
|
Монорепозиторий OCAB включает в себя:
|
||||||
|
* **Ядро OCAB (src/ocab_core):** Содержит основные компоненты платформы, такие как система управления модулями,
|
||||||
|
логирование и утилиты.
|
||||||
|
* **Модули OCAB (src/ocab_modules):** Содержит стандартные и дополнительные модули, которые расширяют
|
||||||
|
функциональность ботов OCAB.
|
||||||
|
* **Пример бота (src/gnomik):** Пример реализации бота на платформе OCAB.
|
||||||
|
|
||||||
|
## Модули
|
||||||
|
|
||||||
|
Модули OCAB - это независимые компоненты, которые добавляют функциональность к боту.
|
||||||
|
|
||||||
### Структура модуля
|
### Структура модуля
|
||||||
|
|
||||||
*Будет дополнено после закрытия [issue #17](https://gitflic.ru/project/armatik/ocab/issue/17).*
|
Структура модуля представлена [здесь](docs/MODULES-SPEC.md).
|
||||||
|
|
||||||
## Стандартные модули
|
### Стандартные модули
|
||||||
|
|
||||||
В стандартный состав бота входят следующие модули:
|
Стандартные модули предоставляют базовые функции для работы бота:
|
||||||
|
* [admin](src/ocab_modules/ocab_modules/standard/admin/README.md) - модуль для модерирования чата.
|
||||||
|
* [roles](src/ocab_modules/ocab_modules/standard/roles/README.md) - модуль ролей пользователей.
|
||||||
|
* [config](src/ocab_modules/ocab_modules/standard/config/README.md) - модуль управления конфигурацией бота.
|
||||||
|
* [database](src/ocab_modules/ocab_modules/standard/database/README.md) - модуль для работы с базой данных.
|
||||||
|
* [fsm_database_storage](src/ocab_modules/ocab_modules/standard/fsm_database_storage/README.md) - модуль для хранения состояний FSM в базе данных.
|
||||||
|
* [filters](src/ocab_modules/ocab_modules/standard/filters/README.md) - модуль, предоставляющий фильтры для aiogram.
|
||||||
|
* [message_processing](src/ocab_modules/ocab_modules/standard/message_processing/README.md) - модуль обработки входящих сообщений.
|
||||||
|
* [miniapp](src/ocab_modules/ocab_modules/standard/miniapp/README.md) - модуль для реализации веб-интерфейса бота.
|
||||||
|
* [command_helper](src/ocab_modules/ocab_modules/standard/command_helper/README.md) - модуль для упрощения регистрации команд бота.
|
||||||
|
* [info](src/ocab_modules/ocab_modules/standard/info/README.md) - модуль предоставления информации о пользователях и чатах.
|
||||||
|
|
||||||
* `admin` - модуль для модерирования чата. Позволяет удалять сообщения, банить пользователей и т.д.
|
### Дополнительные официальные модули
|
||||||
* `reputation` - модуль репутации пользователей. Позволяет оценивать ответы пользователей и накапливать репутацию.
|
|
||||||
* `welcome` - модуль приветствия новых пользователей. Позволяет приветствовать новых пользователей в чате, а также
|
|
||||||
проверять пользователя капчей для предотвращения спама.
|
|
||||||
* `roles` - модуль ролей. Позволяет назначать пользователям роли и ограничивать доступ к командам бота по ролям.
|
|
||||||
Является важной частью системы прав доступа и модуля `admin`.
|
|
||||||
|
|
||||||
## Дополнительные официальные модули
|
Дополнительные официальные модули разработаны командой OCAB и предоставляют расширенные возможности для бота:
|
||||||
|
* [yandexgpt](src/ocab_modules/ocab_modules/external/yandexgpt/README.md) - модуль для интеграции с нейросетью YandexGPT.
|
||||||
* `yandexgpt` - модуль для генерации ответов на основе нейросети GPT-3.5. Позволяет боту отвечать на сообщения
|
* [create_report_apps](src/ocab_modules/ocab_modules/external/create_report_apps/README.md) - модуль для создания отчетов об ошибках.
|
||||||
пользователей, используя нейросеть. Ключевой особенностью является построение линии контекста для нейросети,
|
|
||||||
которая позволяет боту отвечать на вопросы, используя контекст предыдущих сообщений. Для этого используется
|
|
||||||
модуль база данных хранящий историю сообщений.
|
|
||||||
<!--
|
|
||||||
* `bugzilla` - модуль для интеграции с BugZilla. Позволяет получать уведомления о новых багах в BugZilla, отслеживать их
|
|
||||||
статус, формировать стандартизированные сообщения для корректного описания багов. В будущем планируется интеграция с
|
|
||||||
API BugZilla для возможности создания багов из чата.
|
|
||||||
* `alt_packages` - модуль для интеграции с AltLinux Packages. Позволяет получать уведомления о новых пакетах в репозитории
|
|
||||||
AltLinux, поиска пакетов по названию, получения истории изменений, команды для установки пакета из репозитория и
|
|
||||||
прочей информации о пакете.
|
|
||||||
* `notes` - модуль заметок. Позволяет сохранять заметки для пользователей и чатов. Заметки являются ссылками на
|
|
||||||
сообщения в чате.
|
|
||||||
-->
|
|
||||||
|
|
||||||
Список модулей будет пополняться. Идеи для модулей можно оставлять в [issues](https://gitflic.ru/project/armatik/ocab/issue/create).
|
|
||||||
|
|
||||||
## Установка бота
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
### Вручную
|
|
||||||
|
|
||||||
## Технологический стек
|
|
||||||
|
|
||||||
* 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 для работы с базой данных.
|
|
||||||
|
39
docs/DEV.md
Normal file
39
docs/DEV.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
## Настройка рабочего окружения
|
||||||
|
|
||||||
|
Данная инструкция поможет вам настроить рабочее окружение для разработки OCAB.
|
||||||
|
|
||||||
|
### Предварительные требования
|
||||||
|
|
||||||
|
* **Python 3.12:** OCAB требует Python 3.12.
|
||||||
|
* **VSCode:** Рекомендуется использовать VSCode для разработки.
|
||||||
|
* **Git:** У вас должен быть установлен Git для клонирования репозитория.
|
||||||
|
|
||||||
|
### Шаги
|
||||||
|
|
||||||
|
1. **Клонируйте репозиторий:**
|
||||||
|
```bash
|
||||||
|
git clone https://gitflic.ru/project/armatik/ocab.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Откройте проект в VSCode:**
|
||||||
|
* Откройте папку `ocab` в VSCode.
|
||||||
|
* VSCode автоматически предложит открыть проект как workspace, используя файл `ocab.code-workspace`.
|
||||||
|
Нажмите "Открыть Workspace", чтобы принять предложение.
|
||||||
|
|
||||||
|
3. **Настройте Poetry:**
|
||||||
|
* Установите Poetry, следуя инструкциям на официальном сайте: [https://python-poetry.org/docs/](https://python-poetry.org/docs/).
|
||||||
|
* **Для каждого пакета:**
|
||||||
|
* Перейдите в папку пакета (например, `src/ocab_core`).
|
||||||
|
* Выполните команду `poetry install`, чтобы установить зависимости пакета.
|
||||||
|
* Poetry создаст виртуальное окружение внутри папки пакета (`.venv`).
|
||||||
|
|
||||||
|
4. **Активируйте виртуальное окружение:**
|
||||||
|
* Выполните команду `poetry shell` в папке пакета, чтобы активировать виртуальное окружение.
|
||||||
|
|
||||||
|
Теперь ваше рабочее окружение настроено, и вы можете начать.
|
||||||
|
|
||||||
|
### Дополнительная информация
|
||||||
|
|
||||||
|
* Каждый пакет в монорепозитории имеет свой собственный файл `pyproject.toml`, где указаны его зависимости.
|
||||||
|
* Poetry автоматически управляет виртуальными окружениями для каждого пакета.
|
||||||
|
* Вы можете использовать команду `poetry add <package_name>` для добавления новых зависимостей.
|
@ -1,47 +0,0 @@
|
|||||||
## 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
|
|
||||||
```
|
|
@ -22,11 +22,29 @@ TODO: описать функционал
|
|||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
Запуск бота осуществляется с помощью команды:
|
### Docker
|
||||||
|
|
||||||
```bash
|
1. Соберите Docker-образ:
|
||||||
python -m gnomik
|
```bash
|
||||||
```
|
docker build -t gnomik .
|
||||||
|
```
|
||||||
|
2. Запустите контейнер:
|
||||||
|
```bash
|
||||||
|
docker run -p 9000:9000 -v ./config.yaml:/app/config.yaml -v ./database:/app/database gnomik
|
||||||
|
```
|
||||||
|
|
||||||
|
Замените `./config.yaml` и `./database` на пути к вашим локальным файлам конфигурации и паки для базы данных.
|
||||||
|
|
||||||
|
### Вручную
|
||||||
|
|
||||||
|
1. Активируйте виртуальное окружение Gnomика:
|
||||||
|
```bash
|
||||||
|
poetry shell
|
||||||
|
```
|
||||||
|
2. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m gnomik
|
||||||
|
```
|
||||||
|
|
||||||
## Конфигурация
|
## Конфигурация
|
||||||
|
|
||||||
|
18
src/gnomik/config-example.yaml
Normal file
18
src/gnomik/config-example.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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
|
@ -78,7 +78,7 @@ class OCAB:
|
|||||||
singleton = Singleton()
|
singleton = Singleton()
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
config = get_module("standard.config", "config")
|
config = get_module("standard.config", "config")
|
||||||
app.mount("/webapp", singleton.storage["webapp"])
|
app.mount(config.get("miniapp::prefix"), singleton.storage["webapp"])
|
||||||
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
await register_bot_webhook(app, singleton.bot, singleton.dp)
|
||||||
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
|
await singleton.bot.set_webhook(config.get("core::webhook::public_url"))
|
||||||
hyperConfig = HyperConfig()
|
hyperConfig = HyperConfig()
|
||||||
|
@ -15,6 +15,7 @@ def register_settings_page():
|
|||||||
path="/settings",
|
path="/settings",
|
||||||
blueprint=get_miniapp_blueprint(config, prefix),
|
blueprint=get_miniapp_blueprint(config, prefix),
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
|
role="ADMIN",
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .config_manager import ConfigManager
|
from .config_manager import ConfigManager
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
import flask
|
||||||
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
from dash_extensions.enrich import ALL, Input, Output, State, dcc, html
|
||||||
|
|
||||||
DASH_AVAILABLE = True
|
DASH_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DASH_AVAILABLE = False
|
DASH_AVAILABLE = False
|
||||||
|
|
||||||
|
from ocab_core.modules_system.public_api import get_module
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ocab_modules.standard.roles import Roles as IRoles
|
||||||
|
|
||||||
|
|
||||||
def create_control(key: str, config: ConfigManager):
|
def create_control(key: str, config: ConfigManager):
|
||||||
value = config.get(key)
|
value = config.get(key)
|
||||||
@ -126,6 +135,10 @@ def create_settings_components(tree, level=0):
|
|||||||
|
|
||||||
|
|
||||||
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
||||||
|
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
||||||
|
|
||||||
|
roles = Roles()
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from dash_extensions.enrich import DashBlueprint
|
from dash_extensions.enrich import DashBlueprint
|
||||||
@ -171,6 +184,28 @@ def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
|||||||
)
|
)
|
||||||
def save_settings(n_clicks, values, keys):
|
def save_settings(n_clicks, values, keys):
|
||||||
if n_clicks > 0:
|
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: добавить валидацию значений
|
# TODO: добавить валидацию значений
|
||||||
|
|
||||||
updated_settings = {}
|
updated_settings = {}
|
||||||
@ -178,12 +213,18 @@ def get_miniapp_blueprint(config: ConfigManager, prefix: str):
|
|||||||
key: str = id_dict["key"]
|
key: str = id_dict["key"]
|
||||||
if prefix:
|
if prefix:
|
||||||
key = key.removeprefix(f"{prefix}-")
|
key = key.removeprefix(f"{prefix}-")
|
||||||
updated_settings[key] = value
|
|
||||||
|
meta = config.get_meta(key)
|
||||||
|
|
||||||
|
if meta["type"] == "password":
|
||||||
|
if value: # Only update if a new value is provided
|
||||||
|
updated_settings[key] = value
|
||||||
|
else:
|
||||||
|
updated_settings[key] = value
|
||||||
|
|
||||||
config.mass_set(updated_settings)
|
config.mass_set(updated_settings)
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
# locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
date_str = now.strftime("%H:%M:%S")
|
date_str = now.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
@ -1,13 +1,104 @@
|
|||||||
import flask
|
import flask
|
||||||
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
from aiogram.utils.web_app import safe_parse_webapp_init_data
|
||||||
from dash import Dash
|
from dash import Dash
|
||||||
from dash_extensions.enrich import Input
|
from dash_extensions.enrich import Input, Output
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
# TODO: добавить прокидывание BASE_PATH, т.к. это параметр из настроек
|
||||||
|
|
||||||
|
WEBAPP_LOADER_TEMPLATE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OCAB</title>
|
||||||
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
if (event.origin !== window.location.origin) return;
|
||||||
|
if (event.data.type === 'iframe-url-changed') {
|
||||||
|
history.pushState(
|
||||||
|
null,
|
||||||
|
'',
|
||||||
|
window.BASE_PATH + event.data.pathname.substring(
|
||||||
|
window.INTERNAL_PATH.length
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', function(event) {
|
||||||
|
var iframe = document.getElementById('app-frame');
|
||||||
|
var iframeWindow = iframe.contentWindow;
|
||||||
|
iframeWindow.history.back();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#app-frame {
|
||||||
|
display:none;
|
||||||
|
width:100%;
|
||||||
|
height:100vh;
|
||||||
|
border:none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loading">Loading...</div>
|
||||||
|
<iframe id="app-frame"></iframe>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const tg = window.Telegram.WebApp;
|
||||||
|
document.cookie = `tg_init_data=${JSON.stringify(tg.initData)}; path=/`;
|
||||||
|
|
||||||
|
// if (!tg.initData) return;
|
||||||
|
|
||||||
|
const iframe = document.getElementById('app-frame');
|
||||||
|
|
||||||
|
// Константы для путей
|
||||||
|
const BASE_PATH = '/webapp';
|
||||||
|
const INTERNAL_PATH = '/webapp/_internal';
|
||||||
|
|
||||||
|
window.BASE_PATH = BASE_PATH;
|
||||||
|
window.INTERNAL_PATH = INTERNAL_PATH
|
||||||
|
|
||||||
|
// Текущий путь страницы
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
// Формируем новый путь для iframe
|
||||||
|
let iframeSrc = INTERNAL_PATH;
|
||||||
|
|
||||||
|
// Если текущий путь начинается с BASE_PATH, убираем BASE_PATH из текущего пути
|
||||||
|
if (currentPath.startsWith(BASE_PATH)
|
||||||
|
&& currentPath.length > BASE_PATH.length) {
|
||||||
|
iframeSrc += currentPath.substring(BASE_PATH.length);
|
||||||
|
} else if (currentPath !== '/') {
|
||||||
|
iframeSrc += currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.src = iframeSrc;
|
||||||
|
|
||||||
|
iframe.onload = function() {
|
||||||
|
document.getElementById('loading').style.display = 'none';
|
||||||
|
iframe.style.display = 'block';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_auth_server(bot_token: str):
|
def get_auth_server(bot_token: str):
|
||||||
server = flask.Flask(__name__)
|
server = flask.Flask(__name__)
|
||||||
|
|
||||||
|
@server.route("/<path:rest>")
|
||||||
|
@server.route("/")
|
||||||
|
def webapp_loader(rest=None):
|
||||||
|
return flask.Response(WEBAPP_LOADER_TEMPLATE, mimetype="text/html")
|
||||||
|
|
||||||
@server.before_request
|
@server.before_request
|
||||||
def add_auth_data():
|
def add_auth_data():
|
||||||
init_data = request.cookies.get("tg_init_data")
|
init_data = request.cookies.get("tg_init_data")
|
||||||
@ -21,13 +112,35 @@ def get_auth_server(bot_token: str):
|
|||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
def setup_auth_clientcallback(app: Dash):
|
def setup_auth_clientcallbacks(app: Dash):
|
||||||
app.clientside_callback(
|
app.clientside_callback(
|
||||||
"""
|
"""
|
||||||
function(n_inervals) {
|
function(n_intervals) {
|
||||||
const tg = window.Telegram.WebApp;
|
if (window.webAppData) {
|
||||||
document.cookie = `tg_init_data=${JSON.stringify(tg.initData)}; path=/`;
|
return window.webAppData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function receiveMessage(event) {
|
||||||
|
if (event.data.type === 'webAppData') {
|
||||||
|
window.webAppData = event.data.webApp;
|
||||||
|
window.removeEventListener('message', receiveMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', receiveMessage, false);
|
||||||
|
|
||||||
|
return window.dash_clientside.no_update;
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
Input("init-telegram-interval", "n_intervals"),
|
Output("hidden-div", "children"),
|
||||||
|
Input("interval-component", "n_intervals"),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.clientside_callback(
|
||||||
|
"""
|
||||||
|
function(pathname) {
|
||||||
|
window.parent.postMessage({ type: 'iframe-url-changed', pathname }, '*');
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
Input("url", "pathname"),
|
||||||
)
|
)
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
|
import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import dash
|
import dash
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
import flask
|
||||||
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
from dash_extensions.enrich import DashBlueprint, DashProxy, Input, Output, dcc, html
|
||||||
from dash_extensions.pages import setup_page_components
|
from dash_extensions.pages import setup_page_components
|
||||||
|
|
||||||
from ocab_core.modules_system.public_api import get_module
|
from ocab_core.modules_system.public_api import get_module, log
|
||||||
|
|
||||||
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallback
|
from .dash_telegram_auth import get_auth_server, setup_auth_clientcallbacks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ocab_modules.standard.config import IConfig
|
from ocab_modules.standard.config import IConfig
|
||||||
|
from ocab_modules.standard.roles import Roles as IRoles
|
||||||
|
|
||||||
pages = OrderedDict()
|
pages = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
def register_page(name, path, blueprint, prefix=""):
|
def register_page(name, path, blueprint, prefix="", role="USER"):
|
||||||
pages[path] = {
|
pages[path] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
"blueprint": blueprint,
|
"blueprint": blueprint,
|
||||||
"prefix": prefix,
|
"prefix": prefix,
|
||||||
|
"role": role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,9 +37,14 @@ def register_home_page():
|
|||||||
register_home_page()
|
register_home_page()
|
||||||
|
|
||||||
config: "IConfig" = get_module("standard.config", "config")
|
config: "IConfig" = get_module("standard.config", "config")
|
||||||
|
Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
|
||||||
|
|
||||||
|
|
||||||
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
||||||
|
log(requests_pathname_prefix)
|
||||||
|
|
||||||
|
real_prefix = f"{requests_pathname_prefix}_internal/"
|
||||||
|
|
||||||
server = get_auth_server(config.get("core::token"))
|
server = get_auth_server(config.get("core::token"))
|
||||||
|
|
||||||
app = DashProxy(
|
app = DashProxy(
|
||||||
@ -47,10 +56,13 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
dbc.icons.BOOTSTRAP,
|
dbc.icons.BOOTSTRAP,
|
||||||
],
|
],
|
||||||
external_scripts=[
|
external_scripts=[
|
||||||
|
#
|
||||||
"https://telegram.org/js/telegram-web-app.js"
|
"https://telegram.org/js/telegram-web-app.js"
|
||||||
], # Add Telegram Mini Apps script to <head>
|
],
|
||||||
server=server,
|
server=server,
|
||||||
requests_pathname_prefix=requests_pathname_prefix,
|
requests_pathname_prefix=real_prefix,
|
||||||
|
routes_pathname_prefix="/_internal/",
|
||||||
|
# requests_pathname_prefix=requests_pathname_prefix,
|
||||||
meta_tags=[
|
meta_tags=[
|
||||||
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
{"name": "viewport", "content": "width=device-width, initial-scale=1"},
|
||||||
],
|
],
|
||||||
@ -63,30 +75,8 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
|
|
||||||
# Register pages
|
# Register pages
|
||||||
for path, page in pages.items():
|
for path, page in pages.items():
|
||||||
# dash.register_page(page["name"], path=path, layout=page["layout"])
|
|
||||||
page["blueprint"].register(app, path, prefix=page["prefix"])
|
page["blueprint"].register(app, path, prefix=page["prefix"])
|
||||||
|
|
||||||
# Create sidebar
|
|
||||||
sidebar = dbc.Offcanvas(
|
|
||||||
id="offcanvas",
|
|
||||||
title="Меню",
|
|
||||||
is_open=False,
|
|
||||||
children=[
|
|
||||||
dbc.Nav(
|
|
||||||
[
|
|
||||||
dbc.NavLink(
|
|
||||||
page["name"],
|
|
||||||
href=f"{requests_pathname_prefix}{path.lstrip('/')}",
|
|
||||||
id={"type": "nav-link", "index": path},
|
|
||||||
)
|
|
||||||
for path, page in pages.items()
|
|
||||||
],
|
|
||||||
vertical=True,
|
|
||||||
pills=True,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create navbar
|
# Create navbar
|
||||||
navbar = dbc.Navbar(
|
navbar = dbc.Navbar(
|
||||||
dbc.Container(
|
dbc.Container(
|
||||||
@ -104,24 +94,67 @@ def create_dash_app(requests_pathname_prefix: str = None) -> dash.Dash:
|
|||||||
dark=True,
|
dark=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define app layout
|
roles = Roles()
|
||||||
app.layout = html.Div(
|
|
||||||
[
|
|
||||||
dcc.Location(id="url", refresh=False),
|
|
||||||
dcc.Interval(
|
|
||||||
id="init-telegram-interval",
|
|
||||||
interval=100,
|
|
||||||
n_intervals=0,
|
|
||||||
max_intervals=1,
|
|
||||||
),
|
|
||||||
navbar,
|
|
||||||
sidebar,
|
|
||||||
dash.page_container,
|
|
||||||
setup_page_components(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
setup_auth_clientcallback(app)
|
def create_layout():
|
||||||
|
user = getattr(flask.g, "user", None)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return html.Div()
|
||||||
|
|
||||||
|
user_id = user["id"]
|
||||||
|
user_permission = asyncio.run(roles.get_user_permission(user_id)) or "USER"
|
||||||
|
|
||||||
|
available_pages = {
|
||||||
|
path: page
|
||||||
|
for path, page in pages.items()
|
||||||
|
if (isinstance(page["role"], list) and user_permission in page["role"])
|
||||||
|
or page["role"] == user_permission
|
||||||
|
or page["role"] == "USER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create sidebar
|
||||||
|
sidebar = dbc.Offcanvas(
|
||||||
|
id="offcanvas",
|
||||||
|
title="Меню",
|
||||||
|
is_open=False,
|
||||||
|
children=[
|
||||||
|
dbc.Nav(
|
||||||
|
[
|
||||||
|
dbc.NavLink(
|
||||||
|
page["name"],
|
||||||
|
href=f"{real_prefix}/{path.lstrip('/')}",
|
||||||
|
id={"type": "nav-link", "index": path},
|
||||||
|
)
|
||||||
|
for path, page in available_pages.items()
|
||||||
|
],
|
||||||
|
vertical=True,
|
||||||
|
pills=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
layout = html.Div(
|
||||||
|
[
|
||||||
|
dcc.Location(id="url", refresh=False),
|
||||||
|
dcc.Interval(
|
||||||
|
id="init-telegram-interval",
|
||||||
|
interval=100,
|
||||||
|
n_intervals=0,
|
||||||
|
max_intervals=1,
|
||||||
|
),
|
||||||
|
navbar,
|
||||||
|
sidebar,
|
||||||
|
dash.page_container,
|
||||||
|
setup_page_components(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return layout
|
||||||
|
|
||||||
|
app.layout = create_layout
|
||||||
|
|
||||||
|
setup_auth_clientcallbacks(app)
|
||||||
|
|
||||||
# Открытие на кнопку меню
|
# Открытие на кнопку меню
|
||||||
app.clientside_callback(
|
app.clientside_callback(
|
||||||
|
Loading…
Reference in New Issue
Block a user