#include "commandprocessor.h" #include "neuraltemplatemanager.h" #include #include #include #include #include #include struct Replacement { int start; int length; QString text; }; CommandProcessor::CommandProcessor(QObject *parent) : QObject(parent) { } void CommandProcessor::setContext(const Context& context) { m_context = context; } QString CommandProcessor::generateResponse(QString userIndex, const QString &command, const QString &message) { // Сначала пробуем найти пользователя по displayName QString username = ""; if (m_context.userManager) { User* user = m_context.userManager->findUserById(userIndex); if (user) { username = user->displayName; } } if (username.isEmpty()) { // Если не нашли в UserManager, используем переданное имя username = userIndex; } Command cmd = findCommand(command); if (cmd.command.isEmpty()) { return ""; } QString fullCommand = command + (message.isEmpty() ? "" : " " + message); QString result = processCommand(username, fullCommand, cmd.response); return result; } void CommandProcessor::addCommand(const QString &command, const QString &response) { m_commands.append({command, response}); } void CommandProcessor::addCommands(const QVector &commands) { m_commands.append(commands); } void CommandProcessor::clearCommands() { m_commands.clear(); } CommandProcessor::Command CommandProcessor::findCommand(const QString &commandName) const { for (const Command &cmd : m_commands) { if (cmd.command.compare(commandName, Qt::CaseInsensitive) == 0) { return cmd; } } return Command(); } QString CommandProcessor::getUsernameByIndex(QString userIndex) const { if (!m_context.userManager) { return QString(); } const User* user = m_context.userManager->getUserByIndex(userIndex); return user ? user->displayName : QString(); } QString CommandProcessor::processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse) { QString response = rawResponse; QString parameters = extractParameters(fullCommand); response = parseStatic(response, sender, parameters); // Рекурсивная обработка случайных групп (до 5 уровней вложенности) for (int i = 0; i < 5; i++) { QString before = response; response = parseRandomGroups(response); // Если после обработки строка не изменилась, значит групп больше нет if (before == response) { break; } } response = parseNeuralTemplates(response, sender, parameters); response = parseRandomNumbers(response); // Затем обрабатываем случайные числа ВНУТРИ них response = parseSounds(response); response = parseTextFiles(response); response = parseBan(response, sender); response = parseAPI(response, sender); if (response.contains("[AI]", Qt::CaseInsensitive)) { response = parseAI(response, parameters); } return response; } QString CommandProcessor::extractParameters(const QString &fullCommand) { int spacePos = fullCommand.indexOf(' '); if (spacePos != -1) { return fullCommand.mid(spacePos + 1).trimmed(); } return QString(); } QString CommandProcessor::parseStatic(const QString &response, const QString &sender, const QString ¶meters) { QString res = response; res = res.replace("[USERNAME]", "@" + sender); res = res.replace("[TO]", parameters); if (res.contains("[RANDOMUSER]")) { QString randomUserName; if (parameters.contains('@')) { randomUserName = parameters; } else if (m_context.userManager) { const User* randomUser = m_context.userManager->getRandomUser(); if (randomUser) { randomUserName = "@" + randomUser->displayName; } else { randomUserName = "@viewer"; } } else { randomUserName = "@viewer"; } res = res.replace("[RANDOMUSER]", randomUserName); } return res; } QString CommandProcessor::parseRandomNumbers(const QString &response) { QString result = response; QRegularExpression regex("\\[\\[([^\\]]+)\\]\\]"); QRegularExpressionMatchIterator matches = regex.globalMatch(response); while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString rangeName = match.captured(1); if (m_context.randomManager) { int randomNumber = m_context.randomManager->getRandomValue(rangeName); result.replace("[[" + rangeName + "]]", QString::number(randomNumber)); } else { int fallbackNumber = QRandomGenerator::global()->bounded(1, 101); result.replace("[[" + rangeName + "]]", QString::number(fallbackNumber)); } } return result; } QString CommandProcessor::parseSounds(const QString &response) { QString result = response; QRegularExpression regex("\\|\\|([^\\|]+)\\|\\|"); QRegularExpressionMatchIterator matches = regex.globalMatch(response); while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString soundCommand = match.captured(1); QString fn = m_context.mediaFileManager->getFilePathByName(soundCommand); if (m_context.soundManager) { m_context.soundManager->loadSound(SoundManager::SoundChannel::Channel1, fn); m_context.soundManager->playSound(SoundManager::SoundChannel::Channel1); } result.replace("||" + soundCommand + "||", ""); } return result; } QString CommandProcessor::parseTextFiles(const QString &response) { QString result = response; QRegularExpression regex("\\|\\(([^\\)]+)\\|\\("); QRegularExpressionMatchIterator matches = regex.globalMatch(response); while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString fileName = match.captured(1); QString content = getTextFileContent(fileName); if (!content.isEmpty()) { result.replace("|(" + fileName + "|(", content); } } return result; } QString CommandProcessor::parseRandomGroups(const QString &response) { QString result = response; QRegularExpression regex("\\{\\{([^\\}]+)\\}\\}"); QRegularExpressionMatchIterator matches = regex.globalMatch(response); while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString groupName = match.captured(1); QString randomResponse = getRandomResponseFromGroup(groupName); if (!randomResponse.isEmpty()) { result.replace("{{" + groupName + "}}", randomResponse); } } return result; } QString CommandProcessor::parseBan(const QString &response, const QString &sender) { QString result = response; QRegularExpression regex("\\[BAN(\\d*)\\]"); QRegularExpressionMatchIterator matches = regex.globalMatch(response); while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString banTimeStr = match.captured(1); int banSeconds = banTimeStr.isEmpty() ? 0 : banTimeStr.toInt(); if (m_context.twitchAPI) { if (m_context.userManager) { User* user = m_context.userManager->findUser(sender); if (user && !user->id.isEmpty()) { if (banSeconds > 0) { m_context.twitchAPI->banUserTime(user->id, banSeconds); } } } } result.replace("[BAN" + banTimeStr + "]", ""); } return result; } QString CommandProcessor::parseAPI(const QString &response, const QString &sender) { QString result = response; if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) { if (m_context.userManager && m_context.twitchAPI) { User* user = m_context.userManager->findUser(sender); if (user) { if (user->id.isEmpty() && !user->login.isEmpty()) { User fullUser = m_context.twitchAPI->getUserByLogin(user->login); user->id = fullUser.id; } if (!user->id.isEmpty()) { QDate followDate = m_context.twitchAPI->getFollow(user->id); if (followDate.isValid()) { QString follow = getDateDifferenceString(followDate); result.replace("[FOLLOW]", follow, Qt::CaseInsensitive); } } } } if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) { result.replace("[FOLLOW]", "неизвестно", Qt::CaseInsensitive); } } if (result.contains("[AGE]", Qt::CaseInsensitive)) { if (m_context.userManager && m_context.twitchAPI) { User* user = m_context.userManager->findUser(sender); if (user && !user->login.isEmpty()) { User fullUser = m_context.twitchAPI->getUserByLogin(user->login); if (fullUser.createdAt.isValid()) { QString age = getDateDifferenceString(fullUser.createdAt); result.replace("[AGE]", age, Qt::CaseInsensitive); } } } if (result.contains("[AGE]", Qt::CaseInsensitive)) { result.replace("[AGE]", "неизвестно", Qt::CaseInsensitive); } } if (result.contains("[STAT]", Qt::CaseInsensitive)) { if (m_context.twitchAPI && !m_context.channel.isEmpty()) { int avgViewers = 0, maxViewers = 0, hoursWatched = 0, followers = 0, followersTotal = 0; m_context.twitchAPI->getTTWStat(m_context.channel, avgViewers, maxViewers, hoursWatched, followers, followersTotal); QString stat = QString("Средний онлайн: %1; Максимальный онлайн: %2; " "Часов просмотра: %3; Подписчиков за месяц: %4; " "Всего подписчиков: %5") .arg(avgViewers).arg(maxViewers).arg(hoursWatched) .arg(followers).arg(followersTotal); result.replace("[STAT]", stat, Qt::CaseInsensitive); } else { result.replace("[STAT]", "Статистика недоступна", Qt::CaseInsensitive); } } return result; } QString CommandProcessor::parseAI(const QString &response, const QString &question) { QString res = response; if (!m_context.neuralManager) { return res.replace("[AI]", "Нейросеть недоступна", Qt::CaseInsensitive); } if (question.isEmpty()) { return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", Qt::CaseInsensitive); } QString aiResponse; bool responseReceived = false; bool errorOccurred = false; QString errorMessage; QEventLoop eventLoop; auto conn1 = connect(m_context.neuralManager, &NeuralNetworkManager::responseReceived, [&](const QString &response1) { aiResponse = response1; responseReceived = true; eventLoop.quit(); }); auto conn2 = connect(m_context.neuralManager, &NeuralNetworkManager::errorOccurred, [&](const QString &error) { errorMessage = error; errorOccurred = true; eventLoop.quit(); }); m_context.neuralManager->sendMessage(question); QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit); eventLoop.exec(); disconnect(conn1); disconnect(conn2); if (errorOccurred) { return res.replace("[AI]", QString("Ошибка нейросети: %1").arg(errorMessage), Qt::CaseInsensitive); } if (!responseReceived) { return res.replace("[AI]", "Таймаут при ожидании ответа от нейросети", Qt::CaseInsensitive); } return res.replace("[AI]", aiResponse, Qt::CaseInsensitive); } QString CommandProcessor::getRandomResponseFromGroup(const QString &groupName) { if (m_context.randomResponses) { return m_context.randomResponses->getResponse(groupName); } return QString("Ответ из группы: " + groupName); } QString CommandProcessor::getTextFileContent(const QString &fileName) { QFile file(fileName); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); stream.setCodec("UTF-8"); QString content = stream.readAll(); file.close(); return content; } return QString(); } QString CommandProcessor::getDateDifferenceString(const QDate &inputDate) { if (!inputDate.isValid() || inputDate.year() < 2000) { return "неизвестно"; } QDate currentDate = QDate::currentDate(); if (currentDate < inputDate) { return "в будущем"; } int years = inputDate.daysTo(currentDate) / 365; QDate tempDate = inputDate.addYears(years); int months = 0; while (tempDate.addMonths(1) <= currentDate) { months++; tempDate = tempDate.addMonths(1); } int days = tempDate.daysTo(currentDate); if (days >= 30) { months += days / 30; days = days % 30; } QString result; if (years > 0) { result += QString("%1 %2 ").arg(years).arg(getPeriodEnding(years, 0)); } if (months > 0) { result += QString("%1 %2 ").arg(months).arg(getPeriodEnding(months, 1)); } if (days > 0 || result.isEmpty()) { result += QString("%1 %2").arg(days).arg(getPeriodEnding(days, 2)); } return result.trimmed(); } QString CommandProcessor::getPeriodEnding(int n, int r) { static const QVector> endings = { {"год", "года", "лет"}, {"месяц", "месяца", "месяцев"}, {"день", "дня", "дней"} }; if (r < 0 || r >= endings.size()) return ""; if (n % 10 == 1 && n % 100 != 11) { return endings[r][0]; } else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) { return endings[r][1]; } else { return endings[r][2]; } } QString CommandProcessor::parseNeuralTemplates(const QString &response, const QString &sender, const QString ¶meters) { QString result = response; // Исправленное регулярное выражение QRegularExpression regex("<\\|([^<]+)<\\|"); QRegularExpressionMatchIterator matches = regex.globalMatch(response); QList replacements; int matchCount = 0; while (matches.hasNext()) { matchCount++; QRegularExpressionMatch match = matches.next(); QString templateName = match.captured(1).trimmed(); if (m_context.neuralTemplateManager) { QString templateText = m_context.neuralTemplateManager->getTemplateText(templateName); if (!templateText.isEmpty()) { // Заменяем плейсхолдеры в шаблоне QString processedTemplate = templateText; processedTemplate.replace("[TO]", parameters); processedTemplate.replace("[USERNAME]", sender); // Получаем ответ от нейросети QString aiResponse; if (m_context.neuralManager) { // Создаем event loop для асинхронного ожидания QEventLoop eventLoop; bool responseReceived = false; bool errorOccurred = false; QString errorMessage; // Подключаем сигналы от нейросети auto conn1 = connect(m_context.neuralManager, &NeuralNetworkManager::responseReceived, [&](const QString &responseText) { aiResponse = responseText; responseReceived = true; eventLoop.quit(); }); auto conn2 = connect(m_context.neuralManager, &NeuralNetworkManager::errorOccurred, [&](const QString &error) { errorMessage = error; errorOccurred = true; eventLoop.quit(); }); // Отправляем запрос к нейросети m_context.neuralManager->sendMessage(processedTemplate); // Таймаут 30 секунд QTimer::singleShot(30000, &eventLoop, &QEventLoop::quit); eventLoop.exec(); disconnect(conn1); disconnect(conn2); if (errorOccurred) { aiResponse = QString("Ошибка нейросети: %1").arg(errorMessage); } else if (!responseReceived) { aiResponse = "Таймаут при ожидании ответа от нейросети"; } } else { aiResponse = "Нейросеть недоступна"; } // Заменяем шаблон на ответ от нейросети Replacement repl; repl.start = match.capturedStart(); repl.length = match.capturedLength(); repl.text = aiResponse; replacements.append(repl); } else { // Если шаблон не найден, оставляем как есть или заменяем на заглушку Replacement repl; repl.start = match.capturedStart(); repl.length = match.capturedLength(); repl.text = "[Шаблон не найден]"; replacements.append(repl); } } else { // Если менеджер не доступен, заменяем на заглушку Replacement repl; repl.start = match.capturedStart(); repl.length = match.capturedLength(); repl.text = "[Нейросеть недоступна]"; replacements.append(repl); } } // Выполняем замены с конца к началу std::sort(replacements.begin(), replacements.end(), [](const Replacement &a, const Replacement &b) { return a.start > b.start; }); for (const auto &replacement : replacements) { result.replace(replacement.start, replacement.length, replacement.text); } return result; } void CommandProcessor::editCommand(const QString &oldCommand,const QString &newCommand,const QString &response) { Command oldCom = findCommand(oldCommand); if (oldCom.command.isEmpty()) return; for (int i = 0; i < m_commands.size(); ++i) { if (m_commands[i].command.compare(oldCommand, Qt::CaseInsensitive) == 0) { // Обновляем команду m_commands[i].command = newCommand; m_commands[i].response = response; return; } } } void CommandProcessor::deleteCommand(const QString &commandName) { Command oldCom = findCommand(commandName); if (oldCom.command.isEmpty()) return; for (int i = 0; i < m_commands.size(); ++i) { if (m_commands[i].command.compare(commandName, Qt::CaseInsensitive) == 0) { m_commands.remove(i); return; } } }