diff
install.sh | 8 +
irc | 324 +++++++++++++++++++++++++++++++++++
ircf | 556 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 888 insertions(+)
diff --git a/install.sh b/install.sh
new file mode 100644
index 0000000..f8ad7d4
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -eu
+dest=${1:-"$HOME/.local/bin"}
+mkdir -p "$dest"
+cp "./irc" "$dest/irc"
+cp "./ircf" "$dest/ircf"
+printf "installed to %s\n" "$dest"
+printf "add to PATH if needed: export PATH=\"%s:\$PATH\"\n" "$dest"
diff --git a/irc b/irc
new file mode 100755
index 0000000..ba2ba7d
--- /dev/null
+++ b/irc
@@ -0,0 +1,324 @@
+#!/bin/sh
+# Usage: irc [<server>] [<port>] [<nick>] [<username>] [<realname>]
+
+[ -z "$IRC" ] && {
+ fd="/tmp/.irc2"; rm -f "$fd"; mkfifo "$fd"
+ nc "${1:-irc.libera.chat}" "${2:-6667}" <"$fd" | \
+ IRC=1 OUTFD=3 exec "$0" "$@" 3>&1 >"$fd"
+}
+
+stty_save=$(stty -g </dev/tty)
+stty -echo -icanon min 1 time 0 onlcr </dev/tty
+trap 'stty "$stty_save" </dev/tty; kill 0' EXIT HUP INT
+if [ -n "$OUTFD" ]; then
+ out="/dev/fd/$OUTFD"
+else
+ out="/dev/tty"
+fi
+printf 'NICK %s\r\n' "${nick:=${3:-$(id -un)}}"
+printf 'USER %s localhost * :%s\r\n' "${4:-$nick}" "${5:-$nick}"
+
+curr=""
+[ -n "${IRC_CMD_PREFIXES:-}" ] || IRC_CMD_PREFIXES="/ ; : &"
+
+is_cmd_prefix() {
+ cmd=$1
+ [ -z "$cmd" ] && return 1
+ first=${cmd%"${cmd#?}"}
+ case $IRC_CMD_PREFIXES in
+ *' '*)
+ for p in $IRC_CMD_PREFIXES; do
+ [ "$first" = "$p" ] && return 0
+ done
+ return 1
+ ;;
+ esac
+ case $IRC_CMD_PREFIXES in
+ *"$first"*) return 0 ;;
+ esac
+ return 1
+}
+
+handle_cmd() {
+ cmd=$1
+ [ -z "$cmd" ] && return
+ if is_cmd_prefix "$cmd"; then
+ [ "${cmd#?}" = "" ] && return
+ cmd="/${cmd#?}"
+ fi
+ case $cmd in
+ /join*)
+ curr=${cmd#* }
+ printf '%s\r\n' "${cmd#/}"
+ ;;
+ /part*)
+ channel=${cmd#* }
+ printf 'PART %s\r\n' "$channel"
+ ;;
+ /quit*)
+ reason=${cmd#* }
+ [ -z "$reason" ] && reason="Leaving"
+ printf 'QUIT :%s\r\n' "$reason"
+ exit 0
+ ;;
+ /nick*)
+ newnick=${cmd#* }
+ printf 'NICK %s\r\n' "$newnick"
+ ;;
+ /whois*)
+ target=${cmd#* }
+ printf 'WHOIS %s\r\n' "$target"
+ ;;
+ /list*)
+ channel=${cmd#* }
+ if [ -z "$channel" ]; then
+ printf 'LIST\r\n'
+ else
+ printf 'LIST %s\r\n' "$channel"
+ fi
+ ;;
+ /topic*)
+ channel=${cmd#* }
+ printf 'TOPIC %s\r\n' "$channel"
+ ;;
+ /msg*)
+ target_msg=${cmd#* }
+ target=${target_msg%% *}
+ curr=$target
+ if [ "$target_msg" != "$target" ]; then
+ msg=${target_msg#* }
+ printf 'PRIVMSG %s :%s\r\n' "$target" "$msg"
+ printf ':%s!%s@localhost PRIVMSG %s :%s\n' "$nick" "${4:-$nick}" "$target" "$msg" >"$out"
+ fi
+ ;;
+ /invite*)
+ user_channel=${cmd#* }
+ user=${user_channel%% *}
+ channel=${user_channel#* }
+ printf 'INVITE %s %s\r\n' "$user" "$channel"
+ ;;
+ /kick*)
+ channel_user=${cmd#* }
+ channel=${channel_user%% *}
+ user=${channel_user#* }
+ printf 'KICK %s %s\r\n' "$channel" "$user"
+ ;;
+ /mode*)
+ channel_mode=${cmd#* }
+ channel=${channel_mode%% *}
+ mode=${channel_mode#* }
+ printf 'MODE %s %s\r\n' "$channel" "$mode"
+ ;;
+ /away*)
+ message=${cmd#* }
+ if [ -z "$message" ]; then
+ printf 'AWAY\r\n'
+ else
+ printf 'AWAY :%s\r\n' "$message"
+ fi
+ ;;
+ /me*)
+ action=${cmd#* }
+ printf 'PRIVMSG %s :\001ACTION %s\001\r\n' "$curr" "$action"
+ printf ':%s!%s@localhost PRIVMSG %s :\001ACTION %s\001\n' "$nick" "${4:-$nick}" "$curr" "$action" >"$out"
+ ;;
+ /*)
+ printf '%s\r\n' "${cmd#/}"
+ ;;
+ *)
+ printf 'PRIVMSG %s :%s\r\n' "$curr" "$cmd"
+ printf ':%s!%s@localhost PRIVMSG %s :%s\n' "$nick" "${4:-$nick}" "$curr" "$cmd" >"$out"
+ ;;
+ esac
+}
+
+buf=""
+cur=0
+
+buf_len() {
+ printf '%s' "$buf" | awk '{ print length($0) }'
+}
+
+norm_int() {
+ case ${1:-0} in
+ ''|*[!0-9]*)
+ printf '0'
+ ;;
+ *)
+ printf '%s' "$1"
+ ;;
+ esac
+}
+
+calc_add() {
+ awk -v a="$1" -v b="$2" 'BEGIN{
+ if (a !~ /^[0-9]+$/) a = 0
+ if (b !~ /^[0-9]+$/) b = 0
+ print a + b
+ }'
+}
+
+calc_sub_nonneg() {
+ awk -v a="$1" -v b="$2" 'BEGIN{
+ if (a !~ /^[0-9]+$/) a = 0
+ if (b !~ /^[0-9]+$/) b = 0
+ d = a - b
+ if (d < 0) d = 0
+ print d
+ }'
+}
+
+buf_left() {
+ n=$(norm_int "${1:-0}")
+ [ "$n" -le 0 ] && { printf ''; return; }
+ printf '%s' "$buf" | awk -v n="$n" '{ print substr($0, 1, n) }'
+}
+
+buf_right() {
+ n=$(norm_int "${1:-0}")
+ [ "$n" -le 0 ] && { printf '%s' "$buf"; return; }
+ printf '%s' "$buf" | awk -v n="$n" '{ print substr($0, n + 1) }'
+}
+
+buf_insert() {
+ ch=$1
+ cur=$(norm_int "${cur:-0}")
+ left=$(buf_left "$cur")
+ right=$(buf_right "$cur")
+ buf=$left$ch$right
+ cur=$(calc_add "$cur" 1)
+}
+
+buf_backspace() {
+ cur=$(norm_int "${cur:-0}")
+ [ "$cur" -le 0 ] && return
+ left=$(buf_left "$(calc_sub_nonneg "$cur" 1)")
+ right=$(buf_right "$cur")
+ buf=$left$right
+ cur=$(calc_sub_nonneg "$cur" 1)
+}
+
+buf_delete() {
+ cur=$(norm_int "${cur:-0}")
+ len=$(buf_len)
+ len=$(norm_int "$len")
+ [ "$cur" -ge "$len" ] && return
+ left=$(buf_left "$cur")
+ right=$(buf_right "$(calc_add "$cur" 1)")
+ buf=$left$right
+}
+
+buf_move_left() {
+ cur=$(norm_int "${cur:-0}")
+ [ "$cur" -gt 0 ] && cur=$(calc_sub_nonneg "$cur" 1)
+}
+
+buf_move_right() {
+ cur=$(norm_int "${cur:-0}")
+ len=$(buf_len)
+ len=$(norm_int "$len")
+ [ "$cur" -lt "$len" ] && cur=$(calc_add "$cur" 1)
+}
+
+buf_move_home() {
+ cur=0
+}
+
+buf_move_end() {
+ cur=$(norm_int "$(buf_len)")
+}
+
+render_line() {
+ cur=$(norm_int "${cur:-0}")
+ len=$(norm_int "$(buf_len)")
+ [ "$cur" -lt 0 ] && cur=0
+ [ "$cur" -gt "$len" ] && cur=$len
+ printf '\r\033[2K' >/dev/tty
+ printf '%s' "$buf" >/dev/tty
+ if [ "$cur" -lt "$len" ]; then
+ back=$(calc_sub_nonneg "$len" "$cur")
+ printf '\033[%sD' "$back" >/dev/tty
+ fi
+}
+
+read_char() {
+ dd bs=1 count=1 2>/dev/null </dev/tty
+}
+while :; do
+ ch=$(dd bs=1 count=1 2>/dev/null </dev/tty)
+ case $ch in
+ "$(printf '\r')"|"$(printf '\n')")
+ printf '\r\033[2K' >/dev/tty
+ handle_cmd "$buf"
+ buf=""
+ cur=0
+ ;;
+ "$(printf '\177')"|"$(printf '\010')")
+ if [ "$cur" -gt 0 ]; then
+ buf_backspace
+ render_line
+ fi
+ ;;
+ "$(printf '\003')")
+ printf '^C\r\n' >/dev/tty
+ exit 130
+ ;;
+ "$(printf '\001')") # Ctrl-A
+ buf_move_home
+ render_line
+ ;;
+ "$(printf '\005')") # Ctrl-E
+ buf_move_end
+ render_line
+ ;;
+ "$(printf '\033')") # escape sequence
+ ch2=$(read_char)
+ if [ "$ch2" = "[" ]; then
+ ch3=$(read_char)
+ case $ch3 in
+ "D") # left
+ buf_move_left
+ render_line
+ ;;
+ "C") # right
+ buf_move_right
+ render_line
+ ;;
+ "H") # home
+ buf_move_home
+ render_line
+ ;;
+ "F") # end
+ buf_move_end
+ render_line
+ ;;
+ "3") # delete
+ ch4=$(read_char)
+ if [ "$ch4" = "~" ]; then
+ buf_delete
+ render_line
+ fi
+ ;;
+ esac
+ fi
+ ;;
+ *)
+ buf_insert "$ch"
+ render_line
+ ;;
+ esac
+done 2>/dev/null &
+
+while IFS= read -r line; do
+ case $line in
+ 'PING '*)
+ printf 'PONG %s\r\n' "${line#* }"
+ ;;
+ '*'/*)
+ printf '%s\n' "$line" >"$out"
+ ;;
+ *)
+ printf '%s\n' "$line" >"$out"
+ ;;
+ esac
+done
diff --git a/ircf b/ircf
new file mode 100755
index 0000000..d292b25
--- /dev/null
+++ b/ircf
@@ -0,0 +1,556 @@
+#!/bin/sh
+
+: "${IRC_TZ:=UTC}"
+: "${IRC_HL_COLOR:=blue}"
+: "${IRC_CHAN_WIDTH:=12}"
+: "${IRC_NICK_WIDTH:=9}"
+: "${IRC_MSG_MAX:=0}"
+: "${IRC_TIME_FMT:=%H:%M}"
+: "${IRC_SHOW_TS:=1}"
+: "${IRC_PM_LABEL:=pm}"
+: "${IRC_HL_BOLD:=0}"
+: "${IRC_COLOR:=1}"
+: "${IRC_MAX_NICK:=0}"
+: "${IRC_MAX_CHAN:=0}"
+: "${IRC_TAB_WIDTH:=0}"
+: "${IRC_TRUNC_SUFFIX:=...}"
+: "${IRC_ACTION_COLOR:=}"
+: "${IRC_NOTICE_COLOR:=}"
+TZ="$IRC_TZ"
+export TZ
+
+awk -v hl_color="$IRC_HL_COLOR" -v chan_width="$IRC_CHAN_WIDTH" -v nick_width="$IRC_NICK_WIDTH" -v msg_max="$IRC_MSG_MAX" \
+ -v time_fmt="$IRC_TIME_FMT" -v show_ts="$IRC_SHOW_TS" -v pm_label="$IRC_PM_LABEL" -v hl_bold="$IRC_HL_BOLD" \
+ -v color_on="$IRC_COLOR" -v max_nick="$IRC_MAX_NICK" -v max_chan="$IRC_MAX_CHAN" -v tab_width="$IRC_TAB_WIDTH" \
+ -v trunc_suffix="$IRC_TRUNC_SUFFIX" -v action_color="$IRC_ACTION_COLOR" -v notice_color="$IRC_NOTICE_COLOR" '
+function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
+function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
+function trim(s) { return rtrim(ltrim(s)) }
+function nick_from_prefix(p, n) {
+ n = p
+ sub(/^:/, "", n)
+ sub(/!.*/, "", n)
+ return n
+}
+function raw_trailing(raw, tmp, i) {
+ tmp = raw
+ i = match(tmp, / :/)
+ if (i > 0) return substr(tmp, i + 2)
+ if (tmp ~ /^:/ && tmp ~ /^:[^ ]+ [^ ]+ :/) {
+ sub(/^:[^ ]+ [^ ]+ :/, "", tmp)
+ return tmp
+ }
+ return ""
+}
+function ts() { return strftime(time_fmt) }
+function is_channel(t) { return (t ~ /^#|^\+|^&|^!/) }
+function pad_right(s, width, pad, tabs, spaces) {
+ if (tab_width > 0) {
+ pad = width - length(s)
+ if (pad <= 0) return s
+ tabs = int(pad / tab_width)
+ spaces = pad - (tabs * tab_width)
+ return s sprintf("%" tabs "s", "") sprintf("%" spaces "s", "")
+ }
+ return sprintf("%-" width "." width "s", s)
+}
+function pad_left(s, width, pad) { return sprintf("%" width "." width "s", s) }
+function pad16(s, out) { return pad_right(s, chan_width) }
+function padnick(s, out) { return pad_left(s, nick_width) }
+function padnick_colored(raw, colored, w, pad) {
+ raw = substr(raw, 1, nick_width)
+ w = length(raw)
+ pad = sprintf("%" (nick_width - w) "s", "")
+ return pad colored
+}
+function quote_reason(s) {
+ if (s == "") return ""
+ gsub(/"/, "\\\"", s)
+ return "\"" s "\""
+}
+function fmt_action(msg, chan, left) {
+ left = pad16(trim_chan(chan))
+ if (show_ts) return sprintf("[%s] %s %s", ts(), left, msg)
+ return sprintf("%s %s", left, msg)
+}
+function fmt_action_line(chan, nick, text, left, who) {
+ left = pad16(trim_chan(chan))
+ who = padnick(trim_nick(nick))
+ if (show_ts) return sprintf("[%s] %s %s : %s", ts(), left, who, text)
+ return sprintf("%s %s : %s", left, who, text)
+}
+function color_code(name) {
+ if (!color_on) return ""
+ if (name == "black") return "\033[30m"
+ if (name == "red") return "\033[31m"
+ if (name == "green") return "\033[32m"
+ if (name == "yellow") return "\033[33m"
+ if (name == "blue") return "\033[34m"
+ if (name == "magenta") return "\033[35m"
+ if (name == "cyan") return "\033[36m"
+ if (name == "white") return "\033[37m"
+ if (name == "bright_black") return "\033[90m"
+ if (name == "bright_red") return "\033[91m"
+ if (name == "bright_green") return "\033[92m"
+ if (name == "bright_yellow") return "\033[93m"
+ if (name == "bright_blue") return "\033[94m"
+ if (name == "bright_magenta") return "\033[95m"
+ if (name == "bright_cyan") return "\033[96m"
+ if (name == "bright_white") return "\033[97m"
+ return "\033[34m"
+}
+function color_reset() { return (color_on ? "\033[0m" : "") }
+function bold_code() { return (color_on ? "\033[1m" : "") }
+function colorize(s, c, b, out) {
+ if (!color_on || c == "") return s
+ out = color_code(c)
+ if (b) out = out bold_code()
+ return out s color_reset()
+}
+function should_highlight(msg, n) {
+ if (mynick == "") return 0
+ n = tolower(mynick)
+ return (tolower(msg) ~ ("(^|[^[:alnum:]_])" n "([^[:alnum:]_]|$)"))
+}
+function label_for(code) {
+ if (code == "001") return "WELCOME"
+ if (code == "002") return "YOURHOST"
+ if (code == "003") return "CREATED"
+ if (code == "004") return "MYINFO"
+ if (code == "005") return "ISUPPORT"
+ if (code == "251") return "LUSER"
+ if (code == "252") return "LUSEROP"
+ if (code == "253") return "LUSERUNKNOWN"
+ if (code == "254") return "LUSERCHANNELS"
+ if (code == "255") return "LUSERME"
+ if (code == "256") return "ADMINME"
+ if (code == "257") return "ADMINLOC"
+ if (code == "258") return "ADMINLOC"
+ if (code == "259") return "ADMINEMAIL"
+ if (code == "263") return "TRYAGAIN"
+ if (code == "301") return "AWAY"
+ if (code == "302") return "USERHOST"
+ if (code == "303") return "ISON"
+ if (code == "305") return "UNAWAY"
+ if (code == "306") return "NOWAWAY"
+ if (code == "311") return "WHOIS"
+ if (code == "312") return "WHOIS"
+ if (code == "313") return "WHOIS"
+ if (code == "317") return "WHOIS"
+ if (code == "318") return "WHOIS"
+ if (code == "319") return "WHOIS"
+ if (code == "321") return "LIST"
+ if (code == "322") return "LIST"
+ if (code == "323") return "LIST"
+ if (code == "331") return "TOPIC"
+ if (code == "332") return "TOPIC"
+ if (code == "333") return "TOPIC"
+ if (code == "341") return "INVITE"
+ if (code == "351") return "VERSION"
+ if (code == "352") return "WHO"
+ if (code == "315") return "WHO"
+ if (code == "353") return "NAMES"
+ if (code == "366") return "NAMES"
+ if (code == "367") return "BANLIST"
+ if (code == "368") return "BANLIST"
+ if (code == "371") return "INFO"
+ if (code == "372") return "MOTD"
+ if (code == "374") return "ENDOFINFO"
+ if (code == "375") return "MOTD"
+ if (code == "376") return "MOTD"
+ if (code == "381") return "OPER"
+ if (code == "382") return "REHASH"
+ if (code == "391") return "TIME"
+ if (code == "401") return "NOSUCHNICK"
+ if (code == "402") return "NOSUCHSERVER"
+ if (code == "403") return "NOSUCHCHANNEL"
+ if (code == "404") return "CANNOTSEND"
+ if (code == "405") return "TOOMANYCHANNELS"
+ if (code == "406") return "WASNOSUCHNICK"
+ if (code == "407") return "TOOMANYTARGETS"
+ if (code == "409") return "NOORIGIN"
+ if (code == "411") return "NORECIPIENT"
+ if (code == "412") return "NOTEXTTOSEND"
+ if (code == "413") return "NOTOPLEVEL"
+ if (code == "414") return "WILDTOPLEVEL"
+ if (code == "421") return "UNKNOWNCOMMAND"
+ if (code == "422") return "NOMOTD"
+ if (code == "423") return "NOADMININFO"
+ if (code == "424") return "FILEERROR"
+ if (code == "431") return "NONICKNAMEGIVEN"
+ if (code == "432") return "ERRONEUSNICKNAME"
+ if (code == "433") return "NICKNAMEINUSE"
+ if (code == "436") return "NICKCOLLISION"
+ if (code == "437") return "UNAVAILRESOURCE"
+ if (code == "441") return "USERNOTINCHANNEL"
+ if (code == "442") return "NOTONCHANNEL"
+ if (code == "443") return "USERONCHANNEL"
+ if (code == "444") return "NOLOGIN"
+ if (code == "445") return "SUMMONDISABLED"
+ if (code == "446") return "USERSDISABLED"
+ if (code == "451") return "NOTREGISTERED"
+ if (code == "461") return "NEEDMOREPARAMS"
+ if (code == "462") return "ALREADYREGISTRED"
+ if (code == "463") return "NOPERMFORHOST"
+ if (code == "464") return "PASSWDMISMATCH"
+ if (code == "465") return "YOUREBANNED"
+ if (code == "467") return "KEYSET"
+ if (code == "471") return "CHANNELISFULL"
+ if (code == "472") return "UNKNOWNMODE"
+ if (code == "473") return "INVITEONLYCHAN"
+ if (code == "474") return "BANNEDFROMCHAN"
+ if (code == "475") return "BADCHANNELKEY"
+ if (code == "476") return "BADCHANMASK"
+ if (code == "477") return "NOCHANMODES"
+ if (code == "478") return "BANLISTFULL"
+ if (code == "481") return "NOPRIVILEGES"
+ if (code == "482") return "CHANOPRIVSNEEDED"
+ if (code == "483") return "CANTKILLSERVER"
+ if (code == "484") return "RESTRICTED"
+ if (code == "485") return "UNIQOPPRIVSNEEDED"
+ if (code == "491") return "NOOPERHOST"
+ if (code == "501") return "UMODEUNKNOWNFLAG"
+ if (code == "502") return "USERSDONTMATCH"
+ return ""
+}
+function parse(line, rest, i, part, n, tok) {
+ delete params
+ prefix = ""
+ cmd = ""
+ params_n = 0
+ trailing = ""
+ rest = line
+ if (rest ~ /^:/) {
+ i = index(rest, " ")
+ if (i > 0) {
+ prefix = substr(rest, 1, i - 1)
+ rest = substr(rest, i + 1)
+ } else {
+ prefix = rest
+ rest = ""
+ }
+ }
+ i = index(rest, " ")
+ if (i > 0) {
+ cmd = substr(rest, 1, i - 1)
+ rest = substr(rest, i + 1)
+ } else {
+ cmd = rest
+ rest = ""
+ }
+ rest = ltrim(rest)
+ if (rest ~ / :/) {
+ i = match(rest, / :/)
+ part = substr(rest, 1, i - 1)
+ trailing = substr(rest, i + 2)
+ } else if (rest ~ /^:/) {
+ trailing = substr(rest, 2)
+ part = ""
+ } else {
+ part = rest
+ }
+ part = trim(part)
+ if (part != "") {
+ n = split(part, tok, /[ ]+/)
+ for (i = 1; i <= n; i++) params[i] = tok[i]
+ params_n = n
+ } else {
+ params_n = 0
+ }
+}
+function fmt_numeric(code, s, target) {
+ target = (params_n >= 1 ? params[1] : "")
+ s = trailing
+ if (code == "005") {
+ s = ""
+ if (params_n > 1) {
+ for (i = 2; i <= params_n; i++) s = s (i > 2 ? " " : "") params[i]
+ }
+ if (trailing != "") s = s (s != "" ? " " : "") trailing
+ }
+ if (s == "") {
+ s = ""
+ if (params_n > 1) {
+ for (i = 2; i <= params_n; i++) s = s (i > 2 ? " " : "") params[i]
+ }
+ }
+ s = trim(s)
+ if (s == "") {
+ s = trim(raw_trailing(line))
+ }
+ if (s == "") return ""
+
+ if (code == "353") {
+ chan = (params_n >= 3 ? params[3] : "")
+ return sprintf("NAMES %s: %s", chan, s)
+ }
+ if (code == "366") {
+ chan = (params_n >= 2 ? params[2] : "")
+ return sprintf("NAMES: end %s", chan)
+ }
+ if (code == "332") {
+ chan = (params_n >= 2 ? params[2] : "")
+ return sprintf("TOPIC %s: %s", chan, s)
+ }
+ if (code == "331") {
+ chan = (params_n >= 2 ? params[2] : "")
+ return sprintf("TOPIC %s: %s", chan, s)
+ }
+ if (code == "333") {
+ chan = (params_n >= 2 ? params[2] : "")
+ return sprintf("TOPIC %s: %s", chan, s)
+ }
+ if (code == "322") {
+ chan = (params_n >= 2 ? params[2] : "")
+ users = (params_n >= 3 ? params[3] : "")
+ return sprintf("LIST %s (%s): %s", chan, users, s)
+ }
+ if (code == "321" || code == "323") {
+ return sprintf("LIST: %s", s)
+ }
+ if (code == "311") {
+ nick = (params_n >= 2 ? params[2] : "")
+ user = (params_n >= 3 ? params[3] : "")
+ host = (params_n >= 4 ? params[4] : "")
+ return sprintf("WHOIS %s: %s@%s %s", nick, user, host, s)
+ }
+ if (code == "312") {
+ nick = (params_n >= 2 ? params[2] : "")
+ server = (params_n >= 3 ? params[3] : "")
+ return sprintf("WHOIS %s: %s %s", nick, server, s)
+ }
+ if (code == "313") {
+ nick = (params_n >= 2 ? params[2] : "")
+ return sprintf("WHOIS %s: %s", nick, s)
+ }
+ if (code == "317") {
+ nick = (params_n >= 2 ? params[2] : "")
+ idle = (params_n >= 3 ? params[3] : "")
+ return sprintf("WHOIS %s: idle %ss %s", nick, idle, s)
+ }
+ if (code == "318") {
+ nick = (params_n >= 2 ? params[2] : "")
+ return sprintf("WHOIS %s: %s", nick, s)
+ }
+ if (code == "319") {
+ nick = (params_n >= 2 ? params[2] : "")
+ return sprintf("WHOIS %s: %s", nick, s)
+ }
+ if (code == "352") {
+ chan = (params_n >= 2 ? params[2] : "")
+ user = (params_n >= 3 ? params[3] : "")
+ host = (params_n >= 4 ? params[4] : "")
+ nick = (params_n >= 6 ? params[6] : "")
+ return sprintf("WHO %s: %s %s@%s %s", chan, nick, user, host, s)
+ }
+ if (code == "315") {
+ return sprintf("WHO: %s", s)
+ }
+ if (code == "341") {
+ nick = (params_n >= 2 ? params[2] : "")
+ chan = (params_n >= 3 ? params[3] : "")
+ return sprintf("INVITE: %s %s", nick, chan)
+ }
+ if (code == "372" || code == "375" || code == "376") {
+ return sprintf("MOTD: %s", s)
+ }
+
+ label = label_for(code)
+ if (code ~ /^[4-5][0-9][0-9]$/) {
+ return sprintf("ERROR: %s", s)
+ }
+ if (label != "") {
+ return sprintf("%s: %s", label, s)
+ }
+ return sprintf("INFO: %s", s)
+}
+function sanitize_line(s) {
+ if (s ~ /sh: out of range/) return ""
+ if (s ~ /^\[/) return s
+ if (s ~ /^\033\[/) return s
+ if (s ~ /^\)[^ ]+ quit /) {
+ sub(/^\)/, "", s)
+ return s
+ }
+ if (s ~ /^\)[^ ]+ (left|joined) /) sub(/^\)/, "", s)
+ if (s ~ /^\)[^ ]+ (left|joined) /) {
+ tmp = s
+ sub(/^\)/, "", tmp)
+ i = index(tmp, " ")
+ if (i > 0) {
+ nick = substr(tmp, 1, i - 1)
+ tmp = substr(tmp, i + 1)
+ i = index(tmp, " ")
+ if (i > 0) {
+ action = substr(tmp, 1, i - 1)
+ tmp = substr(tmp, i + 1)
+ i = index(tmp, " ")
+ if (i > 0) {
+ chan = substr(tmp, 1, i - 1)
+ reason = substr(tmp, i + 1)
+ } else {
+ chan = tmp
+ reason = ""
+ }
+ if (reason ~ /^\(.*\)$/) {
+ reason = substr(reason, 2, length(reason) - 2)
+ } else {
+ reason = ""
+ }
+ if (reason != "" && length(reason) > length(nick)) {
+ if (substr(reason, length(reason) - length(nick) + 1) == nick) {
+ nick = reason
+ }
+ }
+ s = nick " " action " " chan
+ if (reason != "") s = s " " reason
+ }
+ }
+ }
+ return s
+}
+function print_line(s) {
+ if (s == "") return
+ s = sanitize_line(s)
+ if (s != "") print s
+}
+function limit_with_suffix(s, max, suffix, keep) {
+ if (max <= 0 || length(s) <= max) return s
+ suffix = (trunc_suffix != "" ? trunc_suffix : "")
+ keep = max - length(suffix)
+ if (keep <= 0) return substr(s, 1, max)
+ return substr(s, 1, keep) suffix
+}
+function trim_nick(s) { return limit_with_suffix(s, max_nick) }
+function trim_chan(s) { return limit_with_suffix(s, max_chan) }
+function limit_msg(s) {
+ return limit_with_suffix(s, msg_max)
+}
+function extract_trailing(raw, cmd, tmp) {
+ tmp = raw
+ sub(/^:[^ ]+ /, "", tmp)
+ if (cmd != "") sub(cmd " [^ ]+ ?", "", tmp)
+ sub(/^:/, "", tmp)
+ return trim(tmp)
+}
+{
+ line = $0
+ gsub(/\r/, "", line)
+ if (line !~ /^:/ && line !~ /^PING /) {
+ next
+ }
+ if (line ~ /^PING /) {
+ print_line("** PING " substr(line, 6))
+ next
+ }
+ parse(line)
+ if (cmd ~ /^[0-9][0-9][0-9]$/) {
+ if (cmd == "001" && params_n >= 1) mynick = params[1]
+ out = fmt_numeric(cmd)
+ if (out != "") print_line(out)
+ next
+ }
+ if (cmd == "PRIVMSG") {
+ target = (params_n >= 1 ? params[1] : "")
+ if (trailing == "") trailing = trim(raw_trailing(line))
+ trailing = limit_msg(trailing)
+ sender = trim_nick(nick_from_prefix(prefix))
+ if (should_highlight(trailing) && sender != mynick) {
+ sender_colored = colorize(sender, hl_color, hl_bold)
+ sender = padnick_colored(sender, sender_colored)
+ } else {
+ sender = padnick(sender)
+ }
+ if (is_channel(target)) {
+ target = pad16(trim_chan(target))
+ if (show_ts) print_line(sprintf("[%s] %s %s : %s", ts(), target, sender, trailing))
+ else print_line(sprintf("%s %s : %s", target, sender, trailing))
+ } else {
+ pm = "[" pm_label "]"
+ if (mynick != "" && sender == mynick && target != "") pm = "[" pm_label " " target "]"
+ pm = pad16(trim_chan(pm))
+ if (show_ts) print_line(sprintf("[%s] %s %s : %s", ts(), pm, sender, trailing))
+ else print_line(sprintf("%s %s : %s", pm, sender, trailing))
+ }
+ next
+ }
+ if (cmd == "NOTICE") {
+ target = (params_n >= 1 ? params[1] : "")
+ if (trailing == "") trailing = trim(raw_trailing(line))
+ trailing = limit_msg(trailing)
+ out = sprintf("-%s- %s", trim_nick(nick_from_prefix(prefix)), trailing)
+ if (notice_color != "") out = colorize(out, notice_color, 0)
+ print_line(fmt_action(out, ""))
+ next
+ }
+ if (cmd == "JOIN") {
+ chan = (trailing != "" ? trailing : (params_n >= 1 ? params[1] : ""))
+ if (chan == "") chan = trim(raw_trailing(line))
+ out = sprintf("JOIN %s", trim_chan(chan))
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line(chan, nick_from_prefix(prefix), out)
+ next
+ }
+ if (cmd == "PART") {
+ chan = (params_n >= 1 ? params[1] : "")
+ if (chan == "") chan = trim(raw_trailing(line))
+ if (trailing == "") trailing = trim(raw_trailing(line))
+ trailing = limit_msg(trailing)
+ if (trailing != "" && trailing != trim_nick(nick_from_prefix(prefix))) {
+ out = sprintf("PART %s %s", trim_chan(chan), quote_reason(trailing))
+ } else {
+ out = sprintf("PART %s", trim_chan(chan))
+ }
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line(chan, nick_from_prefix(prefix), out)
+ next
+ }
+ if (cmd == "QUIT") {
+ msg = trailing
+ if (msg == "") msg = trim(raw_trailing(line))
+ msg = limit_msg(msg)
+ if (msg != "") out = sprintf("QUIT %s", quote_reason(msg))
+ else out = sprintf("QUIT")
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line("", nick_from_prefix(prefix), out)
+ next
+ }
+ if (cmd == "NICK") {
+ newnick = (trailing != "" ? trailing : (params_n >= 1 ? params[1] : ""))
+ if (mynick != "" && nick_from_prefix(prefix) == mynick) mynick = newnick
+ out = sprintf("NICK %s", trim_nick(newnick))
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line("", nick_from_prefix(prefix), out)
+ next
+ }
+ if (cmd == "TOPIC") {
+ chan = (params_n >= 1 ? params[1] : "")
+ out = sprintf("TOPIC %s : %s", trim_chan(chan), limit_msg(trailing))
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line(chan, nick_from_prefix(prefix), out)
+ next
+ }
+ if (cmd == "MODE") {
+ target = (params_n >= 1 ? params[1] : "")
+ mode = ""
+ if (params_n > 1) {
+ for (i = 2; i <= params_n; i++) mode = mode (i > 2 ? " " : "") params[i]
+ }
+ out = sprintf("MODE %s %s", trim_chan(target), mode)
+ if (action_color != "") out = colorize(out, action_color, 0)
+ print fmt_action_line(target, nick_from_prefix(prefix), out)
+ next
+ }
+
+ if (prefix != "") {
+ out = nick_from_prefix(prefix) " " cmd
+ } else {
+ out = cmd
+ }
+ if (params_n > 0) {
+ for (i = 1; i <= params_n; i++) out = out " " params[i]
+ }
+ if (trailing != "") out = out " :" trailing
+ print_line(out)
+}
+'