applicationdata.cpp 13 KB

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