163 lines
5.3 KiB
JavaScript
163 lines
5.3 KiB
JavaScript
const express = require('express');
|
||
const axios = require('axios');
|
||
const app = express();
|
||
|
||
app.use(express.json());
|
||
|
||
// --- 1. 設定情報 ---
|
||
const ACCESS_TOKEN = 'YOUR_ACCESS_TOKEN';
|
||
const BOT_ID = 'YOUR_BOT_ID';
|
||
const API_BASE_URL = `https://www.worksapis.com/v1.0/bots/${BOT_ID}/users`;
|
||
|
||
// --- 2. アンケート項目の定義 ---
|
||
const SURVEY_QUESTIONS = [
|
||
{
|
||
id: 'category',
|
||
type: 'select',
|
||
text: 'Q1. お問い合わせの種類を選択してください。',
|
||
options: ['製品について', '採用について', 'その他']
|
||
},
|
||
{
|
||
id: 'age',
|
||
type: 'number',
|
||
text: 'Q2. 年齢を数字のみで入力してください(例: 25)。'
|
||
},
|
||
{
|
||
id: 'comment',
|
||
type: 'text',
|
||
text: 'Q3. 自由にご意見をご記入ください。'
|
||
}
|
||
];
|
||
|
||
// ユーザーの状態管理(メモリ保存用)
|
||
// 実運用では Redis や DB を推奨
|
||
const userStates = {};
|
||
|
||
// --- 3. メッセージ送信ヘルパー関数 ---
|
||
async function sendBotRequest(userId, payload) {
|
||
try {
|
||
await axios.post(`${API_BASE_URL}/${userId}/messages`, payload, {
|
||
headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}`, 'Content-Type': 'application/json' }
|
||
});
|
||
} catch (err) {
|
||
console.error('API Error:', err.response?.data || err.message);
|
||
}
|
||
}
|
||
|
||
// 質問を送信するメイン関数
|
||
async function sendQuestion(userId, stepIndex) {
|
||
const question = SURVEY_QUESTIONS[stepIndex];
|
||
let content = {};
|
||
|
||
// 中断ボタン(共通)
|
||
const cancelAction = { type: 'postback', label: 'アンケートを中断', data: 'action=cancel' };
|
||
|
||
if (question.type === 'select') {
|
||
// ボタン形式
|
||
content = {
|
||
type: 'button_template',
|
||
contentText: `【${stepIndex + 1}/${SURVEY_QUESTIONS.length}】\n${question.text}`,
|
||
actions: [
|
||
...question.options.map(opt => ({
|
||
type: 'postback',
|
||
label: opt,
|
||
data: `step=${stepIndex}&ans=${opt}`
|
||
})),
|
||
cancelAction
|
||
]
|
||
};
|
||
} else {
|
||
// テキスト・数字入力形式
|
||
content = {
|
||
type: 'text',
|
||
text: `【${stepIndex + 1}/${SURVEY_QUESTIONS.length}】\n${question.text}`
|
||
};
|
||
}
|
||
|
||
const payload = { content };
|
||
|
||
// テキスト入力時にはクイックリプライで「中断」を表示
|
||
if (question.type !== 'select') {
|
||
payload.quickReply = { items: [{ action: cancelAction }] };
|
||
}
|
||
|
||
await sendBotRequest(userId, payload);
|
||
}
|
||
|
||
// --- 4. アンケート開始トリガー(プッシュ送信用) ---
|
||
async function initiateSurvey(userId) {
|
||
userStates[userId] = { currentStep: 0, answers: {} };
|
||
await sendQuestion(userId, 0);
|
||
}
|
||
|
||
// --- 5. Webhook 受信処理 ---
|
||
app.post('/callback', async (req, res) => {
|
||
const event = req.body;
|
||
const userId = event.source?.userId;
|
||
if (!userId) return res.sendStatus(200);
|
||
|
||
// A. 中断処理
|
||
if (event.type === 'postback' && event.postback?.data === 'action=cancel') {
|
||
delete userStates[userId];
|
||
await sendBotRequest(userId, { content: { type: 'text', text: 'アンケートを中断しました。' } });
|
||
return res.sendStatus(200);
|
||
}
|
||
|
||
// B. アンケート中のユーザーか確認
|
||
const state = userStates[userId];
|
||
if (!state) {
|
||
// アンケート中ではない場合、特定のワードで開始させる
|
||
if (event.type === 'message' && event.content.text === 'アンケート開始') {
|
||
await initiateSurvey(userId);
|
||
}
|
||
return res.sendStatus(200);
|
||
}
|
||
|
||
const currentQuestion = SURVEY_QUESTIONS[state.currentStep];
|
||
let answer = null;
|
||
|
||
// C. 回答の受け取りとバリデーション
|
||
if (currentQuestion.type === 'select' && event.type === 'postback') {
|
||
const params = new URLSearchParams(event.postback.data);
|
||
answer = params.get('ans');
|
||
} else if (event.type === 'message' && event.content.type === 'text') {
|
||
const inputText = event.content.text;
|
||
|
||
if (currentQuestion.type === 'number') {
|
||
if (!/^\d+$/.test(inputText)) {
|
||
await sendBotRequest(userId, { content: { type: 'text', text: '⚠️ 半角数字のみで入力してください。' } });
|
||
return res.sendStatus(200);
|
||
}
|
||
}
|
||
answer = inputText;
|
||
}
|
||
|
||
// D. 次のステップへ進む処理
|
||
if (answer !== null) {
|
||
state.answers[currentQuestion.id] = answer;
|
||
state.currentStep++;
|
||
|
||
if (state.currentStep < SURVEY_QUESTIONS.length) {
|
||
await sendQuestion(userId, state.currentStep);
|
||
} else {
|
||
// 全回答完了
|
||
const summary = Object.entries(state.answers)
|
||
.map(([id, val]) => `・${id}: ${val}`).join('\n');
|
||
|
||
await sendBotRequest(userId, {
|
||
content: { type: 'text', text: `ご協力ありがとうございました!\n回答内容:\n${summary}` }
|
||
});
|
||
|
||
console.log(`【集計完了】User: ${userId}`, state.answers);
|
||
delete userStates[userId];
|
||
}
|
||
}
|
||
|
||
res.sendStatus(200);
|
||
});
|
||
|
||
// サーバー起動
|
||
const PORT = process.env.PORT || 3000;
|
||
app.listen(PORT, () => {
|
||
console.log(`Server is running on port ${PORT}`);
|
||
}); |