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}`); });