Created modular system for calculators, added soap and candles calculators, universal components, updated backend
208 lines
8.0 KiB
JavaScript
208 lines
8.0 KiB
JavaScript
|
||
// backend/bot.js
|
||
// ───────────────────────────────────────────────────────────────────────────────
|
||
// 1) Устанавливаем зависимости:
|
||
// npm install express body-parser node-telegram-bot-api
|
||
//
|
||
// 2) Запуск:
|
||
// node bot.js
|
||
// (или: npx nodemon bot.js — для auto-reload при изменениях)
|
||
// ───────────────────────────────────────────────────────────────────────────────
|
||
|
||
const express = require('express');
|
||
const bodyParser = require('body-parser');
|
||
const TelegramBot = require('node-telegram-bot-api');
|
||
const crypto = require('crypto');
|
||
|
||
// Добавляем multer и streamifier
|
||
const multer = require('multer');
|
||
const streamifier = require('streamifier');
|
||
|
||
const app = express();
|
||
const HTTP_PORT = 3001; // порт для BACKEND API
|
||
const BOT_TOKEN = '7801636590:AAFphqOK0Dqta7v9VCLkTPGYC1OujNIFgXA'; // ← замените на свой токен
|
||
const WEBAPP_BASE_URL = 'https://dosoap.duckdns.org'; // ← например: https://xyz123.ngrok.io
|
||
|
||
// 1) Запускаем бота (polling)
|
||
const bot = new TelegramBot(BOT_TOKEN, { polling: true });
|
||
|
||
// 2) Настраиваем multer (храним файлы в памяти)
|
||
const storage = multer.memoryStorage();
|
||
const upload = multer({ storage });
|
||
|
||
// 3) Разрешаем CORS для фронтенда
|
||
app.use((req, res, next) => {
|
||
res.header('Access-Control-Allow-Origin', '*');
|
||
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
||
next();
|
||
});
|
||
|
||
// 4) JSON-парсер (не обязателен для multipart/form-data)
|
||
app.use(bodyParser.json());
|
||
|
||
// 5) Обработчик POST /api/submit (multipart/form-data)
|
||
app.post(
|
||
'/api/submit',
|
||
upload.single('photo'), // поле "photo" — это файл, если есть
|
||
(req, res) => {
|
||
try {
|
||
// Текстовые поля придут в req.body, файл — в req.file
|
||
const {
|
||
chat_id,
|
||
calculator_id,
|
||
calculator_name,
|
||
telegram_message,
|
||
} = req.body;
|
||
|
||
// Проверяем обязательные поля
|
||
if (!chat_id) {
|
||
return res.status(400).send('chat_id не передан');
|
||
}
|
||
|
||
// Если есть готовое сообщение для Telegram (форматированное на фронтенде),
|
||
// используем его, иначе формируем универсальное
|
||
let text = '';
|
||
|
||
if (telegram_message) {
|
||
// Используем готовое сообщение из конфигурации калькулятора
|
||
text = telegram_message;
|
||
} else {
|
||
// Формируем универсальное сообщение из всех полей
|
||
// (fallback для старых версий или калькуляторов без formatTelegramMessage)
|
||
text = `📊 <b>Расчёт:</b> ${calculator_name || 'Калькулятор'}\n\n`;
|
||
|
||
// Добавляем все пользовательские поля (кроме служебных)
|
||
const excludeFields = ['chat_id', 'calculator_id', 'calculator_name', 'telegram_message'];
|
||
Object.keys(req.body).forEach(key => {
|
||
if (!excludeFields.includes(key) && !key.startsWith('step_') && !key.startsWith('subtotal_') && !key.startsWith('additional_')) {
|
||
const value = req.body[key];
|
||
if (value !== undefined && value !== '' && value !== null) {
|
||
text += `${key}: ${value}\n`;
|
||
}
|
||
}
|
||
});
|
||
|
||
// Добавляем результаты расчетов
|
||
const steps = {};
|
||
const subtotals = {};
|
||
const additional = {};
|
||
|
||
Object.keys(req.body).forEach(key => {
|
||
if (key.startsWith('step_')) {
|
||
steps[key.replace('step_', '')] = req.body[key];
|
||
} else if (key.startsWith('subtotal_')) {
|
||
subtotals[key.replace('subtotal_', '')] = req.body[key];
|
||
} else if (key.startsWith('additional_')) {
|
||
additional[key.replace('additional_', '')] = req.body[key];
|
||
}
|
||
});
|
||
|
||
if (Object.keys(steps).length > 0) {
|
||
text += '\n<b>Шаги расчёта:</b>\n';
|
||
Object.keys(steps).forEach(key => {
|
||
text += ` ${key}: ${Number(steps[key]).toFixed(2)} ₽\n`;
|
||
});
|
||
}
|
||
|
||
if (Object.keys(subtotals).length > 0) {
|
||
text += '\n<b>Подитоги:</b>\n';
|
||
Object.keys(subtotals).forEach(key => {
|
||
text += ` ${key}: ${Number(subtotals[key]).toFixed(2)} ₽\n`;
|
||
});
|
||
}
|
||
|
||
if (Object.keys(additional).length > 0) {
|
||
text += '\n<b>Дополнительно:</b>\n';
|
||
Object.keys(additional).forEach(key => {
|
||
text += ` ${key}: ${Number(additional[key]).toFixed(2)} ₽\n`;
|
||
});
|
||
}
|
||
}
|
||
|
||
// Если пользователь прикрепил фото (req.file), шлём sendPhoto
|
||
if (req.file) {
|
||
const bufferStream = streamifier.createReadStream(req.file.buffer);
|
||
|
||
return bot
|
||
.sendPhoto(
|
||
Number(chat_id),
|
||
bufferStream,
|
||
{ caption: text, parse_mode: 'HTML' }
|
||
)
|
||
.then(() => res.sendStatus(200))
|
||
.catch((err) => {
|
||
console.error('Ошибка при отправке фото:', err);
|
||
res.status(500).send('Ошибка при отправке фото ботом');
|
||
});
|
||
}
|
||
|
||
// Если фото не было, просто шлём текст
|
||
bot
|
||
.sendMessage(Number(chat_id), text, { parse_mode: 'HTML' })
|
||
.then(() => res.sendStatus(200))
|
||
.catch((err) => {
|
||
console.error('Ошибка при отправке сообщения:', err);
|
||
res.sendStatus(500);
|
||
});
|
||
} catch (err) {
|
||
console.error('Ошибка в /api/submit:', err);
|
||
res.status(500).send('Внутренняя ошибка сервера');
|
||
}
|
||
}
|
||
);
|
||
|
||
// 6) Команда /menu — отправляем inline-кнопку с chat_id
|
||
bot.setMyCommands([
|
||
{ command: 'menu', description: 'Открыть калькулятор' },
|
||
{ command: 'myid', description: 'Узнать мой chat_id' },
|
||
]);
|
||
|
||
bot.onText(/\/menu/, (msg) => {
|
||
try {
|
||
const chatId = msg.chat.id;
|
||
const url = `${WEBAPP_BASE_URL}/?chat_id=${chatId}`;
|
||
|
||
bot.sendMessage(
|
||
chatId,
|
||
'Нажмите кнопку ниже, чтобы открыть калькулятор:',
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [
|
||
[
|
||
{
|
||
text: 'Открыть калькулятор',
|
||
web_app: { url },
|
||
},
|
||
],
|
||
],
|
||
},
|
||
}
|
||
);
|
||
} catch (err) {
|
||
console.error('Ошибка в обработчике /menu:', err);
|
||
}
|
||
});
|
||
|
||
// Команда для получения chat_id
|
||
bot.onText(/\/myid/, (msg) => {
|
||
try {
|
||
const chatId = msg.chat.id;
|
||
bot.sendMessage(
|
||
chatId,
|
||
`Ваш chat_id: <code>${chatId}</code>\n\nВы можете открыть калькулятор напрямую по ссылке:\n${WEBAPP_BASE_URL}/?chat_id=${chatId}`,
|
||
{ parse_mode: 'HTML' }
|
||
);
|
||
} catch (err) {
|
||
console.error('Ошибка в обработчике /myid:', err);
|
||
}
|
||
});
|
||
|
||
// 7) Ловим ошибки polling-а и логируем детали
|
||
bot.on('polling_error', (err) => {
|
||
console.error('Polling error:', err);
|
||
});
|
||
|
||
// 8) Запускаем Express-сервер на порту 3001
|
||
app.listen(HTTP_PORT, () => {
|
||
console.log(`Bot+API запущены, слушаем порт ${HTTP_PORT}`);
|
||
}); |