Описание изменений

This commit is contained in:
2025-03-18 16:35:13 +03:00
parent 513db4e206
commit 31df93e8b1
15 changed files with 1636 additions and 1073 deletions

View File

@@ -1,6 +1,7 @@
const db = require('../db');
const logger = require('../utils/logger');
const { ethers } = require('ethers');
const { processMessage } = require('./ai-assistant'); // Используем AI Assistant
// В начале файла auth-service.js
const getProvider = (network) => {
@@ -246,6 +247,108 @@ class AuthService {
return false;
}
}
/**
* Обрабатывает гостевые сообщения после аутентификации
*/
async processGuestMessages(userId, guestId) {
try {
logger.info(`Processing guest messages for user ${userId} with guestId ${guestId}`);
// Сначала обновляем user_id для всех бесед с гостевыми сообщениями
await db.query(
`UPDATE conversations c
SET user_id = $1
WHERE id IN (
SELECT DISTINCT conversation_id
FROM messages m
WHERE m.metadata->>'guest_id' = $2
)`,
[userId, guestId]
);
// Получаем все гостевые сообщения без ответов
const guestMessages = await db.query(
`SELECT m.id, m.content, m.conversation_id, m.metadata, m.created_at
FROM messages m
WHERE m.metadata->>'guest_id' = $1
AND NOT EXISTS (
SELECT 1 FROM messages
WHERE conversation_id = m.conversation_id
AND sender_type = 'assistant'
)
ORDER BY m.created_at ASC`,
[guestId]
);
logger.info(`Found ${guestMessages.rows.length} unprocessed guest messages`);
// Обрабатываем каждое гостевое сообщение
for (const msg of guestMessages.rows) {
logger.info(`Processing guest message ${msg.id}: ${msg.content}`);
// Получаем язык из метаданных
const metadata = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata;
const language = metadata?.language || 'ru';
// Используем AI Assistant для обработки сообщения
const aiResponse = await processMessage(userId, msg.content, language);
// Сохраняем ответ AI в ту же беседу
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'assistant', $2, 'chat', NOW())`,
[msg.conversation_id, aiResponse]
);
logger.info(`Saved AI response for message ${msg.id}`);
}
// Обновляем метаданные сообщений, чтобы показать, что они обработаны
await db.query(
`UPDATE messages m
SET metadata = jsonb_set(
CASE
WHEN m.metadata IS NULL THEN '{}'::jsonb
ELSE m.metadata::jsonb
END,
'{processed}',
'true'
)
WHERE m.metadata->>'guest_id' = $1`,
[guestId]
);
logger.info(`Successfully processed all guest messages for user ${userId}`);
return true;
} catch (error) {
logger.error('Error processing guest messages:', error);
return false;
}
}
async disconnect() {
try {
// Очищаем состояние аутентификации
this.isAuthenticated = false;
this.userId = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
// Очищаем сессию
localStorage.removeItem('auth');
// Очищаем guestId
localStorage.removeItem('guestId');
return true;
} catch (error) {
logger.error('Error during disconnect:', error);
return false;
}
}
}
module.exports = new AuthService();

View File

@@ -1,273 +1,218 @@
const TelegramBot = require('node-telegram-bot-api');
const logger = require('../utils/logger');
const { pool } = require('../db');
const crypto = require('crypto');
// Создаем бота
const token = process.env.TELEGRAM_BOT_TOKEN;
let bot = null;
// Добавим хранилище для кодов подтверждения
const verificationCodes = new Map(); // Формат: { telegramId: { code: '123456', token: 'auth_token', expires: timestamp } }
/**
* Функция для отправки кода подтверждения
*/
async function sendVerificationCode(chatId) {
try {
// Генерируем код и токен
const code = Math.floor(100000 + Math.random() * 900000).toString();
const authToken = crypto.randomBytes(32).toString('hex');
// Создаем пользователя и сохраняем код в базу данных
const result = await pool.query(
`WITH new_user AS (
INSERT INTO users (created_at)
VALUES (NOW())
RETURNING id
)
INSERT INTO telegram_auth_tokens
(user_id, token, verification_code, telegram_id, expires_at)
VALUES (
(SELECT id FROM new_user),
$1, $2, $3,
NOW() + INTERVAL '5 minutes'
)
RETURNING user_id`,
[authToken, code, chatId.toString()]
);
// Отправляем код с инлайн-кнопкой
const sentMessage = await bot.sendMessage(chatId,
'Привет! Я бот для аутентификации в DApp for Business.\n\n' +
'🔐 Ваш код подтверждения:\n\n' +
`<code>${code}</code>\n\n` +
'Введите этот код на сайте для завершения авторизации.\n' +
'Код действителен в течение 5 минут.',
{
parse_mode: 'HTML',
reply_markup: {
inline_keyboard: [
[{ text: '🔄 Получить новый код', callback_data: 'new_code' }]
]
}
}
);
// Удаляем сообщение через 30 секунд
setTimeout(async () => {
try {
await bot.deleteMessage(chatId, sentMessage.message_id);
await bot.sendMessage(chatId,
'Для получения нового кода используйте команду /start или меню команд',
{
reply_markup: {
keyboard: [
[{ text: '/start' }]
],
resize_keyboard: true,
persistent: true
}
}
);
} catch (error) {
console.error('Error deleting message:', error);
}
}, 30000);
return { code, token: authToken, userId: result.rows[0].user_id };
} catch (error) {
console.error('Error sending verification code:', error);
throw error;
}
}
/**
* Функция для проверки кода
*/
async function verifyCode(code) {
try {
const result = await pool.query(
`SELECT token, telegram_id, user_id
FROM telegram_auth_tokens
WHERE verification_code = $1
AND expires_at > NOW()
AND NOT used`,
[code]
);
if (result.rows.length === 0) {
return { success: false, error: 'Неверный или истекший код' };
}
const { token, telegram_id, user_id } = result.rows[0];
// Помечаем токен как использованный
await pool.query(
'UPDATE telegram_auth_tokens SET used = true WHERE token = $1',
[token]
);
// Добавляем Telegram ID в таблицу идентификаторов
await pool.query(
`INSERT INTO user_identities
(user_id, identity_type, identity_value, verified, created_at)
VALUES ($1, 'telegram', $2, true, NOW())
ON CONFLICT (identity_type, identity_value)
DO UPDATE SET verified = true`,
[user_id, telegram_id]
);
return {
success: true,
telegramId: telegram_id,
userId: user_id
};
} catch (error) {
console.error('Error verifying code:', error);
throw error;
}
}
/**
* Инициализация Telegram бота
* @returns {Object|null} - Объект с методами для работы с ботом или null, если инициализация не удалась
*/
function initTelegramBot() {
if (!token) {
console.warn('TELEGRAM_BOT_TOKEN not set, Telegram integration disabled');
console.warn('TELEGRAM_BOT_TOKEN not set');
return null;
}
try {
// Создаем бота с опцией polling
bot = new TelegramBot(token, { polling: true });
// Создаем бота с опцией обработки ошибок
bot = new TelegramBot(token, {
polling: {
autoStart: true,
params: {
timeout: 10
}
},
request: {
timeout: 30000, // увеличиваем таймаут до 30 секунд
proxy: process.env.HTTPS_PROXY // используем прокси если есть
}
});
console.log('Telegram bot initialized');
// Регистрируем обработчики событий
registerHandlers();
// Очищаем все предыдущие обработчики
bot.removeAllListeners();
// Устанавливаем команды бота с обработкой ошибок
bot.setMyCommands([
{ command: '/start', description: 'Получить код подтверждения' },
{ command: '/help', description: 'Показать справку' }
]).catch(error => {
console.warn('Error setting bot commands:', error);
// Продолжаем работу даже если не удалось установить команды
});
// Обработчик команды /start
bot.onText(/\/start/, async (msg) => {
const chatId = msg.chat.id;
try {
await sendVerificationCode(chatId);
} catch (error) {
console.error('Error handling /start:', error);
await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.')
.catch(err => console.error('Error sending error message:', err));
}
});
// Обработчик ошибок polling
bot.on('polling_error', (error) => {
console.error('Telegram bot polling error:', error);
// Перезапускаем polling при ошибке
setTimeout(() => {
try {
bot.startPolling();
} catch (e) {
console.error('Error restarting polling:', e);
}
}, 10000); // пробуем перезапустить через 10 секунд
});
// Обработчик остановки polling
bot.on('stop', () => {
console.log('Bot polling stopped');
// Пробуем перезапустить
setTimeout(() => {
try {
bot.startPolling();
} catch (e) {
console.error('Error restarting polling after stop:', e);
}
}, 5000);
});
return bot;
return {
bot,
sendMessage: (chatId, text) => bot.sendMessage(chatId, text)
};
} catch (error) {
console.error('Error initializing Telegram bot:', error);
return null;
}
}
/**
* Регистрация обработчиков событий для бота
*/
function registerHandlers() {
// Обработчик /start
bot.onText(/\/start(.*)/, async (msg, match) => {
const chatId = msg.chat.id;
const param = match[1] ? match[1].trim() : '';
console.log(`Получена команда /start с параметром: "${param}" от пользователя ${chatId}`);
if (param.startsWith('auth_')) {
// Это токен авторизации через deep link
const authToken = param.replace('auth_', '');
console.log(`Обработка токена авторизации: ${authToken}`);
try {
// Проверяем, существует ли токен
const { pool } = require('../db');
const tokenResult = await pool.query(
'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1',
[authToken]
);
if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) {
bot.sendMessage(chatId, '❌ Недействительный или истекший токен авторизации.');
return;
}
// Генерируем код подтверждения
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код
// Сохраняем в хранилище
verificationCodes.set(chatId.toString(), {
code: verificationCode,
token: authToken,
expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут
});
// Отправляем код пользователю
bot.sendMessage(chatId,
'🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' +
`<code>${verificationCode}</code>\n\n` +
'Код действителен в течение 5 минут.',
{ parse_mode: 'HTML' }
);
} catch (error) {
console.error('Error processing auth token:', error);
bot.sendMessage(chatId, '❌ Произошла ошибка при обработке запроса авторизации.');
}
} else {
// Получаем последний активный токен для этого чата, если есть
const { pool } = require('../db');
try {
const lastTokenResult = await pool.query(`
SELECT token FROM telegram_auth_tokens
WHERE expires_at > NOW() AND used = FALSE
ORDER BY created_at DESC LIMIT 1
`);
if (lastTokenResult.rows.length > 0) {
const authToken = lastTokenResult.rows[0].token;
// Генерируем код подтверждения
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код
// Сохраняем в хранилище
verificationCodes.set(chatId.toString(), {
code: verificationCode,
token: authToken,
expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут
});
// Отправляем код пользователю
bot.sendMessage(chatId,
'🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' +
`<code>${verificationCode}</code>\n\n` +
'Код действителен в течение 5 минут.',
{ parse_mode: 'HTML' }
);
return;
}
} catch (error) {
console.error('Error checking last token:', error);
}
// Если нет активного токена, отправляем стандартное сообщение
bot.sendMessage(chatId,
'Привет! Я бот для аутентификации в DApp for Business.\n\n' +
'Для связи с вашим аккаунтом используйте кнопку на сайте.'
);
}
});
// Обработчик для проверки кода подтверждения
bot.on('message', async (msg) => {
const chatId = msg.chat.id;
const text = msg.text;
// Игнорируем команды
if (text && text.startsWith('/')) return;
// Проверяем, есть ли ожидающая верификация для этого чата
const verificationData = verificationCodes.get(chatId.toString());
if (verificationData && text === verificationData.code) {
// Код верный, проверяем срок действия
if (Date.now() > verificationData.expires) {
bot.sendMessage(chatId, '❌ Срок действия кода истек. Пожалуйста, начните процесс заново.');
verificationCodes.delete(chatId.toString());
return;
}
// Код верный и актуальный, завершаем аутентификацию
try {
const result = await linkTelegramAccount(chatId.toString(), verificationData.token);
if (result.success) {
bot.sendMessage(chatId,
'✅ Аутентификация успешна!\n\n' +
'Ваш Telegram аккаунт связан с DApp for Business.\n' +
'Теперь вы можете использовать бота для общения с системой.'
);
} else {
bot.sendMessage(chatId,
'❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка')
);
}
// Удаляем данные верификации
verificationCodes.delete(chatId.toString());
} catch (error) {
console.error('Error completing authentication:', error);
bot.sendMessage(chatId, '❌ Произошла ошибка при завершении аутентификации.');
}
} else if (verificationData) {
// Есть ожидающая верификация, но код неверный
bot.sendMessage(chatId, '❌ Неверный код. Пожалуйста, попробуйте еще раз.');
} else {
// Нет ожидающей верификации
bot.sendMessage(chatId, 'Я могу помочь с аутентификацией. Используйте кнопку на сайте для начала процесса.');
}
});
// Добавить обработку прямых команд аутентификации
bot.onText(/\/auth (.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const authToken = match[1].trim();
console.log(`Получена прямая команда авторизации с токеном: ${authToken}`);
try {
// Связываем Telegram ID с аккаунтом по токену
const result = await linkTelegramAccount(chatId.toString(), authToken);
console.log(`Результат связывания: ${JSON.stringify(result)}`);
if (result.success) {
bot.sendMessage(chatId,
'✅ Аутентификация успешна!\n\n' +
'Ваш Telegram аккаунт связан с DApp for Business.\n' +
'Теперь вы можете использовать бота для общения с системой.'
);
} else {
bot.sendMessage(chatId,
'❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка')
);
}
} catch (error) {
console.error('Error linking telegram account:', error);
bot.sendMessage(chatId, '❌ Произошла ошибка при связывании аккаунта.');
}
});
// Обработка ошибок
bot.on('polling_error', (error) => {
logger.error(`[polling_error] ${JSON.stringify(error)}`);
});
console.log('Telegram bot handlers registered');
}
/**
* Связывание Telegram ID с аккаунтом пользователя
* @param {string} telegramId - ID пользователя в Telegram
* @param {string} authToken - Токен авторизации
* @returns {Promise<Object>} - Результат операции
*/
async function linkTelegramAccount(telegramId, authToken) {
try {
console.log(`Попытка связать Telegram ID ${telegramId} с токеном ${authToken}`);
// Здесь должен быть код для связывания через API или напрямую с БД
const { pool } = require('../db');
// Проверяем токен авторизации
const tokenResult = await pool.query(
'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1',
[authToken]
);
console.log(`Результат запроса токена: ${JSON.stringify(tokenResult.rows)}`);
if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) {
console.log('Токен не найден или истек');
return { success: false, error: 'Недействительный или истекший токен' };
}
const userId = tokenResult.rows[0].user_id;
console.log(`Найден пользователь с ID: ${userId}`);
// Добавляем идентификатор Telegram для пользователя
await pool.query(
'INSERT INTO user_identities (user_id, identity_type, identity_value, verified, created_at) ' +
'VALUES ($1, $2, $3, true, NOW()) ' +
'ON CONFLICT (identity_type, identity_value) ' +
'DO UPDATE SET user_id = $1, verified = true',
[userId, 'telegram', telegramId]
);
// Отмечаем токен как использованный
await pool.query(
'UPDATE telegram_auth_tokens SET used = true WHERE token = $1',
[authToken]
);
return { success: true };
} catch (error) {
console.error('Error in linkTelegramAccount:', error);
return { success: false, error: 'Внутренняя ошибка сервера' };
}
}
// Экспортируем функции
module.exports = {
initTelegramBot
initTelegramBot,
verifyCode,
sendVerificationCode
};