Sequence dialog example
This commit is contained in:
parent
1e04cb169e
commit
3371759544
|
@ -11,3 +11,6 @@
|
|||
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
|
||||
node_modules
|
||||
.idea
|
|
@ -0,0 +1,33 @@
|
|||
# Loop Dialog Sequence Example
|
||||
|
||||
Пример приложения для последовательного открытия модальных окон с формой.
|
||||
|
||||
### Запуск и установка
|
||||
|
||||
В файле index.js меняем переменные:
|
||||
- SERVER_URL - адрес вашего сервера Loop
|
||||
- LOOP_URL - адрес приложения
|
||||
- PORT - по желанию
|
||||
|
||||
Устанавливаем зависимости `yarn` или `npm install`
|
||||
|
||||
Запускаем командой `yarn start` или `npm start`
|
||||
|
||||
Переходим в Loop и устанавливаем приложение через слеш команду в любом канале:
|
||||
`/apps install http _SERVER_URL_/manifest.json`
|
||||
в появившемся окне ставим галочку, что мы все понимаем и подтверждаем.
|
||||
|
||||
После установки должна появиться новая кнопка на правой панели или в верхней панели канала.
|
||||
- Переходим в Loop и создаем тестовый канал
|
||||
- Приглашаем бота ru.loop.dialog-sequence-example в команду
|
||||
- Добавляем бота ru.loop.dialog-sequence-example в тестовый канал
|
||||
- Нажимаем на новую кнопку "Открыть форму" (с иконкой Мистфикса)
|
||||
- Заполняем форму и нажимаем отправить
|
||||
- Если все хорошо, в канал придет сообщение от бота и откроется следующая форма
|
||||
- Enjoy
|
||||
|
||||
#### Важные замечания по работе с apps framework
|
||||
- Все ответы должны быть с кодом 200 (не 201 и тд.)
|
||||
- Для получения данных о канале требуется разрешение act_as_user, если его убрать то контекст канала приходить не будет
|
||||
- Все иконки описываются без указания пути, Loop всегда будет их искать по пути `_SERVER_URL_/static/`
|
||||
- Без основной иконки (указанной в манифесте) приложение не установится
|
Binary file not shown.
After Width: | Height: | Size: 3.1 MiB |
|
@ -0,0 +1,178 @@
|
|||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Адрес сервера данного приложения (для доступа из Интернета)
|
||||
const SERVER_URL = '';
|
||||
// Адрес сервера Loop
|
||||
const LOOP_URL = '';
|
||||
// Порт на котором будет слушать наш сервер
|
||||
const PORT = 4000;
|
||||
|
||||
// Описание первой формы
|
||||
const form1 = {
|
||||
title: 'Первая форма',
|
||||
icon: 'icon.png', // Иконку для каждой формы можно задать свою
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'message',
|
||||
modal_label: 'Тут какое-то поле',
|
||||
position: 1,
|
||||
is_required: true,
|
||||
},
|
||||
],
|
||||
submit: {
|
||||
// Куда отправлять запрос после нажатия кнопки "Отправить"
|
||||
path: '/second_modal',
|
||||
// Контекст для запроса
|
||||
expand: {
|
||||
// Запрашиваем данные пользователя который вызвал действие
|
||||
acting_user: "all",
|
||||
// запрашиваем канал в котором было вызвано действие
|
||||
channel: "all",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Описание второй формы
|
||||
const form2 = {
|
||||
title: 'Вторая форма',
|
||||
icon: 'icon.png',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'message2',
|
||||
modal_label: 'Еще одно поле',
|
||||
position: 1,
|
||||
},
|
||||
],
|
||||
submit: {
|
||||
// Куда отправлять запрос после нажатия кнопки "Отправить"
|
||||
path: '/second_result',
|
||||
expand: {
|
||||
// Запрашиваем данные пользователя который вызвал действие
|
||||
acting_user: "all",
|
||||
// запрашиваем канал в котором было вызвано действие
|
||||
channel: "all",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Отправка сообщения в канал Loop
|
||||
const createPost = async (bot_access_token, channel_id, message) => {
|
||||
try {
|
||||
await fetch(`${LOOP_URL}/api/v4/posts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + bot_access_token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
channel_id,
|
||||
message,
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error when create post', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик результата первой формы
|
||||
app.post('/second_modal', (req, res) => {
|
||||
const context = req.body.context;
|
||||
|
||||
// Отправляем данные формы в канал
|
||||
createPost(
|
||||
context.bot_access_token,
|
||||
context.channel.id,
|
||||
'### Заполнена форма 1\n```json\n' + JSON.stringify(req.body.values, null, 2) + '\n```',
|
||||
);
|
||||
|
||||
// Отвечаем второй формой. Обязательно статус 200 (не 201 и тд)
|
||||
res.status(200).json({
|
||||
type: 'form',
|
||||
form: form2,
|
||||
})
|
||||
});
|
||||
|
||||
// Обработчик результата второй формы
|
||||
app.post('/second_result', (req, res) => {
|
||||
const context = req.body.context;
|
||||
|
||||
// Отправляем результат в канал
|
||||
createPost(
|
||||
context.bot_access_token,
|
||||
context.channel.id,
|
||||
'### Заполнена форма 2\n```json\n' + JSON.stringify(req.body.values, null, 2) + '\n```',
|
||||
);
|
||||
|
||||
// Отвечаем что все хорошо. Обязательно статус 200 (не 201 и тд)
|
||||
res.status(200).json({
|
||||
type: 'ok',
|
||||
});
|
||||
})
|
||||
|
||||
// Данный метод будет вызываться при открытии любого канала, любым пользователем
|
||||
// Отвечает за отрисовку кнопки в сайдбаре
|
||||
app.post('/bindings', (req, res) => {
|
||||
res.status(200).json({
|
||||
type: 'ok',
|
||||
data: [
|
||||
{
|
||||
// Место где показать кнопку
|
||||
location: '/channel_header',
|
||||
// Описание кнопки
|
||||
bindings: [
|
||||
{
|
||||
// идентификатор, который будет отправлен в запросе
|
||||
location: 'send-button',
|
||||
// Иконка кнопки
|
||||
icon: 'icon.png',
|
||||
// Подпись для кнопки
|
||||
label: 'Открыть форму',
|
||||
// Первая форма
|
||||
form: form1,
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
// Манифест для установки приложения
|
||||
app.get('/manifest.json', (req, res) => {
|
||||
res.json({
|
||||
app_id: 'ru.loop.dialog-sequence-example',
|
||||
version: '0.1.0',
|
||||
display_name: 'Loop Dialog Sequence Example',
|
||||
description: 'An app that opens modal dialogs sequentially',
|
||||
icon: 'icon.png',
|
||||
homepage_url: 'https://git.wilix.dev/loop/integration-examples',
|
||||
// Разрешения для приложения
|
||||
requested_permissions: [
|
||||
// разрешение, чтобы приложение могло писать от имени бота
|
||||
'act_as_bot',
|
||||
// разрешение, чтобы получать данные пользователя, который вызывает действия
|
||||
'act_as_user'
|
||||
],
|
||||
// Разрешения для установки кнопок или команд
|
||||
requested_locations: [
|
||||
// Устанавливает кнопку во всех каналах в сайдбаре или в заголовке канала
|
||||
// (расположение завит от того, включен ли сайдбар)
|
||||
'/channel_header'
|
||||
],
|
||||
app_type: 'http',
|
||||
http: {
|
||||
root_url: SERVER_URL
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Сервим иконки из папки. Все иконки всегда будут искаться по пути /static
|
||||
app.use('/static', express.static('./static'));
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "loop-dialog-sequence-example",
|
||||
"version": "0.1.0",
|
||||
"description": "Example App for dialog sequence",
|
||||
"scripts": {
|
||||
"start": "node ./index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "21"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "4.17.1"
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "modal-pages-test",
|
||||
"packageManager": "yarn@4.1.1"
|
||||
}
|
12
yarn.lock
12
yarn.lock
|
@ -1,12 +0,0 @@
|
|||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"modal-pages-test@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "modal-pages-test@workspace:."
|
||||
languageName: unknown
|
||||
linkType: soft
|
Loading…
Reference in New Issue