DoSoapCalc/backend/bot.js
2025-06-04 02:46:45 +03:00

181 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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://d472-46-246-24-76.ngrok-free.app/'; // ← например: 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 = `🧼 <b>Расчёт мыла:</b> <i>${soapName}</i>\n\n`;
text += `🔹 <b>Вес мыла:</b> ${weight} г\n`;
text += `🔹 <b>Цена за 1 кг основы:</b> ${basePrice} ₽/кг\n\n`;
text += `🔹 <b>Отдушка:</b> ${aromaWeight} г по ${aromaPrice} ₽/фасовка\n`;
text += `🔹 <b>Пигмент:</b> ${pigmentWeight} г по ${pigmentPrice} ₽/фасовка\n\n`;
text += `🔹 <b>Цена формы:</b> ${moldPrice}\n\n`;
text += `🔹 <b>Упаковка:</b>\n`;
text += ` • Пакет/коробка: ${box}\n`;
text += ` • Наполнитель: ${filler}\n`;
text += ` • Лента: ${ribbon}\n`;
text += ` • Наклейка: ${labelValue}\n\n`;
text += `🔹 <b>Наценка:</b> ${markup}%\n\n`;
text += `📊 <b>Итоги расчёта:</b>\n`;
text += ` • Себестоимость: ${Number(totalCost).toFixed(1)}\n`;
text += ` • Итоговая цена с наценкой: ${Number(finalPrice).toFixed(1)}\n`;
text += ` • Цена за 100 г: ${Number(pricePer100g).toFixed(1)}`;
// Если пользователь прикрепил фото (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}`);
});