irc.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2018 K. Lange
  5. *
  6. * irc - Internet Relay Chat client
  7. */
  8. #include <stdlib.h>
  9. #include <stdio.h>
  10. #include <unistd.h>
  11. #include <fcntl.h>
  12. #include <termios.h>
  13. #include <va_list.h>
  14. #include <time.h>
  15. #include <string.h>
  16. #include <sys/fswait.h>
  17. #define _ITALIC "\033[3m"
  18. #define _END "\033[0m\n"
  19. #define VERSION_STRING "0.3.0"
  20. /* Theming */
  21. #define TIME_FMT "%02d:%02d:%02d"
  22. #define TIME_ARGS hr, min, sec
  23. static char * nick = "toaru-user";
  24. static char * host = NULL;
  25. static char * pass = NULL;
  26. static unsigned short port = 6667;
  27. static char * channel = NULL;
  28. static int sock_fd;
  29. static FILE * sock_r;
  30. static FILE * sock_w;
  31. struct color_pair {
  32. int fg;
  33. int bg;
  34. };
  35. static void show_usage(int argc, char * argv[]) {
  36. fprintf(stderr,
  37. "irc - Terminal IRC client.\n"
  38. "\n"
  39. "usage: %s [-h] [-p port] [-n nick] host\n"
  40. "\n"
  41. " -p port " _ITALIC "Specify port to connect to" _END
  42. " -P pass " _ITALIC "Password for server connection" _END
  43. " -n nick " _ITALIC "Specify a nick to use" _END
  44. " -h " _ITALIC "Print this help message" _END
  45. "\n", argv[0]);
  46. exit(1);
  47. }
  48. static struct termios old;
  49. static void set_unbuffered() {
  50. tcgetattr(fileno(stdin), &old);
  51. struct termios new = old;
  52. new.c_lflag &= (~ICANON & ~ECHO);
  53. tcsetattr(fileno(stdin), TCSAFLUSH, &new);
  54. }
  55. static void set_buffered() {
  56. tcsetattr(fileno(stdin), TCSAFLUSH, &old);
  57. }
  58. static int user_color(char * user) {
  59. int i = 0;
  60. while (*user) {
  61. i += *user;
  62. user++;
  63. }
  64. i = i % 5;
  65. switch (i) {
  66. case 0: return 2;
  67. case 1: return 3;
  68. case 2: return 4;
  69. case 3: return 6;
  70. case 4: return 10;
  71. }
  72. return 0;
  73. }
  74. static struct color_pair irc_color_to_pair(int fg, int bg) {
  75. int _fg = 0;
  76. int _bg = 0;
  77. if (fg == -1) {
  78. _fg = -1;
  79. } else {
  80. fg = fg % 16;
  81. switch (fg) {
  82. case 0: _fg = 15; break;
  83. case 1: _fg = 0; break;
  84. case 2: _fg = 4; break;
  85. case 3: _fg = 2; break;
  86. case 4: _fg = 9; break;
  87. case 5: _fg = 1; break;
  88. case 6: _fg = 5; break;
  89. case 7: _fg = 3; break;
  90. case 8: _fg = 11; break;
  91. case 9: _fg = 10; break;
  92. case 10: _fg = 6; break;
  93. case 11: _fg = 14; break;
  94. case 12: _fg = 12; break;
  95. case 13: _fg = 13; break;
  96. case 14: _fg = 8; break;
  97. case 15: _fg = 7; break;
  98. }
  99. }
  100. if (bg == -1) {
  101. _bg = -1;
  102. } else {
  103. bg = bg % 16;
  104. switch (bg) {
  105. case 0: _bg = 15; break;
  106. case 1: _bg = 0; break;
  107. case 2: _bg = 4; break;
  108. case 3: _bg = 2; break;
  109. case 4: _bg = 9; break;
  110. case 5: _bg = 1; break;
  111. case 6: _bg = 5; break;
  112. case 7: _bg = 3; break;
  113. case 8: _bg = 11; break;
  114. case 9: _bg = 10; break;
  115. case 10: _bg = 6; break;
  116. case 11: _bg = 14; break;
  117. case 12: _bg = 12; break;
  118. case 13: _bg = 13; break;
  119. case 14: _bg = 8; break;
  120. case 15: _bg = 7; break;
  121. }
  122. }
  123. return (struct color_pair){_fg, _bg};
  124. }
  125. static void get_time(int * h, int * m, int * s) {
  126. time_t rawtime;
  127. time(&rawtime);
  128. struct tm *tm_struct = localtime(&rawtime);
  129. *h = tm_struct->tm_hour;
  130. *m = tm_struct->tm_min;
  131. *s = tm_struct->tm_sec;
  132. }
  133. static void print_color(struct color_pair t) {
  134. fprintf(stdout, "\033[");
  135. if (t.fg == -1) {
  136. fprintf(stdout,"39");
  137. } else if (t.fg > 15) {
  138. /* TODO */
  139. } else if (t.fg > 7) {
  140. fprintf(stdout,"9%d", t.fg - 8);
  141. } else {
  142. fprintf(stdout,"3%d", t.fg);
  143. }
  144. fprintf(stdout, ";");
  145. if (t.bg == -1) {
  146. fprintf(stdout, "49");
  147. } else if (t.bg > 15) {
  148. /* TODO */
  149. } else if (t.bg > 7) {
  150. fprintf(stdout,"10%d", t.bg - 8);
  151. } else {
  152. fprintf(stdout,"4%d", t.bg);
  153. }
  154. fprintf(stdout, "m");
  155. fflush(stdout);
  156. }
  157. static void WRITE(const char * fmt, ...) {
  158. int bold_on = 0;
  159. int italic_on = 0;
  160. va_list args;
  161. va_start(args, fmt);
  162. char * tmp;
  163. vasprintf(&tmp, fmt, args);
  164. va_end(args);
  165. struct winsize w;
  166. ioctl(0, TIOCGWINSZ, &w);
  167. fprintf(stdout,"\033[%d;1H\033[K", w.ws_row);
  168. int line_feed_pending = 0;
  169. char * c = tmp;
  170. while (*c) {
  171. if (*c == '\n') {
  172. if (line_feed_pending) {
  173. /* Print line feed */
  174. fprintf(stdout, "\n");
  175. }
  176. line_feed_pending = 1;
  177. c++;
  178. continue;
  179. } else {
  180. if (line_feed_pending) {
  181. line_feed_pending = 0;
  182. /* Print line feed */
  183. fprintf(stdout, "\n");
  184. }
  185. }
  186. if (*c == 0x03) {
  187. c++;
  188. int i = -1;
  189. int j = -1;
  190. if (*c >= '0' && *c <= '9') {
  191. i = (*c - '0');
  192. c++;
  193. }
  194. if (*c >= '0' && *c <= '9') {
  195. i *= 10;
  196. i += (*c - '0');
  197. c++;
  198. }
  199. if (*c == ',') {
  200. c++;
  201. if (*c >= '0' && *c <= '9') {
  202. j = (*c - '0');
  203. c++;
  204. }
  205. if (*c >= '0' && *c <= '9') {
  206. j *= 10;
  207. j = (*c - '0');
  208. c++;
  209. }
  210. }
  211. struct color_pair t = irc_color_to_pair(i, j);
  212. print_color(t);
  213. continue;
  214. }
  215. if (*c == 0x02) {
  216. if (bold_on) {
  217. fprintf(stdout,"\033[22m");
  218. bold_on = 0;
  219. } else {
  220. fprintf(stdout,"\033[1m");
  221. bold_on = 1;
  222. }
  223. c++;
  224. continue;
  225. }
  226. if (*c == 0x16) {
  227. if (italic_on) {
  228. fprintf(stdout,"\033[23m");
  229. italic_on = 0;
  230. } else {
  231. fprintf(stdout,"\033[3m");
  232. italic_on = 1;
  233. }
  234. c++;
  235. continue;
  236. }
  237. if (*c == 0x0f) {
  238. fprintf(stdout, "\033[0m");
  239. c++;
  240. bold_on = 0;
  241. italic_on = 0;
  242. continue;
  243. }
  244. fprintf(stdout, "%c", *c);
  245. c++;
  246. }
  247. if (line_feed_pending) {
  248. fprintf(stdout, "\033[0m\033[K\n");
  249. }
  250. fflush(stdout);
  251. free(tmp);
  252. }
  253. static void handle(char * line) {
  254. char * c = line;
  255. while (c < line + strlen(line)) {
  256. char * e = strstr(c, "\r\n");
  257. if (e > line + strlen(line)) {
  258. break;
  259. }
  260. if (!e) {
  261. /* Write c */
  262. WRITE(c);
  263. goto next;
  264. }
  265. *e = '\0';
  266. if (strstr(c, "PING") == c) {
  267. char * t = strstr(c, ":");
  268. fprintf(sock_w, "PONG %s\r\n", t);
  269. fflush(sock_w);
  270. goto next;
  271. }
  272. char * user, * command, * channel, * message;
  273. user = c;
  274. if (user[0] == ':') {
  275. user++;
  276. }
  277. command = strstr(user, " ");
  278. if (!command) {
  279. WRITE("%s\n", user);
  280. goto next;
  281. }
  282. command[0] = '\0';
  283. command++;
  284. channel = strstr(command, " ");
  285. if (!channel) {
  286. WRITE("%s %s\n", user, command);
  287. goto next;
  288. }
  289. channel[0] = '\0';
  290. channel++;
  291. message = strstr(channel, " ");
  292. if (message) {
  293. message[0] = '\0';
  294. message++;
  295. if (message[0] == ':') {
  296. message++;
  297. }
  298. }
  299. int hr, min, sec;
  300. get_time(&hr, &min, &sec);
  301. if (!strcmp(command, "PRIVMSG")) {
  302. if (!message) continue;
  303. char * t = strstr(user, "!");
  304. if (t) { t[0] = '\0'; }
  305. t = strstr(user, "@");
  306. if (t) { t[0] = '\0'; }
  307. if (strstr(message, "\001ACTION ") == message) {
  308. message = message + 8;
  309. char * x = strstr(message, "\001");
  310. if (x) *x = '\0';
  311. WRITE(TIME_FMT " \002* \003%d%s\003\002 %s\n", TIME_ARGS, user_color(user), user, message);
  312. } else {
  313. WRITE(TIME_FMT " \00314<\003%d%s\00314>\003 %s\n", TIME_ARGS, user_color(user), user, message);
  314. }
  315. } else if (!strcmp(command, "332")) {
  316. if (!message) {
  317. continue;
  318. }
  319. /* Topic */
  320. } else if (!strcmp(command, "JOIN")) {
  321. char * t = strstr(user, "!");
  322. if (t) { t[0] = '\0'; }
  323. t = strstr(user, "@");
  324. if (t) { t[0] = '\0'; }
  325. if (channel[0] == ':') { channel++; }
  326. WRITE(TIME_FMT " \00312-\003!\00312-\00311 %s\003 has joined \002%s\n", TIME_ARGS, user, channel);
  327. } else if (!strcmp(command, "PART")) {
  328. char * t = strstr(user, "!");
  329. if (t) { t[0] = '\0'; }
  330. t = strstr(user, "@");
  331. if (t) { t[0] = '\0'; }
  332. if (channel[0] == ':') { channel++; }
  333. WRITE(TIME_FMT " \00312-\003!\00312\003-\00310 %s\003 has left \002%s\n", TIME_ARGS, user, channel);
  334. } else if (!strcmp(command, "372")) {
  335. WRITE(TIME_FMT " \00314%s\003 %s\n", TIME_ARGS, user, message ? message : "");
  336. } else if (!strcmp(command, "376")) {
  337. /* End of MOTD */
  338. WRITE(TIME_FMT " \00314%s (end of MOTD)\n", TIME_ARGS, user);
  339. } else {
  340. WRITE(TIME_FMT " \00310%s %s %s %s\n", TIME_ARGS, user, command, channel, message ? message : "");
  341. }
  342. next:
  343. if (!e) break;
  344. c = e + 2;
  345. }
  346. }
  347. static void redraw_buffer(char * buf) {
  348. struct winsize w;
  349. ioctl(0, TIOCGWINSZ, &w);
  350. fprintf(stdout,"\033[%d;1H [%s] ", w.ws_row, channel ? channel : "(status)");
  351. fprintf(stdout,"%s\033[K", buf);
  352. fflush(stdout);
  353. }
  354. void handle_input(char * buf) {
  355. fflush(stdout);
  356. if (strstr(buf, "/help") == buf) {
  357. WRITE("[help] help text goes here\n");
  358. } else if (strstr(buf, "/quit") == buf) {
  359. char * m = strstr(buf, " "); if (m) m++;
  360. fprintf(sock_w, "QUIT :%s\r\n", m ? m : "https://gitlab.com/toaruos");
  361. fflush(sock_w);
  362. fprintf(stderr,"\033[0m\n");
  363. set_buffered();
  364. exit(0);
  365. } else if (strstr(buf,"/part") == buf) {
  366. if (!channel) {
  367. fprintf(stderr, "Not in a channel.\n");
  368. return;
  369. }
  370. char * m = strstr(buf, " "); if (m) m++;
  371. fprintf(sock_w, "PART %s%s%s\r\n", channel, m ? " :" : "", m ? m : "");
  372. fflush(sock_w);
  373. free(channel);
  374. channel = NULL;
  375. } else if (strstr(buf,"/join ") == buf) {
  376. char * m = strstr(buf, " "); if (m) m++;
  377. fprintf(sock_w, "JOIN %s\r\n", m);
  378. fflush(sock_w);
  379. channel = strdup(m);
  380. } else if (strstr(buf, "/") == buf) {
  381. WRITE("[system] Unknown command: %s\n", buf);
  382. } else {
  383. int hr, min, sec;
  384. get_time(&hr, &min, &sec);
  385. WRITE("%02d:%02d:%02d \00314<\003\002%s\002\00314>\003 %s\n", hr, min, sec, nick, buf);
  386. fprintf(sock_w, "PRIVMSG %s :%s\r\n", channel, buf);
  387. }
  388. redraw_buffer("");
  389. }
  390. int main(int argc, char * argv[]) {
  391. /* Option parsing */
  392. int c;
  393. while ((c = getopt(argc, argv, "?hp:n:P:")) != -1) {
  394. switch (c) {
  395. case 'n':
  396. nick = optarg;
  397. break;
  398. case 'P':
  399. pass = optarg;
  400. break;
  401. case 'p':
  402. port = atoi(optarg);
  403. break;
  404. case 'h':
  405. case '?':
  406. default:
  407. show_usage(argc, argv);
  408. break;
  409. }
  410. }
  411. if (optind >= argc) {
  412. show_usage(argc, argv);
  413. }
  414. host = argv[optind];
  415. /* Connect */
  416. {
  417. char tmphost[512];
  418. sprintf(tmphost, "/dev/net/%s:%d", host, port);
  419. sock_fd = open(tmphost, O_RDWR);
  420. if (sock_fd < 0) {
  421. fprintf(stderr, "%s: Connection failed or network not available.\n", argv[0]);
  422. return 1;
  423. }
  424. sock_r = fdopen(sock_fd, "r");
  425. sock_w = fdopen(sock_fd, "w");
  426. }
  427. set_unbuffered();
  428. fprintf(stdout, " - Toaru IRC v %s - \n", VERSION_STRING);
  429. fprintf(stdout, " Copyright 2015-2018 K. Lange\n");
  430. fprintf(stdout, " https://toaruos.org - https://gitlab.com/toaruos\n");
  431. fprintf(stdout, " \n");
  432. fprintf(stdout, " For help, type /help\n");
  433. if (pass) {
  434. fprintf(sock_w, "PASS %s\r\n", pass);
  435. }
  436. fprintf(sock_w, "NICK %s\r\nUSER %s * 0 :%s\r\n", nick, nick, nick);
  437. fflush(sock_w);
  438. int fds[] = {sock_fd, STDIN_FILENO, sock_fd};
  439. char net_buf[2048];
  440. memset(net_buf, 0, 2048);
  441. int net_buf_p = 0;
  442. char buf[1024] = {0};
  443. int buf_p = 0;
  444. while (1) {
  445. int index = fswait2(2,fds,200);
  446. if (index == 1) {
  447. /* stdin */
  448. int c = fgetc(stdin);
  449. if (c < 0) {
  450. continue;
  451. }
  452. if (c == 0x08) {
  453. /* Remove from buffer */
  454. if (buf_p) {
  455. buf[buf_p-1] = '\0';
  456. buf_p--;
  457. redraw_buffer(buf);
  458. }
  459. } else if (c == '\n') {
  460. /* Send buffer */
  461. handle_input(buf);
  462. memset(buf, 0, 1024);
  463. buf_p = 0;
  464. } else {
  465. /* Append buffer, or check special keys */
  466. buf[buf_p] = c;
  467. buf_p++;
  468. redraw_buffer(buf);
  469. }
  470. } else if (index == 0) {
  471. /* network */
  472. do {
  473. int c = fgetc(sock_r);
  474. if (c < 0) continue;
  475. net_buf[net_buf_p] = c;
  476. net_buf_p++;
  477. if (c == '\n' || net_buf_p == 2046) {
  478. handle(net_buf);
  479. net_buf_p = 0;
  480. memset(net_buf, 0, 2048);
  481. redraw_buffer(buf);
  482. }
  483. } while (!_fwouldblock(sock_r));
  484. } else {
  485. /* timer */
  486. }
  487. }
  488. }