applicationdata.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #include "applicationdata.h"
  2. #include "global.h"
  3. #include "version.h"
  4. #include <QFile>
  5. #include <QDir>
  6. #include <QDebug>
  7. #include <QRegularExpression>
  8. void ApplicationData::createConfigExample(const QString &pathToConfig)
  9. {
  10. QFile file(pathToConfig);
  11. if (not file.open(QIODevice::WriteOnly)) {
  12. throw std::runtime_error("ApplicationData::createConfigExample(): Output file openning is failed");
  13. }
  14. const QString configExample {
  15. "[GLOBAL]\n"
  16. "log_path = /srv/ircabot/chatlog\n"
  17. "# Uncomment \"AJAXIsDisabled\" for disable real time reading mode.\n"
  18. "# This mode generates many requests from the client side\n"
  19. "# and in exceptional cases may be undesirable. All JavaScript code will be removed.\n"
  20. "#AJAXIsDisabled = true\n\n"
  21. "# Global options for all servers.\n"
  22. "nick = default_nickname\n"
  23. "user = default_ident\n"
  24. "real_name = default_real_name\n"
  25. "# If password is empty logging in without password.\n"
  26. "password = password_for_NickServ\n\n"
  27. "# This triggers available in all chats.\n"
  28. "# 'request' and 'answer' must be splitted by ':::'\n"
  29. "# triggers splitting by '<!>'\n"
  30. "triggers = request = version ::: answer = " + IRCABOT_VERSION + " <!> request=hello:::answer=hi\n\n"
  31. "# Web interface options\n"
  32. "bind_to_address = 127.0.0.1\n"
  33. "bind_to_port = 8080\n\n"
  34. "[Displayed server name]\n"
  35. "address = 127.0.0.1\n"
  36. "port = 6667\n"
  37. "# Channels splitting with comma.\n"
  38. "# Default channel for the web interface marked with an '@' at the end (globally only one).\n"
  39. "channels = #general@,#acetonevideo\n"
  40. "# This triggers available in current server only.\n"
  41. "# 'request' and 'answer' must be splitted by ':::'\n"
  42. "# triggers splitting by '<!>'\n"
  43. "triggers = request = hi ::: answer = hello <!> request=developer:::answer=acetone\n\n"
  44. "# Optional parameters if global is defined:\n"
  45. "#nick = unique_nickname_for_this_server\n"
  46. "#user = unique_ident_for_this_server\n"
  47. "#real_name = unique_real_name_for_this_server\n"
  48. "#password = password_for_this_user\n"
  49. };
  50. file.write(configExample.toUtf8());
  51. file.close();
  52. }
  53. ApplicationData::ApplicationData(const QString& pathToConfig) :
  54. m_webInterfacePort(0),
  55. m_ajaxIsDisabled(false)
  56. {
  57. if (not QFile::exists(pathToConfig)) {
  58. throw std::runtime_error("Configuration file not exist (" + pathToConfig.toStdString() + ")");
  59. }
  60. m_file = pathToConfig;
  61. readConfig();
  62. }
  63. std::pair<QString, quint16> ApplicationData::getWebInterfaceAddress()
  64. {
  65. if (m_webInterfacePort == 0) {
  66. throw std::runtime_error("Web interface port is undefined");
  67. }
  68. if (m_webInterfaceAddress.isEmpty()) {
  69. throw std::runtime_error("Web interface address is undefined");
  70. }
  71. return {m_webInterfaceAddress, m_webInterfacePort};
  72. }
  73. QList<ConnectionData> ApplicationData::getConnections()
  74. {
  75. return m_connections;
  76. }
  77. QString ApplicationData::getMainChannel()
  78. {
  79. return m_mainChannel;
  80. }
  81. QString ApplicationData::getLogFolder()
  82. {
  83. return m_logPath;
  84. }
  85. bool ApplicationData::getAjaxIsDisabled()
  86. {
  87. return m_ajaxIsDisabled;
  88. }
  89. void ApplicationData::readConfig()
  90. {
  91. QFile file(m_file);
  92. if (not file.open(QIODevice::ReadOnly)) {
  93. throw std::runtime_error("ApplicationData::readConfig(): Can't open file for reading");
  94. }
  95. QString line {file.readLine()};
  96. QString conffile;
  97. while (not line.isEmpty()) {
  98. line.remove('\n');
  99. line.remove('\r');
  100. if (line.startsWith('#') or line.isEmpty()) {
  101. line = file.readLine();
  102. continue;
  103. }
  104. conffile += line + '\n';
  105. line = file.readLine();
  106. }
  107. file.close();
  108. if (conffile.isEmpty()) {
  109. throw std::runtime_error("ApplicationData::readConfig(): Empty data");
  110. }
  111. //// Парсинг GLOBAL
  112. QString globalSection {conffile};
  113. int globalBegin = conffile.indexOf("[GLOBAL]");
  114. if (globalBegin == -1) {
  115. throw std::runtime_error("ApplicationData::readConfig(): Wrong config. [GLOBAL] section not exist!");
  116. }
  117. int globalEnd = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), globalBegin+1); //IPv6 addresses safe
  118. if (globalEnd != -1) {
  119. // Удаление последующей [секции]
  120. globalSection.remove(globalEnd, conffile.size()-globalEnd);
  121. }
  122. globalSection.remove(0, globalBegin);
  123. //// Инициализация GLOBAL
  124. // logPath
  125. m_logPath = global::getValue(globalSection, "log_path");
  126. if (m_logPath.isEmpty()) {
  127. throw std::runtime_error("ApplicationData::readConfig(): 'log_path' in [GLOBAL] is undefined");
  128. }
  129. QDir logDir;
  130. if (not QFile::exists(m_logPath)) {
  131. if (not logDir.mkpath(m_logPath)) {
  132. throw std::runtime_error("ApplicationData::readConfig(): Can't create " + m_logPath.toStdString());
  133. }
  134. }
  135. logDir.setPath(m_logPath);
  136. if (not logDir.mkdir("TeSt__FoLdEr")) {
  137. throw std::runtime_error("ApplicationData::readConfig(): Log path '" + m_logPath.toStdString() + "' is unwritable");
  138. }
  139. logDir.rmdir("TeSt__FoLdEr");
  140. if (not m_logPath.endsWith(global::slash)) {
  141. m_logPath += global::slash;
  142. }
  143. // web interface
  144. m_webInterfaceAddress = global::getValue(globalSection, "bind_to_address");
  145. if (m_webInterfaceAddress.isEmpty()) {
  146. throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_address' in [GLOBAL] is undefined");
  147. }
  148. bool success = false;
  149. m_webInterfacePort = global::getValue(globalSection, "bind_to_port").toUShort(&success);
  150. if (not success) {
  151. throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_port' in [GLOBAL] is incorrect");
  152. }
  153. QMap<QString, QString> globalTriggers;
  154. QString triggersLine = global::getValue(globalSection, "triggers");
  155. if (not triggersLine.isEmpty()) {
  156. QStringList triggersPair = triggersLine.split("<!>");
  157. for (auto &pair: triggersPair) {
  158. QString request = global::getValue(pair, "request", global::Type::eForTriggers);
  159. if (request.isEmpty()) continue;
  160. QString answer = global::getValue(pair, "answer", global::Type::eForTriggers);
  161. if (answer.isEmpty()) continue;
  162. globalTriggers[request] = answer;
  163. qInfo().noquote() << "[GLOBAL] Trigger (" << request << ":::" << answer << ")";
  164. }
  165. }
  166. m_ajaxIsDisabled = global::getValue(globalSection, "AJAXIsDisabled") == "true";
  167. // other
  168. m_nick = global::getValue(globalSection, "nick");
  169. m_nick.replace(' ', '_');
  170. m_user = global::getValue(globalSection, "user");
  171. m_realName = global::getValue(globalSection, "real_name");
  172. m_password = global::getValue(globalSection, "password");
  173. conffile.remove(globalSection);
  174. //// Парсинг подключений
  175. // Цикл до тех пор, пока остались заголовки секций
  176. while (conffile.contains(QRegularExpression("\\[[^\\n]*\\]"))) {
  177. QString currentSection {conffile};
  178. int begin = conffile.indexOf(QRegularExpression("\\[[^\\n]*\\]"));
  179. int end = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), begin+1);
  180. if (end != -1) {
  181. currentSection.remove(end, currentSection.size() - end);
  182. }
  183. currentSection.remove(0, begin);
  184. conffile.remove(currentSection);
  185. //// Инициализация информации о подключениях
  186. ConnectionData newConnection;
  187. newConnection.displayName = currentSection.toStdString().substr(1, currentSection.indexOf(']')-1).c_str();
  188. newConnection.address = global::getValue(currentSection, "address");
  189. if (newConnection.address.isEmpty()) {
  190. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'address')";
  191. continue;
  192. }
  193. success = false;
  194. newConnection.port = global::getValue(currentSection, "port").toUShort(&success);
  195. if (not success) {
  196. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (wrong 'port')";
  197. continue;
  198. }
  199. QString channelsString = global::getValue(currentSection, "channels");
  200. if (channelsString.isEmpty()) {
  201. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'channels')";
  202. continue;
  203. }
  204. newConnection.channels = channelsString.split(',');
  205. newConnection.channels.removeAll("");
  206. for (auto &ch: newConnection.channels) {
  207. ch.remove(' ');
  208. if (not ch.startsWith('#')) {
  209. ch = '#' + ch;
  210. }
  211. if (ch.endsWith('@')) {
  212. ch.remove('@');
  213. m_mainChannel = global::toLowerAndNoSpaces(newConnection.displayName) + "/";
  214. m_mainChannel += global::toLowerAndNoSpaces(ch);
  215. }
  216. }
  217. newConnection.user = global::getValue(currentSection, "user");
  218. if (newConnection.user.isEmpty()) {
  219. if (m_user.isEmpty()) {
  220. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'user' and global too)";
  221. continue;
  222. }
  223. else {
  224. newConnection.user = m_user;
  225. }
  226. }
  227. newConnection.nick = global::getValue(currentSection, "nick");
  228. if (newConnection.nick.isEmpty()) {
  229. if (m_nick.isEmpty()) {
  230. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'nick' and global too)";
  231. continue;
  232. }
  233. else {
  234. newConnection.nick = m_nick;
  235. }
  236. }
  237. else {
  238. newConnection.nick.replace(' ', '_');
  239. }
  240. newConnection.realName = global::getValue(currentSection, "real_name");
  241. if (newConnection.realName.isEmpty()) {
  242. if (m_realName.isEmpty()) {
  243. qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'real_name' and global too)";
  244. continue;
  245. }
  246. else {
  247. newConnection.realName = m_realName;
  248. }
  249. }
  250. newConnection.password = global::getValue(currentSection, "password");
  251. if (newConnection.password.isEmpty() and not m_password.isEmpty()) {
  252. newConnection.password = m_password;
  253. }
  254. QString path {global::toLowerAndNoSpaces(newConnection.displayName)};
  255. newConnection.logFolderPath = m_logPath + path;
  256. QString triggersLine = global::getValue(currentSection, "triggers");
  257. if (not triggersLine.isEmpty()) {
  258. QStringList triggersPair = triggersLine.split("<!>");
  259. for (auto &pair: triggersPair) {
  260. QString request = global::getValue(pair, "request", global::Type::eForTriggers);
  261. if (request.isEmpty()) continue;
  262. QString answer = global::getValue(pair, "answer", global::Type::eForTriggers);
  263. if (answer.isEmpty()) continue;
  264. newConnection.triggers[request] = answer;
  265. qInfo().noquote() << "[" + newConnection.displayName + "] Trigger (" <<
  266. request << ":::" << answer << ")";
  267. }
  268. }
  269. for (auto &glob: globalTriggers) {
  270. bool detected = false;
  271. for (auto local: newConnection.triggers) {
  272. if (newConnection.triggers.key(local) == globalTriggers.key(glob)) {
  273. qInfo().noquote() << "[" + newConnection.displayName + "]" <<
  274. "Trigger '" + globalTriggers.key(glob) + "' ignored (GLOBAL)";
  275. detected = true;
  276. }
  277. }
  278. if (not detected) {
  279. newConnection.triggers[globalTriggers.key(glob)] = glob;
  280. }
  281. }
  282. m_connections.push_back(newConnection);
  283. }
  284. if (m_mainChannel.isEmpty()) {
  285. m_mainChannel = global::toLowerAndNoSpaces(m_connections.first().displayName) + "/";
  286. m_mainChannel += global::toLowerAndNoSpaces(m_connections.first().channels.first());
  287. }
  288. m_mainChannel.remove('#');
  289. qInfo().noquote() << "[GLOBAL] Main channel:" << m_mainChannel;
  290. }