mirror of https://notabug.org/acetone/ircabot.git
344 lines
13 KiB
C++
344 lines
13 KiB
C++
#include "applicationdata.h"
|
||
#include "global.h"
|
||
#include "version.h"
|
||
|
||
#include <QFile>
|
||
#include <QDir>
|
||
#include <QDebug>
|
||
#include <QRegularExpression>
|
||
|
||
void ApplicationData::createConfigExample(const QString &pathToConfig)
|
||
{
|
||
QFile file(pathToConfig);
|
||
if (not file.open(QIODevice::WriteOnly)) {
|
||
throw std::runtime_error("ApplicationData::createConfigExample(): Output file openning is failed");
|
||
}
|
||
|
||
const QString configExample {
|
||
"[GLOBAL]\n"
|
||
"data_path = /srv/ircabot/data\n"
|
||
"# Uncomment \"AJAXIsDisabled\" for disable real time reading mode.\n"
|
||
"# This mode generates many requests from the client side\n"
|
||
"# and in exceptional cases may be undesirable. All JavaScript code will be removed.\n"
|
||
"#AJAXIsDisabled = true\n\n"
|
||
|
||
"# Global options for all servers.\n"
|
||
"nick = default_nickname\n"
|
||
"user = default_ident\n"
|
||
"real_name = default_real_name\n"
|
||
"# If password is empty logging in without password.\n"
|
||
"password = password_for_NickServ\n\n"
|
||
"# This triggers available in all chats.\n"
|
||
"# Request and answer must be splitted by \":::\". Triggers splitting by \"<!>\".\n"
|
||
"# You can use \"%CHANNEL_FOR_URL%\" to automatically substitute the address for the chat,\n"
|
||
"# from which the link to the web interface is requested.\n"
|
||
"triggers = version ::: " + IRCABOT_VERSION + " <!> webui ::: http://example.com/%CHANNEL_FOR_URL%\n\n"
|
||
|
||
"# Web interface options\n"
|
||
"# service_* parameters used for the appearance of the home button\n"
|
||
"service_name = IRCaBot\n"
|
||
"service_emoji = 📁\n"
|
||
"bind_to_address = 127.0.0.1\n"
|
||
"bind_to_port = 8080\n\n"
|
||
|
||
"[Displayed server name]\n"
|
||
"address = 127.0.0.1\n"
|
||
"port = 6667\n"
|
||
"# Channels splitting with comma.\n"
|
||
"channels = #general,#acetonevideo\n"
|
||
"# This triggers available in current server only.\n"
|
||
"triggers = hi ::: hello <!> developer ::: acetone\n\n"
|
||
"# Optional parameters if global is defined:\n"
|
||
"#nick = unique_nickname_for_this_server\n"
|
||
"#user = unique_ident_for_this_server\n"
|
||
"#real_name = unique_real_name_for_this_server\n"
|
||
"#password = password_for_this_user\n"
|
||
};
|
||
|
||
file.write(configExample.toUtf8());
|
||
file.close();
|
||
}
|
||
|
||
ApplicationData::ApplicationData(const QString& pathToConfig) :
|
||
m_webInterfacePort(0),
|
||
m_ajaxIsDisabled(false)
|
||
{
|
||
if (not QFile::exists(pathToConfig)) {
|
||
throw std::runtime_error("Configuration file not exist (" + pathToConfig.toStdString() + ")");
|
||
}
|
||
|
||
m_file = pathToConfig;
|
||
readConfig();
|
||
}
|
||
|
||
std::pair<QString, quint16> ApplicationData::getWebInterfaceAddress()
|
||
{
|
||
if (m_webInterfacePort == 0) {
|
||
throw std::runtime_error("Web interface port is undefined");
|
||
}
|
||
if (m_webInterfaceAddress.isEmpty()) {
|
||
throw std::runtime_error("Web interface address is undefined");
|
||
}
|
||
|
||
return {m_webInterfaceAddress, m_webInterfacePort};
|
||
}
|
||
|
||
QList<ConnectionData> ApplicationData::getConnections()
|
||
{
|
||
return m_connections;
|
||
}
|
||
|
||
QString ApplicationData::getServiceEmoji()
|
||
{
|
||
return m_serviceEmoji;
|
||
}
|
||
|
||
QString ApplicationData::getServiceName()
|
||
{
|
||
return m_serviceName;
|
||
}
|
||
|
||
QString ApplicationData::getDataFolder()
|
||
{
|
||
return m_dataPath;
|
||
}
|
||
|
||
bool ApplicationData::getAjaxIsDisabled()
|
||
{
|
||
return m_ajaxIsDisabled;
|
||
}
|
||
|
||
void ApplicationData::readConfig()
|
||
{
|
||
QFile file(m_file);
|
||
if (not file.open(QIODevice::ReadOnly)) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): Can't open file for reading");
|
||
}
|
||
|
||
QString line {file.readLine()};
|
||
QString conffile;
|
||
|
||
while (not line.isEmpty()) {
|
||
line.remove('\n');
|
||
line.remove('\r');
|
||
|
||
if (line.startsWith('#') or line.isEmpty()) {
|
||
line = file.readLine();
|
||
continue;
|
||
}
|
||
|
||
conffile += line + '\n';
|
||
line = file.readLine();
|
||
}
|
||
file.close();
|
||
if (conffile.isEmpty()) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): Empty data");
|
||
}
|
||
|
||
//// Парсинг GLOBAL
|
||
QString globalSection {conffile};
|
||
int globalBegin = conffile.indexOf("[GLOBAL]");
|
||
if (globalBegin == -1) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): Wrong config. [GLOBAL] section not exist!");
|
||
}
|
||
int globalEnd = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), globalBegin+1); //IPv6 addresses safe
|
||
if (globalEnd != -1) {
|
||
// Удаление последующей [секции]
|
||
globalSection.remove(globalEnd, conffile.size()-globalEnd);
|
||
}
|
||
globalSection.remove(0, globalBegin);
|
||
|
||
//// Инициализация GLOBAL
|
||
// logPath
|
||
m_dataPath = global::getValue(globalSection, "data_path");
|
||
if (m_dataPath.isEmpty()) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): 'data_path' in [GLOBAL] is undefined");
|
||
}
|
||
QDir logDir;
|
||
if (not QFile::exists(m_dataPath)) {
|
||
if (not logDir.mkpath(m_dataPath)) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): Can't create " + m_dataPath.toStdString());
|
||
}
|
||
}
|
||
logDir.setPath(m_dataPath);
|
||
if (not logDir.mkdir("TeSt__FoLdEr")) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): Log path '" + m_dataPath.toStdString() + "' is unwritable");
|
||
}
|
||
logDir.rmdir("TeSt__FoLdEr");
|
||
|
||
if (not m_dataPath.endsWith(global::slash)) {
|
||
m_dataPath += global::slash;
|
||
}
|
||
|
||
// web interface
|
||
m_webInterfaceAddress = global::getValue(globalSection, "bind_to_address");
|
||
if (m_webInterfaceAddress.isEmpty()) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_address' in [GLOBAL] is undefined");
|
||
}
|
||
|
||
bool success = false;
|
||
m_webInterfacePort = global::getValue(globalSection, "bind_to_port").toUShort(&success);
|
||
if (not success) {
|
||
throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_port' in [GLOBAL] is incorrect");
|
||
}
|
||
|
||
m_serviceName = global::getValue(globalSection, "service_name");
|
||
if (m_serviceName.isEmpty()) m_serviceName = "IRCaBot";
|
||
m_serviceEmoji = global::getValue(globalSection, "service_emoji");
|
||
if (m_serviceEmoji.isEmpty()) m_serviceEmoji = "📁";
|
||
|
||
QMap<QString, QString> globalTriggers;
|
||
QString triggersLine = global::getValue(globalSection, "triggers");
|
||
if (not triggersLine.isEmpty()) {
|
||
QStringList triggersPair = triggersLine.split("<!>");
|
||
for (auto &p: triggersPair) {
|
||
QStringList pair = p.split(":::");
|
||
if (pair.size() != 2) continue;
|
||
QString request = pair.first();
|
||
QString answer = pair.last();
|
||
|
||
if (request.startsWith(' ')) request.remove(QRegularExpression("^\\s*"));
|
||
if (request.endsWith(' ')) request.remove(QRegularExpression("\\s*$"));
|
||
if (answer.startsWith(' ')) answer.remove(QRegularExpression("^\\s*"));
|
||
if (answer.endsWith(' ')) answer.remove(QRegularExpression("\\s*$"));
|
||
if (request.isEmpty() or answer.isEmpty()) continue;
|
||
|
||
globalTriggers[request] = answer;
|
||
qInfo().noquote() << "[GLOBAL] Trigger (" << request << ":::" << answer << ")";
|
||
}
|
||
}
|
||
m_ajaxIsDisabled = global::getValue(globalSection, "AJAXIsDisabled") == "true";
|
||
|
||
// other
|
||
m_nick = global::getValue(globalSection, "nick");
|
||
m_nick.replace(' ', '_');
|
||
m_user = global::getValue(globalSection, "user");
|
||
m_realName = global::getValue(globalSection, "real_name");
|
||
m_password = global::getValue(globalSection, "password");
|
||
|
||
conffile.remove(globalSection);
|
||
|
||
//// Парсинг подключений
|
||
// Цикл до тех пор, пока остались заголовки секций
|
||
while (conffile.contains(QRegularExpression("\\[[^\\n]*\\]"))) {
|
||
QString currentSection {conffile};
|
||
int begin = conffile.indexOf(QRegularExpression("\\[[^\\n]*\\]"));
|
||
int end = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), begin+1);
|
||
if (end != -1) {
|
||
currentSection.remove(end, currentSection.size() - end);
|
||
}
|
||
currentSection.remove(0, begin);
|
||
conffile.remove(currentSection);
|
||
|
||
//// Инициализация информации о подключениях
|
||
ConnectionData newConnection;
|
||
newConnection.displayName = currentSection.mid(1, currentSection.indexOf(']')-1);
|
||
|
||
newConnection.address = global::getValue(currentSection, "address");
|
||
if (newConnection.address.isEmpty()) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'address')";
|
||
continue;
|
||
}
|
||
|
||
success = false;
|
||
newConnection.port = global::getValue(currentSection, "port").toUShort(&success);
|
||
if (not success) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (wrong 'port')";
|
||
continue;
|
||
}
|
||
|
||
QString channelsString = global::getValue(currentSection, "channels");
|
||
if (channelsString.isEmpty()) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'channels')";
|
||
continue;
|
||
}
|
||
newConnection.channels = channelsString.split(',');
|
||
newConnection.channels.removeAll("");
|
||
for (auto &ch: newConnection.channels) {
|
||
ch.remove(' ');
|
||
if (not ch.startsWith('#')) {
|
||
ch = '#' + ch;
|
||
}
|
||
}
|
||
|
||
newConnection.user = global::getValue(currentSection, "user");
|
||
if (newConnection.user.isEmpty()) {
|
||
if (m_user.isEmpty()) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'user' and global too)";
|
||
continue;
|
||
}
|
||
else {
|
||
newConnection.user = m_user;
|
||
}
|
||
}
|
||
|
||
newConnection.nick = global::getValue(currentSection, "nick");
|
||
if (newConnection.nick.isEmpty()) {
|
||
if (m_nick.isEmpty()) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'nick' and global too)";
|
||
continue;
|
||
}
|
||
else {
|
||
newConnection.nick = m_nick;
|
||
}
|
||
}
|
||
else {
|
||
newConnection.nick.replace(' ', '_');
|
||
}
|
||
|
||
newConnection.realName = global::getValue(currentSection, "real_name");
|
||
if (newConnection.realName.isEmpty()) {
|
||
if (m_realName.isEmpty()) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'real_name' and global too)";
|
||
continue;
|
||
}
|
||
else {
|
||
newConnection.realName = m_realName;
|
||
}
|
||
}
|
||
|
||
newConnection.password = global::getValue(currentSection, "password");
|
||
if (newConnection.password.isEmpty() and not m_password.isEmpty()) {
|
||
newConnection.password = m_password;
|
||
}
|
||
|
||
QString path {global::toLowerAndNoSpaces(newConnection.displayName)};
|
||
newConnection.logFolderPath = m_dataPath + path;
|
||
|
||
QString triggersLine = global::getValue(currentSection, "triggers");
|
||
if (not triggersLine.isEmpty()) {
|
||
QStringList triggersPair = triggersLine.split("<!>");
|
||
for (auto &p: triggersPair) {
|
||
QStringList pair = p.split(":::");
|
||
if (pair.size() != 2) continue;
|
||
QString request = pair.first();
|
||
QString answer = pair.last();
|
||
|
||
if (request.startsWith(' ')) request.remove(QRegularExpression("^\\s*"));
|
||
if (request.endsWith(' ')) request.remove(QRegularExpression("\\s*$"));
|
||
if (answer.startsWith(' ')) answer.remove(QRegularExpression("^\\s*"));
|
||
if (answer.endsWith(' ')) answer.remove(QRegularExpression("\\s*$"));
|
||
if (request.isEmpty() or answer.isEmpty()) continue;
|
||
|
||
newConnection.triggers[request] = answer;
|
||
qInfo().noquote() << "["+newConnection.displayName+"] Trigger (" << request << ":::" << answer << ")";
|
||
}
|
||
}
|
||
for (auto &glob: globalTriggers) {
|
||
bool detected = false;
|
||
for (auto local: newConnection.triggers) {
|
||
if (newConnection.triggers.key(local) == globalTriggers.key(glob)) {
|
||
qInfo().noquote() << "[" + newConnection.displayName + "]" <<
|
||
"Trigger '" + globalTriggers.key(glob) + "' ignored (GLOBAL)";
|
||
detected = true;
|
||
}
|
||
}
|
||
if (not detected) {
|
||
newConnection.triggers[globalTriggers.key(glob)] = glob;
|
||
}
|
||
}
|
||
|
||
m_connections.push_back(newConnection);
|
||
}
|
||
}
|