Files
TTW_Bot/commandprocessor.cpp
T
PTyTb 39f0c447c1 добавил обновление списков в реальном времени
исправил поиск пользователя
убрал лишние qDebug
2026-02-09 22:24:24 +03:00

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 &parameters)
{
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 &parameters)
{
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;
}
}
}