# IRC.AWK - A IRC library/toolkit for gawk, v1 # USE: $ gawk -f irc.awk -f [prog].awk # or by using '@include "irc.awk"' # or gawk -l irc -f [prog.awk] if irc.awk # is correctly placed in in AWKLIBPATH # AUTHOR: Philip K. # SHARE: public domain (CC0) # VERSION: 0.1, rather unstable # TODO: - more response/error checking # - better utility functions # - more auxiliary functions # - don't require external ticker ############# # CONSTANTS # ############# BEGIN { # regular expression to match the IRC message format, according to # RFC2812, 2.3.1 -- slightly more liberal than ABNF specification, # since irc.awk dies when an invalid message is read (TODO: rethink). __irc_re = "^(:(\\S+) )?([[:alpha:]]+|[[:digit:]]{3})( (([^:]+){0,14})(:(.*))?)?\\r$"; # responses codes as listed in RFC2812, 5.1 __irc_r_WELCOME = 001; __irc_r_YOURHOST = 002; __irc_r_CREATED = 003; __irc_r_MYINFO = 004; __irc_r_BOUNCE = 005; __irc_r_USERHOST = 302; __irc_r_ISON = 303; __irc_r_AWAY = 301; __irc_r_UNAWAY = 305; __irc_r_NOWAWAY = 306; __irc_r_WHOISUSER = 311; __irc_r_WHOISSERVER = 312; __irc_r_WHOISOPERATOR = 313; __irc_r_WHOISIDLE = 317; __irc_r_ENDOFWHOIS = 318; __irc_r_WHOISCHANNELS = 319; __irc_r_WHOWASUSER = 314; __irc_r_ENDOFWHOWAS = 369; __irc_r_LISTSTART = 321; __irc_r_LIST = 322; __irc_r_LISTEND = 323; __irc_r_UNIQOPIS = 325; __irc_r_CHANNELMODEIS = 324; __irc_r_NOTOPIC = 331; __irc_r_TOPIC = 332; __irc_r_INVITING = 341; __irc_r_SUMMONING = 342; __irc_r_INVITELIST = 346; __irc_r_ENDOFINVITELIST = 347; __irc_r_EXCEPTLIST = 348; __irc_r_ENDOFEXCEPTLIST = 349; __irc_r_VERSION = 351; __irc_r_WHOREPLY = 352; __irc_r_ENDOFWHO = 315; __irc_r_NAMREPLY = 353; __irc_r_ENDOFNAMES = 366; __irc_r_LINKS = 364; __irc_r_ENDOFLINKS = 365; __irc_r_BANLIST = 367; __irc_r_ENDOFBANLIST = 368; __irc_r_INFO = 371; __irc_r_ENDOFINFO = 374; __irc_r_MOTDSTART = 375; __irc_r_MOTD = 372; __irc_r_ENDOFMOTD = 376; __irc_r_YOUREOPER = 381; __irc_r_REHASHING = 382; __irc_r_YOURESERVICE = 383; __irc_r_TIME = 391; __irc_r_USERSSTART = 392; __irc_r_USERS = 393; __irc_r_ENDOFUSERS = 394; __irc_r_NOUSERS = 395; __irc_r_TRACELINK = 200; __irc_r_TRACECONNECTING = 201; __irc_r_TRACEHANDSHAKE = 202; __irc_r_TRACEUNKNOWN = 203; __irc_r_TRACEOPERATOR = 204; __irc_r_TRACEUSER = 205; __irc_r_TRACESERVER = 206; __irc_r_TRACESERVICE = 207; __irc_r_TRACENEWTYPE = 208; __irc_r_TRACECLASS = 209; __irc_r_TRACERECONNECT = 210; __irc_r_TRACELOG = 261; __irc_r_TRACEEND = 262; __irc_r_STATSLINKINFO = 211; __irc_r_STATSCOMMANDS = 212; __irc_r_ENDOFSTATS = 219; __irc_r_STATSUPTIME = 242; __irc_r_STATSOLINE = 243; __irc_r_UMODEIS = 221; __irc_r_SERVLIST = 234; __irc_r_SERVLISTEND = 235; __irc_r_LUSERCLIENT = 251; __irc_r_LUSEROP = 252; __irc_r_LUSERUNKNOWN = 253; __irc_r_LUSERCHANNELS = 254; __irc_r_LUSERME = 255; __irc_r_ADMINME = 256; __irc_r_ADMINLOC1 = 257; __irc_r_ADMINLOC2 = 258; __irc_r_ADMINEMAIL = 259; __irc_r_TRYAGAIN = 263; # error replies as listed in RFC2812, 5.2 __irc_e_NOSUCHNICK = 401; __irc_e_NOSUCHSERVER = 402; __irc_e_NOSUCHCHANNEL = 403; __irc_e_CANNOTSENDTOCHAN = 404; __irc_e_TOOMANYCHANNELS = 405; __irc_e_WASNOSUCHNICK = 406; __irc_e_TOOMANYTARGETS = 407; __irc_e_NOSUCHSERVICE = 408; __irc_e_NOORIGIN = 409; __irc_e_NORECIPIENT = 411; __irc_e_NOTEXTTOSEND = 412; __irc_e_NOTOPLEVEL = 413; __irc_e_WILDTOPLEVEL = 414; __irc_e_BADMASK = 415; __irc_e_UNKNOWNCOMMAND = 421; __irc_e_NOMOTD = 422; __irc_e_NOADMININFO = 423; __irc_e_FILEERROR = 424; __irc_e_NONICKNAMEGIVEN = 431; __irc_e_ERRONEUSNICKNAME = 432; __irc_e_NICKNAMEINUSE = 433; __irc_e_NICKCOLLISION = 436; __irc_e_UNAVAILRESOURCE = 437; __irc_e_USERNOTINCHANNEL = 441; __irc_e_NOTONCHANNEL = 442; __irc_e_USERONCHANNEL = 443; __irc_e_NOLOGIN = 444; __irc_e_SUMMONDISABLED = 445; __irc_e_USERSDISABLED = 446; __irc_e_NOTREGISTERED = 451; __irc_e_NEEDMOREPARAMS = 461; __irc_e_ALREADYREGISTRED = 462; __irc_e_NOPERMFORHOST = 463; __irc_e_PASSWDMISMATCH = 464; __irc_e_YOUREBANNEDCREEP = 465; __irc_e_YOUWILLBEBANNED = 466; __irc_e_KEYSET = 467; __irc_e_CHANNELISFULL = 471; __irc_e_UNKNOWNMODE = 472; __irc_e_INVITEONLYCHAN = 473; __irc_e_BANNEDFROMCHAN = 474; __irc_e_BADCHANNELKEY = 475; __irc_e_BADCHANMASK = 476; __irc_e_NOCHANMODES = 477; __irc_e_BANLISTFULL = 478; __irc_e_NOPRIVILEGES = 481; __irc_e_CHANOPRIVSNEEDED = 482; __irc_e_CANTKILLSERVER = 483; __irc_e_RESTRICTED = 484; __irc_e_UNIQOPPRIVSNEEDED = 485; __irc_e_NOOPERHOST = 491; __irc_e_UMODEUNKNOWNFLAG = 501; __irc_e_USERSDONTMATCH = 502; # array of regular expression describing valid RFC 2812 IRC # commands, excluding commands only the irc.awk should use __irc_commands["NICK"] = /^[^ @\n\r\0]$/; # 3.1.2 __irc_commands["OPER"] = /^[^ @\n\r\0]( [^ ]+)?$/; # 3.1.4 __irc_commands["MODE"] = /^[^ @\n\r\0] [+-][iwoOr]$/; # 3.1.5 __irc_commands["JOIN"] = /^[^\0\a\r\n ,:]( [^ ]+)?$/; # 3.2.1 __irc_commands["PART"] = /^[^\0\a\r\n ,:]/; # 3.2.2 __irc_commands["TOPIC"] = /^[^\0\a\r\n ,:] /; # 3.2.4 __irc_commands["NAMES"] = /^[^\0\a\r\n ,:]( [^ ]+)?$/; # 3.2.5 __irc_commands["INVITE"] = /^[^ @\n\r\0] [^\0\a\r\n ,:]$/; # 3.2.7 __irc_commands["KICK"] = /^[^ @\n\r\0] [^\0\a\r\n ,:]$/; # 3.2.8 __irc_commands["PRIVMSG"] = /^([^ @\n\r\0]|[^\0\a\r\n ,:])/; # 3.3.1 __irc_commands["NOTICE"] = /^([^ @\n\r\0]|[^\0\a\r\n ,:])/; # 3.3.2 __irc_commands["STATS"] = /^[lmou] /; # 3.4.4 __irc_commands["WHOIS"] = /^[^ @\n\r\0]/; # 3.6.2 __irc_commands["WHOWAS"] = /^[^ @\n\r\0]/; # 3.6.2 __irc_commands["KILL"] = /^[^ @\n\r\0]/; # 3.7.1 } ###################### # INTERNAL FUNCTIONS # ###################### # generic function to send and validate a message function __irc_write(cmd, msg, dest, nocheck) { if (msg) msg = ":" msg msg = dest " " msg; if (!nocheck && __irc_commands[cmd] && msg !~ __irc_commands[cmd]) return -1; printf("%s %s\r\n", cmd, msg) |& __irc_conn; } # internal function to parse connection input function __irc_read() { __irc_timeout = 0; do { if ((__irc_conn |& getline __irc_msg_raw) <= 0) return; # __irc_parts: # - prefix, sans colon (eg. m@m.net) # - type (eg. PING, PRIVMSG, 391, etc.) # - destination (eg. #chat, user_name32, etc.) # - message, sans colon match(__irc_msg_raw, __irc_re, __irc_raw_parts); # "ignore" irrelevant groups if (__irc_parts[0] = __irc_raw_parts[0]) { __irc_parts[1] = __irc_raw_parts[2] __irc_parts[2] = __irc_raw_parts[3] __irc_parts[3] = __irc_raw_parts[5] __irc_parts[4] = __irc_raw_parts[8] } # line MUST much the form of an IRC response if (!__irc_parts[0]) { gsub(/\r$/, "", __irc_msg_raw) printf("malformed network input: \"%s\"\n", __irc_msg_raw) > "/dev/stderr"; __irc_noquit = 1 exit 1 } __irc = toupper(__irc_parts[2]); } while (__irc_ignore_cmd[__irc] || __irc_ignore_src[__irc_parts[3]]); } # maintain and update irc_rooms array (2d: name -> key -> value) function __irc_join_setup(chan) { do { __irc_read(); if (__irc == __irc_e_BANNEDFROMCHAN) return -1; } while (__irc != __irc_r_ENDOFNAMES); irc_rooms[chan]["topic"] = __irc_parts[4]; } # extract nickname from message prefix function __irc_trimname(name) { sub(/!.*$/, "", name) return name } ################## # USER FUNCTIONS # ################## # initialize IRC connection to a server function irc_setup(host, port, nick, autojoin, user, realname, passwd) { if (__irc_conn) return 0; if (!port) port = "6667"; if (port == "ssl") port = "6697"; __irc_conn = sprintf("/inet/tcp/0/%s/%d", host, port); if (passwd) __irc_write("PASS", "", passwd); __irc_write("NICK", "", nick); if (!user) user = nick; if (!realname) realname = user; __irc_write("USER", realname, user " -1 *"); __irc_nick = nick; __irc_user = user; do { __irc_read(); if (__irc == __irc_e_NICKNAMEINUSE || __irc == __irc_e_NICKCOLLISION || __irc == __irc_e_UNAVAILRESOURCE || __irc == __irc_e_RESTRICTED || __irc == __irc_e_ALREADYREGISTRED) { print __irc > "/dev/stderr" __irc_noquit = 1 exit 1 } } while (__irc != __irc_r_ENDOFMOTD); if (irc_autojoin_invite = (autojoin ~ /^!/)) autojoin = gensub(/^!/, "", "g", autojoin); split(autojoin, __irc_autojoin, " ") for (i in __irc_autojoin) irc_join(__irc_autojoin[i]); } # function to join a channel, possibly with password # # shoudln't be used during operation, since other messages might be # missed/ignored when doing so. function irc_join(chan, passwd) { __irc_write("JOIN " (passwd ? chan " " passwd : chan)); __irc_last_chan = chan; return __irc_join_setup(chan); } # function to write a message to user to channel function irc_send(to, msg) { __irc_write("PRIVMSG", msg, to); } # utility function to write a message to the last channel a message was # received from function irc_msg(msg) { irc_send(TO, msg); } # quit current connection function irc_quit(quitmsg, msg, len) { if (!quitmsg) { # chosen phrases from # https://en.wikipedia.org/wiki/List_of_Latin_phrases_(full) msg[0] = "mors mihi lucrum"; msg[1] = "mors certa, hora incerta"; msg[2] = "morte magis metuenda senectus"; msg[3] = "morior invictus"; msg[4] = "nil igitur mors est ad nos"; msg[5] = "malo mori quam foedari"; msg[6] = "nil nisi bonum de mortuis dicere"; len = length(msg); srand(); quitmsg = msg[int(rand() * 1000 * len) % len]; } __irc_write("QUIT", quitmsg) close(__irc_conn); } #################### # INPUT PROCESSING # #################### __irc_conn { # start processing after connected __irc_read(); TO = __irc_parts[3]; WHO = __irc_trimname(__irc_parts[-1]); MSG = ""; # regular message EMSG = ""; # event message NOTICE = ""; INVITE = ""; JOIN = PART = QUIT = KICK = 0; } # auto responses __irc == "PING" { __irc_write("PONG", __irc_parts[4]); next; } # events __irc == "PRIVMSG" { MSG = __irc_parts[4]; } __irc == "NOTICE" { NOTICE = __irc_parts[4]; } __irc == "JOIN" { JOIN = 1; TO = __irc_parts[4]; } __irc == "PART" { PART = 1; EMSG = irc_parts[4]; } __irc == "QUIT" { QUIT = 1; EMSG = __irc_parts[4]; } __irc == "KICK" { KICK = 1; EMSG = __irc_parts[4]; } __irc == "INVITE" { if (__irc_parts[4]) INVITE = __irc_parts[4] else INVITE = gensub(/^\w+ /, "", "g", __irc_parts[3]); if (irc_autojoin_invite) irc_join(INVITE); } ################################# # AUXILIARY CONDITIONAL MATCHES # ################################# function READ(pat, to) { return MSG && (MSG ~ pat) && (TO ~ to); } function FROM(pat, to) { return MSG && (WHO ~ pat) && (TO ~ to); } function JOINED(pat, to) { return JOIN && (!pat || WHO ~ pat) && (!to || TO ~ to); } function MENTIONED(to) { return MSG && (MSG ~ ("^" __irc_nick ":")) && (TO ~ to); } ############### # CLEANING UP # ############### END { if (!__irc_noquit && __irc_conn) irc_quit(); }