main.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #include <chrono>
  2. #include <iostream>
  3. #include <string>
  4. #include <thread> // sleep_for
  5. #include "door.h"
  6. #include "irc.h"
  7. #include "yaml-cpp/yaml.h"
  8. #include <boost/asio.hpp>
  9. // #include <boost/thread.hpp>
  10. YAML::Node config;
  11. std::function<std::ofstream &(void)> get_logger;
  12. bool file_exists(const std::string name) {
  13. std::ifstream f(name.c_str());
  14. return f.good();
  15. }
  16. const int ms_input_delay = 50;
  17. std::string input;
  18. std::string prompt; // mostly for length to erase/restore properly
  19. int max_input = 100;
  20. door::ANSIColor prompt_color{door::COLOR::YELLOW, door::COLOR::BLUE,
  21. door::ATTR::BOLD};
  22. door::ANSIColor input_color{door::COLOR::WHITE}; // , door::COLOR::BLUE};
  23. void erase(door::Door &d, int count) {
  24. d << door::reset;
  25. for (int x = 0; x < count; ++x) {
  26. d << "\x08 \x08";
  27. }
  28. }
  29. void clear_input(door::Door &d) {
  30. if (prompt.empty())
  31. return;
  32. erase(d, input.size());
  33. erase(d, prompt.size());
  34. }
  35. void restore_input(door::Door &d) {
  36. if (prompt.empty())
  37. return;
  38. d << prompt_color << prompt << input_color << input;
  39. }
  40. /*
  41. commands:
  42. /h /help /?
  43. /t /talk /talkto [TARGET]
  44. /msg [TO] [message]
  45. /me [message]
  46. /quit [message, maybe]
  47. /join [TARGET]
  48. /part [TARGET]
  49. future:
  50. /list ?
  51. /version ?
  52. */
  53. bool check_for_input(door::Door &d, ircClient &irc) {
  54. int c;
  55. // return true when we have input and is "valid" // ready
  56. if (prompt.empty()) {
  57. // ok, nothing has been displayed at this time.
  58. if (d.haskey()) {
  59. // something to do.
  60. prompt = "[" + irc.talkto + "] ";
  61. d << prompt_color << prompt << input_color;
  62. c = d.sleep_key(1);
  63. if (c < 0) {
  64. // handle timeout/hangup/out of time
  65. return false;
  66. }
  67. if (c > 0x1000)
  68. return false;
  69. if (isprint(c)) {
  70. d << (char)c;
  71. input.append(1, c);
  72. }
  73. }
  74. return false;
  75. } else {
  76. // continue on with what we have displayed.
  77. c = d.sleep_ms_key(ms_input_delay);
  78. if (c != -1) {
  79. /*
  80. c = d.sleep_key(1);
  81. if (c < 0) {
  82. // handle error
  83. return false;
  84. }
  85. */
  86. if (c > 0x1000)
  87. return false;
  88. if (isprint(c)) {
  89. d << (char)c;
  90. input.append(1, c);
  91. // hot-keys
  92. if (input[0] == '/') {
  93. if (input.size() == 2) {
  94. switch (input[1]) {
  95. case 'j':
  96. case 'J':
  97. erase(d, input.size());
  98. input = "/join ";
  99. d << input;
  100. break;
  101. case 'p':
  102. case 'P':
  103. erase(d, input.size());
  104. input = "/part ";
  105. d << input;
  106. break;
  107. case 't':
  108. case 'T':
  109. erase(d, input.size());
  110. input = "/talkto ";
  111. d << input;
  112. break;
  113. case 'h':
  114. case 'H':
  115. case '?':
  116. erase(d, input.size());
  117. input = "/help";
  118. d << input;
  119. break;
  120. }
  121. }
  122. }
  123. }
  124. if ((c == 0x08) or (c == 0x7f)) {
  125. // hot-keys
  126. if (input[0] == '/') {
  127. if ((input == "/help") or (input == "/talkto ") or
  128. (input == "/join ") or (input == "/part")) {
  129. erase(d, input.size());
  130. erase(d, prompt.size());
  131. input.clear();
  132. prompt.clear();
  133. return false;
  134. }
  135. }
  136. if (input.size() > 1) {
  137. erase(d, 1);
  138. d << input_color;
  139. input.erase(input.length() - 1);
  140. } else {
  141. // erasing the last character
  142. erase(d, 1);
  143. input.clear();
  144. erase(d, prompt.size());
  145. prompt.clear();
  146. return false;
  147. }
  148. }
  149. if (c == 0x0d) {
  150. prompt.clear();
  151. return true;
  152. }
  153. }
  154. return false;
  155. }
  156. }
  157. int main(int argc, char *argv[]) {
  158. using namespace std::chrono_literals;
  159. boost::asio::io_context io_context;
  160. ircClient irc(io_context);
  161. door::Door door("irc-door", argc, argv);
  162. get_logger = [&door]() -> ofstream & { return door.log(); };
  163. if (file_exists("irc-door.yaml")) {
  164. config = YAML::LoadFile("irc-door.yaml");
  165. }
  166. bool update_config = false;
  167. if (!config["hostname"]) {
  168. config["hostname"] = "127.0.0.1";
  169. update_config = true;
  170. }
  171. if (!config["port"]) {
  172. config["port"] = "6697";
  173. update_config = true;
  174. }
  175. if (!config["autojoin"]) {
  176. config["autojoin"] = "#bugz";
  177. update_config = true;
  178. }
  179. if (!config["realname"]) {
  180. config["realname"] = "A poor soul on BZBZ BBS...";
  181. update_config = true;
  182. }
  183. if (!config["username"]) {
  184. config["username"] = "bzbz";
  185. update_config = true;
  186. }
  187. if (update_config) {
  188. std::ofstream fout("irc-door.yaml");
  189. fout << config << std::endl;
  190. }
  191. // configure
  192. irc.nick = door.handle;
  193. irc.realname = config["realname"].as<std::string>();
  194. irc.hostname = config["hostname"].as<std::string>();
  195. irc.port = config["port"].as<std::string>();
  196. irc.username = config["username"].as<std::string>();
  197. irc.autojoin = config["autojoin"].as<std::string>();
  198. irc.debug_output = "irc.log";
  199. irc.begin(); // start the initial request so io_context has work to do
  200. // boost::thread thread(boost::bind(&boost::asio::io_service::run,
  201. // &io_context));
  202. // thread Thread(boost::bind(&boost::asio::io_service::run, &io_context));
  203. thread Thread([&io_context]() -> void { io_context.run(); });
  204. door << "Welcome to the IRC chat door." << door::nl;
  205. // main "loop" -- not the proper way to do it.
  206. bool in_door = true;
  207. while (in_door) {
  208. // the main loop
  209. // custom input routine goes here
  210. if (check_for_input(door, irc)) {
  211. // yes, we have something
  212. if (input[0] == '/') {
  213. // command given
  214. if (std::toupper(input[1]) == 'Q') {
  215. irc.write("QUIT");
  216. }
  217. } else {
  218. std::string output = "PRIVMSG " + irc.talkto + " :" + input;
  219. irc.write(output);
  220. };
  221. input.clear();
  222. door << door::nl;
  223. }
  224. /*
  225. if (door.haskey()) {
  226. door << ">> ";
  227. std::string input = door.input_string(100);
  228. door << door::nl;
  229. if (!input.empty()) {
  230. if (input == "/q") {
  231. irc.write("QUIT :What other doors on BZ&BZ BBS work...");
  232. in_door = false;
  233. }
  234. if (input[0] == '/') {
  235. input.erase(0, 1);
  236. irc.write(input);
  237. } else {
  238. if (irc.talkto.empty()) {
  239. door << " talkto is empty? Whaat?" << door::nl;
  240. } else {
  241. std::string output = "PRIVMSG " + irc.talkto + " :" + input;
  242. irc.write(output);
  243. }
  244. }
  245. }
  246. }
  247. */
  248. boost::optional<std::vector<std::string>> msg;
  249. // hold list of users -- until end names received.
  250. // std::vector<std::string> names;
  251. bool input_cleared = false;
  252. do {
  253. msg = irc.buffer_maybe_pop();
  254. if (msg) {
  255. if (!input_cleared) {
  256. input_cleared = true;
  257. clear_input(door);
  258. }
  259. std::vector<std::string> m = *msg;
  260. if (m.size() == 1) {
  261. // system message
  262. door << "(" << m[0] << ")" << door::nl;
  263. continue;
  264. }
  265. std::string cmd = m[1];
  266. if (cmd == "366") {
  267. // end of names, output and clear
  268. std::string channel = split_limit(m[3], 2)[0];
  269. irc.channels_lock.lock();
  270. door << "* users on " << channel << " : ";
  271. for (auto name : irc.channels[channel]) {
  272. door << name << " ";
  273. }
  274. irc.channels_lock.unlock();
  275. door << door::nl;
  276. // names.clear();
  277. }
  278. // 400 and 500 are errors? should show those.
  279. if ((cmd[0] == '4') or (cmd[0] == '5')) {
  280. std::string tmp = m[3];
  281. tmp.erase(0, 1);
  282. door << "* " << tmp << door::nl;
  283. }
  284. if (cmd == "NOTICE") {
  285. std::string tmp = m[3];
  286. tmp.erase(0, 1);
  287. door << parse_nick(m[0]) << " NOTICE " << tmp << door::nl;
  288. }
  289. if (cmd == "ACTION") {
  290. if (m[2][0] == '#') {
  291. door << "* " << parse_nick(m[0]) << "/" << m[2] << " " << m[3]
  292. << door::nl;
  293. } else {
  294. door << "* " << parse_nick(m[0]) << " " << m[3] << door::nl;
  295. }
  296. }
  297. if (cmd == "TOPIC") {
  298. std::string tmp = m[3];
  299. tmp.erase(0, 1);
  300. door << parse_nick(m[0]) << " set topic of " << m[2] << " to " << tmp
  301. << door::nl;
  302. }
  303. if (cmd == "PRIVMSG") {
  304. door::ANSIColor nick_color{door::COLOR::WHITE, door::COLOR::BLUE};
  305. if (m[2][0] == '#') {
  306. std::string tmp = m[3];
  307. tmp.erase(0, 1);
  308. door::ANSIColor channel_color{door::COLOR::WHITE,
  309. door::COLOR::BLUE};
  310. if (m[2] == irc.talkto) {
  311. channel_color = door::ANSIColor{
  312. door::COLOR::YELLOW, door::COLOR::BLUE, door::ATTR::BOLD};
  313. }
  314. door << nick_color << parse_nick(m[0])
  315. << door::ANSIColor(door::COLOR::CYAN) << "/" << channel_color
  316. << m[2] << door::reset << " " << tmp << door::nl;
  317. } else {
  318. std::string tmp = m[3];
  319. tmp.erase(0, 1);
  320. door << nick_color << parse_nick(m[0]) << door::reset << " " << tmp
  321. << door::nl;
  322. }
  323. }
  324. }
  325. } while (msg);
  326. if (input_cleared)
  327. restore_input(door);
  328. // std::this_thread::sleep_for(200ms);
  329. if (irc.shutdown)
  330. in_door = false;
  331. }
  332. io_context.stop();
  333. Thread.join();
  334. door << "Returning to the BBS..." << door::nl;
  335. // std::this_thread::sleep_for(2s);
  336. return 0;
  337. }