// 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, soapName, weight, basePrice, aromaPrice, aromaWeight, pigmentPrice, pigmentWeight, moldPrice, box, filler, ribbon, labelValue, markup, totalCost, finalPrice, pricePer100g, } = req.body; // Проверяем обязательные поля if (!chat_id) { return res.status(400).send('chat_id не передан'); } if (!soapName) { return res.status(400).send('soapName не передан'); } // Соберём сообщение так, чтобы в чат пришло всё, что ввели: // 1. Название мыла // 2. Вес и цена основы // 3. Отдушка // 4. Пигмент // 5. Форма // 6. Упаковка // 7. Наценка // 8. Итоги // let text = `🧼 Расчёт мыла: ${soapName}\n\n`; // text += `🔹 Вес мыла: ${weight} г\n\n`; // // text += `🔹 Цена за 1 кг основы: ${basePrice} ₽/кг\n\n`; // // text += `🔹 Отдушка: ${aromaWeight} г по ${aromaPrice} ₽/фасовка\n`; // // text += `🔹 Пигмент: ${pigmentWeight} г по ${pigmentPrice} ₽/фасовка\n\n`; // // text += `🔹 Цена формы: ${moldPrice} ₽\n\n`; // text += `🔹 Упаковка:\n`; // text += ` • Пакет/коробка: ${box} ₽\n`; // text += ` • Наполнитель: ${filler} ₽\n`; // text += ` • Лента: ${ribbon} ₽\n`; // text += ` • Наклейка: ${labelValue} ₽\n\n`; // text += `🔹 Наценка: ${markup}%\n\n`; // text += `📊 Итоги расчёта:\n`; // text += ` • Себестоимость: ${Number(totalCost).toFixed(1)} ₽\n`; // text += ` • Итоговая цена с наценкой: ${Number(finalPrice).toFixed(1)} ₽\n`; // text += ` • Цена за 100 г: ${Number(pricePer100g).toFixed(1)} ₽`; let text = `🧼 Расчёт мыла: ${soapName}\n ────────────────────────\n ⚖️ Вес мыла: ${weight} г\n ────────────────────────\n 📦 Упаковка:\n 📥 Пакет/коробка: ${box} ₽\n 🌾 Наполнитель: ${filler} ₽\n 🎀 Лента: ${ribbon} ₽\n 🏷️ Наклейка: ${labelValue} ₽\n ────────────────────────\n 💹 Наценка: ${markup}%\n ────────────────────────\n 📊 Итоги расчёта:\n 💵 Себестоимость: ${Number(totalCost).toFixed(1)} ₽\n 🎯 Итоговая цена: ${Number(finalPrice).toFixed(1)} ₽\n ⚗️ Цена за 100 г: ${Number(pricePer100g).toFixed(1)} ₽\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: 'Открыть калькулятор' }, ]); 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); } }); // 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}`); });