39f0c447c1
исправил поиск пользователя убрал лишние qDebug
590 lines
20 KiB
C++
590 lines
20 KiB
C++
#include "commandprocessor.h"
|
|
#include "neuraltemplatemanager.h"
|
|
#include <QRegularExpression>
|
|
#include <QRandomGenerator>
|
|
#include <QFile>
|
|
#include <QTextStream>
|
|
#include <QEventLoop>
|
|
#include <QTimer>
|
|
|
|
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<Command> &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<QVector<QString>> 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<Replacement> 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;
|
|
}
|
|
}
|
|
}
|