applicationdata.cpp 12 KB

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