refactor
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
#include "commandprocessor.h"
|
||||
#include <QRegularExpression>
|
||||
#include <QRandomGenerator>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
|
||||
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)
|
||||
{
|
||||
qDebug() << "generateResponse: userIndex =" << userIndex << "command =" << command;
|
||||
|
||||
// Сначала пробуем найти пользователя по displayName
|
||||
QString username = "";
|
||||
if (m_context.userManager) {
|
||||
User* user = m_context.userManager->findUser(userIndex);
|
||||
if (user) {
|
||||
username = user->displayName;
|
||||
qDebug() << "Найден пользователь:" << username;
|
||||
} else {
|
||||
qDebug() << "Пользователь не найден в UserManager по displayName:" << userIndex;
|
||||
|
||||
// Попробуем найти по ID
|
||||
user = m_context.userManager->findUserById(userIndex);
|
||||
if (user) {
|
||||
username = user->displayName;
|
||||
qDebug() << "Найден пользователь по ID:" << username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// Если не нашли в UserManager, используем переданное имя
|
||||
username = userIndex;
|
||||
qDebug() << "Используем переданное имя:" << username;
|
||||
}
|
||||
|
||||
Command cmd = findCommand(command);
|
||||
if (cmd.command.isEmpty()) {
|
||||
qDebug() << "Команда не найдена:" << command;
|
||||
return QString("Команда '%1' не найдена").arg(command);
|
||||
}
|
||||
|
||||
qDebug() << "Найдена команда:" << cmd.command << "ответ:" << cmd.response;
|
||||
|
||||
QString fullCommand = command + (message.isEmpty() ? "" : " " + message);
|
||||
QString result = processCommand(username, fullCommand, cmd.response);
|
||||
|
||||
qDebug() << "Итоговый результат:" << result;
|
||||
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 = 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);
|
||||
|
||||
qDebug() << "parseRandomNumbers: исходная строка:" << response;
|
||||
qDebug() << "Найдено совпадений:" << matches.hasNext();
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString rangeName = match.captured(1);
|
||||
qDebug() << "Найден диапазон:" << rangeName;
|
||||
|
||||
if (m_context.randomManager) {
|
||||
int randomNumber = m_context.randomManager->getRandomValue(rangeName);
|
||||
qDebug() << "Получено случайное число:" << randomNumber << "для диапазона" << rangeName;
|
||||
result.replace("[[" + rangeName + "]]", QString::number(randomNumber));
|
||||
} else {
|
||||
qDebug() << "RandomManager не инициализирован!";
|
||||
int fallbackNumber = QRandomGenerator::global()->bounded(1, 101);
|
||||
result.replace("[[" + rangeName + "]]", QString::number(fallbackNumber));
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Результат после замены:" << result;
|
||||
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);
|
||||
qDebug() << user->displayName;
|
||||
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, NeuralNetworkManager::DeepSeek);
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user