From 1e302bac7d786056ac7224bd045d5f7706adb1eb Mon Sep 17 00:00:00 2001 From: Tobias Girstmair Date: Sat, 25 May 2024 22:14:57 +0200 Subject: [PATCH] provide a tool to highlight irc protocol messages this can be used as a bare-bones irc client like so: IRC_PASSWD=... ./ircpipe -n $NICK -p -s $BOUNCER_HOST | ./highlight | ts ('ts' being the timestamping tool from moreutils) the code for this looks a bit wonky, but that's kind of on purpose: while we first parse the message, we then not use this for actual output and instead slice the raw line and splice in control sequences, as to not accidentally modify the input; copy-pasting from it should yield the same bytes as it processed (modulo mIRC style color control chars). using bytes instead of native strings is necessary as to not break on non-utf8-encoded messages, something that regularly happens in practise. --- contrib/highlight | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 contrib/highlight diff --git a/contrib/highlight b/contrib/highlight new file mode 100755 index 0000000..a9ee030 --- /dev/null +++ b/contrib/highlight @@ -0,0 +1,106 @@ +#!/usr/bin/python3 + +import sys + +def parse(line): #-> [nick, user, host], command, arguments + prefix, params = b"", [] + command, arguments = None, [] + nick, user, host = None, None, None + + line = line.rstrip(b"\r\n") + # handle prefix: + if line.startswith(b":"): + prefix, line = line.split(b" ", 1) + # handle command and arguments: + while b" " in line and not line.startswith(b":"): + word, line = line.split(b" ", 1) + params += [word] + # handle final parameter: + params += [line.removeprefix(b":")] + + if params: + # note: command may also be a numeric reply + command, *arguments = params + + if b"!" in prefix and b"@" in prefix: + nick, rest = prefix.removeprefix(b":").split(b"!", 1) + user, host = rest.split(b"@", 1) + elif prefix: # server + host = prefix.removeprefix(b":") + + return [nick, user, host], command, arguments + +DIM = b"\033[0;2m" +BOLD = b"\033[0;1m" +RESET = b"\033[0m" +REV = b"\033[0;7m" + +def say(*args): + sys.stdout.buffer.write(b"".join(args)+b"\n") + sys.stdout.buffer.flush() + +def findall(needle, haystack): + if not needle: return [] + spans, offset, length = [], 0, len(needle) + nickchars = ( + br"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + br"abcdefghijklmnopqrstuvwxyz" + br"0123456789-[\]^_`{|}") + + while offset < len(haystack): + try: + offset = haystack.index(needle, offset) + # check for a word boundary before and after nick: + if (offset==0 or haystack[offset-1] not in nickchars) \ + and (offset+length==len(haystack) or haystack[offset+length] not in nickchars): + spans += [(offset, length)] + offset += length + except ValueError as e: + break # no more matches + return spans + +myself = None +for line in sys.stdin.buffer: + [nick, user, host], command, arguments = parse(line) + + if command in (b"PRIVMSG", b"NOTICE") and len(arguments) == 2 and nick: + chan, msg = arguments + nick_start = len(b":") + nick_end = nick_start+len(nick) + cmd_start = nick_end+len(b"!")+len(user)+len(b"@")+len(host)+len(b" ") + cmd_end = cmd_start+len(command) + chan_start = cmd_end+len(b" ") + chan_end = chan_start+len(chan) + msg_start = chan_end+len(b" ") + msg_end = msg_start+len(msg) + if line[msg_start] == ord(':'): + msg_start +=1 + msg_end +=1 + + hi_msg = line[msg_start:msg_end] + for s,l in reversed(findall(myself, hi_msg)): + hi_msg = hi_msg[:s] + REV + hi_msg[s:s+l] + RESET + hi_msg[s+l:] + + # maybe todo: + # - support mIRC highlighting (colors, bold, etc) + # https://modern.ircdocs.horse/formatting.html + # including \x01ACTION privmsg prefix + # - highlight own nick and/or user-def'd strings + + say( + DIM, line[:nick_start], + BOLD, line[nick_start:nick_end], + DIM, line[nick_end:chan_start], + BOLD, line[chan_start:chan_end], + DIM, line[chan_end:msg_start], + RESET, hi_msg, + REV, line[msg_end:].rstrip(b"\r\n"), + RESET) + elif command in (b"PING", b"PONG"): + pass # hide (could also do "JOIN", "QUIT", motd, names, ...) + else: + if command == b"001" and arguments: + myself = arguments[0] # store own username for later highlighting it + say( + DIM, line.rstrip(b"\r\n"), + RESET) -- 2.39.3