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