diff options
Diffstat (limited to '')
-rw-r--r-- | .github/kirc.png | bin | 0 -> 442070 bytes | |||
-rw-r--r-- | README | 100 | ||||
-rw-r--r-- | README.md | 89 | ||||
-rw-r--r-- | kirc.c | 99 |
4 files changed, 137 insertions, 151 deletions
diff --git a/.github/kirc.png b/.github/kirc.png Binary files differnew file mode 100644 index 0000000..52a6075 --- /dev/null +++ b/.github/kirc.png @@ -1,100 +0,0 @@ -kirc - -KISS for IRC, an IRC client written in POSIX C99. - - -BACKGROUND ----------- - -After having tried multiple IRC clients, I decided to develope my own. The -result is a portable application that has no dependencies other than a C99 -compiler. - - -OBJECTIVES ----------- - -- Per the UNIX philosophy, "Do one thing and do it well" [3]. -- True to the KISS principle [4], the code-base is intentionally smaller - than most IRC clients (<250 sloc). The benefits of a small code base - reflects in the ability for kirc to be reviewed, understood and - maintained by a single person. Therefore, all feature requests and - commits shall be considered with respect to the readability and - maintainance of the overall project. -- Let's not "reinvent the wheel". Commands and functionality should - feel familiar (e.g. vi command shortcuts) and accessable via a - standard 104-key US QWERTY keyboard layout [5]. Where possible, the - number of keystrokes shall be minimized per command. - - -FEATURES --------- - -- automatic host PING response. -- vi-like shortcuts: - - <message> send a message to the current channel - /m <nick|channel> <message> send a message to a specified nick or channel - /n <message> send a message to NickServ - /j <channel> join a specified channel - /p <channel> leave (part) a specified channel - /Q <message> send a message and close the host connection - /q close the host connection - -- automatic word wrapping using the greedy algorithm. -- color scheme definition via ANSI 8-bit colors [1]. Therefore, one could - theoretically achieve uniform color definition across all shell applications - and tools. - - -INSTALLATION ------------- - -Building and installing on KISS Linux using the Community repository [2]: - - kiss b kirc - kiss i kirc - -Building and installing on Arch Linux using the AUR [6]: - - git clone https://aur.archlinux.org/kirc-git.git - cd kirc - makepkg -si - -Building and installing from source: - - git clone https://github.com/mcpcpc/kirc.git - cd kirc - make - make install - - -USAGE ------ - -usage: kirc [-s hostname] [-p port] [-c channel] [-n nick] [-r real name] -[-u username] [-k password] [-w columns] [-W columns] [-o path] [-v|V] --s server address (default: 'irc.freenode.org') --p server port (default: '6667') --c channel name (default: '#kisslinux') --n nickname (required) --u server username (optional) --k server password (optional) --r real name (optional) --v version information --V verbose output (e.g. raw stream) --o output path to log irc stream --w maximum width of the printed left column (default: '10') --W maximum width of the entire printed stream (default '80') - - -REFERENCES ----------- - -[0] https://tools.ietf.org/html/rfc2812 -[1] https://en.wikipedia.org/wiki/ANSI_escape_code -[2] https://github.com/kisslinux/community -[3] https://en.wikipedia.org/wiki/Unix_philosophy -[4] https://en.wikipedia.org/wiki/KISS_principle -[5] https://en.wikipedia.org/wiki/Keyboard_layout -[6] https://aur.archlinux.org/packages/kirc-git/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e6d703 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +<h3 align="center"><img src="https://raw.githubusercontent.com/mcpcpc/kirc/master/.github/kirc.png" alt="logo" height="170px"></h3> +<p align="center">KISS for IRC, a tiny IRC client written in POSIX C99.</p> +<p align="center"> +<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"></a> +<a href="https://github.com/mcpcpc/kirc/releases"><img src="https://img.shields.io/github/v/release/mcpcpc/kirc.svg"></a> +<a href="https://repology.org/metapackage/kirc"><img src="https://repology.org/badge/tiny-repos/kirc.svg" alt="Packaging status"></a> +</p> + + +## Objectives + +*"Do one thing and do it well"* — Per the [Unix philosophy](https://en.wikipedia.org/wiki/Unix_philosophy), emphasis was placed on building simple, short, clear, modular, and extensible code that can be easily maintained and repurposed. + +*Portability* — [POSIX](https://en.wikipedia.org/wiki/POSIX) compliance ensures seamless compatibility and interoperability between variants of Unix and other operating systems. + +*Usability* — Commands and shortcuts should feel natural and accessible using a [standard 104-key US keyboard layout](https://en.wikipedia.org/wiki/Keyboard_layout). Where possible, the number of keystrokes shall be minimized. + +Usage +----- + +```shell +usage: kirc [-s hostname] [-p port] [-c channel] [-n nick] [-r real name] [-u username] [-k password] [-w columns] [-W columns] [-o path] [-v|V] +-s server address (default: 'irc.freenode.org') +-p server port (default: '6667') +-c channel name (default: '#kisslinux') +-n nickname (required) +-u server username (optional) +-k server password (optional) +-r real name (optional) +-v version information +-V verbose output (e.g. raw stream) +-o output path to log irc stream +-w maximum width of the printed left column (default: '10') +-W maximum width of the entire printed stream (default '80') +``` + +Features +-------- + +- No dependencies other than a [C99 compiler](https://gcc.gnu.org/). +- Automatic server *PING* response. +- Complies with [RFC 2812](https://tools.ietf.org/html/rfc2812) standard. +- vi-like command shortcuts: + +```shell +<message> Send a message to the current channel. +/m <nick|channel> <message> Send a message to a specified channel or nick. +/M <message> Send a message to NickServ. +/Q <message> Send a message and close the host connection. +/x <message> Send a message directly to the server. +/j <channel> Join a specified channel. +/p <channel> Leave (part) a specified channel. +/n List all users on the current channel. +/q Close the host connection. +``` + +- Color scheme definition via [ANSI 8-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code). Therefore, one could theoretically achieve uniform color definition across all shell applications and tools. + +Screenshots +----------- + + + +Installation +------------ + +Building and installing on **KISS Linux** using the Community repository: + +```shell +kiss b kirc +kiss i kirc +``` + +Building and installing on **Arch Linux** or **Manjaro** using the Arch AUR: + +```shell +git clone https://aur.archlinux.org/kirc-git.git +cd kirc +makepkg -si +``` + +Building and installing from source (works on **Rasbian**, **Debian**, **Ubuntu** and many other distributions): + +```shell +git clone https://github.com/mcpcpc/kirc.git +cd kirc +make +make install +``` @@ -16,9 +16,9 @@ #define CHA_MAX 200 /* gauranteed max channel length */ static int conn; /* connection socket */ -static size_t verb = 0; /* verbose output (e.g. raw stream) */ -static size_t cmax = 80; /* max number of chars per line */ -static size_t gutl = 10; /* max char width of left column */ +static size_t verb = 0; /* verbose output (e.g. raw stream) */ +static size_t cmax = 80; /* max number of chars per line */ +static size_t gutl = 10; /* max char width of left column */ static char * host = "irc.freenode.org"; /* irc host address */ static char * chan = "kisslinux"; /* channel */ static char * port = "6667"; /* server port */ @@ -77,13 +77,6 @@ irc_init() { getaddrinfo(host, port, &hints, &res); conn = socket(res->ai_family, res->ai_socktype, res->ai_protocol); connect(conn, res->ai_addr, res->ai_addrlen); - - if (nick) raw("NICK %s\r\n", nick); - if (user && real) raw("USER %s - - :%s\r\n", user, real); - if (user && !real && nick) raw("USER %s - - :%s\r\n", user, nick); - if (!user && !real && nick) raw("USER %s - - :%s\r\n", nick, nick); - if (pass) raw("PASS %s\r\n", pass); - fcntl(conn, F_SETFL, O_NONBLOCK); } @@ -100,47 +93,45 @@ printw(const char *format, ...) { if (olog) printa(line); - for (i = 0; isspace(line[i]); i++) printf("%c", line[i]); + for (i = 0; isspace(line[i]); i++) putchar(line[i]); spaceleft = cmax + gutl - (i - 1); for(tok = strtok(&line[i], " "); tok != NULL; tok = strtok(NULL, " ")) { wordwidth = strlen(tok); if ((wordwidth + spacewidth) > spaceleft) { - printf("\n%*.s%s", (int) gutl + 2, "", tok); - spaceleft = cmax - (gutl + 2 + wordwidth); + printf("\n%*.s%s ", (int) gutl + 1, "", tok); + spaceleft = cmax - (gutl + 1 + wordwidth); } else { - printf(" %s", tok); + printf("%s ", tok); spaceleft = spaceleft - (wordwidth + spacewidth); } } + printf("\n"); } static void -raw_parser(char *in) { - if (verb) printf(">> %s\n", in); - if (!strncmp(in, "PING", 4)) { - in[1] = 'O'; - raw("%s\r\n", in); - } else if (in[0] == ':') { - char *prefix = strtok(in, " ") + 1; - char *suffix = strtok(NULL, ":"); - char *message = strtok(NULL, "\r"); - char *nickname = strtok(prefix, "!"); /* , *usr = strtok(NULL, "@"), *hos = prefix; */ - char *command = strtok(suffix, "#& "), *channel = strtok(NULL, " "); +raw_parser(char *usrin) { + if (verb) printf(">> %s\n", usrin); + if (!strncmp(usrin, "PING", 4)) { + usrin[1] = 'O'; + raw("%s\r\n", usrin); + } else if (usrin[0] == ':') { + char *prefix = strtok(usrin, " ") + 1, *suffix = strtok(NULL, ":"), + *message = strtok(NULL, "\r"), *nickname = strtok(prefix, "!"), + *command = strtok(suffix, "#& "), *channel = strtok(NULL, " "); if (!strncmp(command, "001", 3)) raw("JOIN #%s\r\n", chan); else if (!strncmp(command, "QUIT", 4)) { - printw("%*s \x1b[34;1m%s\x1b[0m\n", gutl, "<--", nickname); + printw("%*s \x1b[34;1m%s\x1b[0m", (int)gutl, "<--", nickname); } else if (!strncmp(command, "JOIN", 4)) { - printw("%*s \x1b[32;1m%s\x1b[0m\n", gutl, "-->", nickname); - } else if (!strncmp(command, "PRIVMSG", 7) && !strncmp(channel, nick, strlen(nick))) { - size_t len = strlen(nickname); - printw("%*s\x1b[43;1m%-.*s\x1b[0m %s\n", \ - gutl-(len <= gutl ? len : gutl), "", gutl, nickname, message); + printw("%*s \x1b[32;1m%s\x1b[0m", (int)gutl, "-->", nickname); + } else if (!strncmp(command, "PRIVMSG", 7) && + !strncmp(channel, nick, strlen(nick))) { + int s = gutl - (strlen(nickname) <= gutl ? strlen(nickname) : gutl); + printw("%*s\x1b[43;1m%-.*s\x1b[0m %s", s, "", (int)gutl, nickname, message); } else { - size_t len = strlen(nickname); - printw("%*s\x1b[33;1m%-.*s\x1b[0m %s\n", \ - gutl-(len <= gutl ? len : gutl), "", gutl, nickname, message); + int s = gutl - (strlen(nickname) <= gutl ? strlen(nickname) : gutl); + printw("%*s\x1b[33;1m%-.*s\x1b[0m %s", s, "", (int)gutl, nickname, message); } } } @@ -168,7 +159,7 @@ main(int argc, char **argv) { } } - if (nick == NULL) { + if (!nick) { fprintf(stderr, "nick not specified\n"); return 1; } @@ -186,6 +177,12 @@ main(int argc, char **argv) { irc_init(); + if (nick) raw("NICK %s\r\n", nick); + if (user && real) raw("USER %s - - :%s\r\n", user, real); + if (user && !real && nick) raw("USER %s - - :%s\r\n", user, nick); + if (!user && !real && nick) raw("USER %s - - :%s\r\n", nick, nick); + if (pass) raw("PASS %s\r\n", pass); + while ((sl = read(conn, &s, 1))) { if (sl > 0) b[o] = s; @@ -204,33 +201,33 @@ main(int argc, char **argv) { else { char usrin[MSG_MAX], v1[MSG_MAX - CHA_MAX], v2[CHA_MAX], c1; struct termios tp, save; + tcgetattr(STDIN_FILENO, &tp); save = tp; tp.c_cc[VERASE] = 127; + if (tcsetattr(STDIN_FILENO, TCSANOW, &tp) < 0) return 2; while (waitpid(pid, NULL, WNOHANG) == 0) { if (!kbhit()) dprintf(fd[1], "/\n"); - else { - tcsetattr(STDIN_FILENO, TCSANOW, &tp); - if (fgets(usrin, MSG_MAX, stdin) == NULL) return 1; - tcsetattr(STDIN_FILENO, TCSANOW, &save); - - if (sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) == 3 || - sscanf(usrin, "/%[Qnjp] %[^\n]\n", &c1, v1) == 2 || - sscanf(usrin, "/%[q]\n", &c1) == 1) { + else if (fgets(usrin, MSG_MAX, stdin) != NULL) { + if (sscanf(usrin, "/%[m] %s %[^\n]\n", &c1, v2, v1) > 2 || + sscanf(usrin, "/%[xMQqnjp] %[^\n]\n", &c1, v1) > 0) { switch (c1) { - case 'q': dprintf(fd[1], "quit\n"); break; - case 'Q': dprintf(fd[1], "quit %s\n", v1); break; - case 'j': dprintf(fd[1], "join %s\n", v1); break; - case 'p': dprintf(fd[1], "part %s\n", v1); break; - case 'n': dprintf(fd[1], "privmsg nickserv :%s\n", v1); break; - case 'm': dprintf(fd[1], "privmsg %s :%s\n", v2, v1); break; + case 'x': dprintf(fd[1], "%s\n", v1); break; + case 'q': dprintf(fd[1], "quit\n"); break; + case 'Q': dprintf(fd[1], "quit %s\n", v1); break; + case 'j': dprintf(fd[1], "join %s\n", v1); break; + case 'p': dprintf(fd[1], "part %s\n", v1); break; + case 'n': dprintf(fd[1], "names #%s\n", chan); break; + case 'M': dprintf(fd[1], "privmsg nickserv :%s\n", v1); break; + case 'm': dprintf(fd[1], "privmsg %s :%s\n", v2, v1); break; } - } else dprintf(fd[1], "privmsg #%s :%s", chan, usrin); + } else dprintf(fd[1], "privmsg #%s :%s", chan, usrin); } } - fprintf(stderr, "<< irc server connection closed\n"); + if (tcsetattr(STDIN_FILENO, TCSANOW, &save) < 0) return 2; + puts("<< connection closed"); } return 0; } |