From 9f2d8a1b01cf7681665e337b4414d879ca955c57 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 Sep 2015 14:25:11 -0700 Subject: [PATCH] Document the sources of protocols/inspircd & ts6_common --- protocols/inspircd.py | 318 +++++++++++++++++++++++----------------- protocols/ts6.py | 2 +- protocols/ts6_common.py | 66 +++++---- 3 files changed, 220 insertions(+), 166 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index febdbb1..f70ed5c 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -4,32 +4,36 @@ import os import re from copy import copy +# Import hacks to access utils and classes... curdir = os.path.dirname(__file__) sys.path += [curdir, os.path.dirname(curdir)] import utils from log import log from classes import * +# Some functions are shared with the charybdis module... (ts6_common) from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \ removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \ handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \ handle_notice, _send, handle_part +# Set our case mapping (rfc1459 maps "\" and "|" together, for example". casemapping = 'rfc1459' # Raw commands sent from servers vary from protocol to protocol. Here, we map -# non-standard names to our hook handlers, so plugins get the information they need. - +# non-standard names to our hook handlers, so command handlers' outputs +# are called with the right hooks. hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE', 'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST', 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'} -def _send(irc, sid, msg): - irc.send(':%s %s' % (sid, msg)) - def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None): + """Spawns a client with nick on the given IRC connection. + + Note: No nick collision / valid nickname checks are done here; it is + up to plugins to make sure they don't introduce anything invalid.""" server = server or irc.sid if not utils.isInternalServer(irc, server): raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) @@ -56,9 +60,10 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() return u def joinClient(irc, client, channel): + """Joins an internal spawned client to a channel.""" # InspIRCd doesn't distinguish between burst joins and regular joins, # so what we're actually doing here is sending FJOIN from the server, - # on behalf of the clients that call it. + # on behalf of the clients that are joining. channel = utils.toLower(irc, channel) server = utils.isInternalClient(irc, client) if not server: @@ -73,6 +78,16 @@ def joinClient(irc, client, channel): irc.users[client].channels.add(channel) def sjoinServer(irc, server, channel, users, ts=None): + """Sends an SJOIN for a group of users to a channel. + + The sender should always be a Server ID (SID). TS is optional, and defaults + to the one we've stored in the channel state if not given. + is a list of (prefix mode, UID) pairs: + + Example uses: + sjoinServer(irc, '100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')]) + sjoinServer(irc, irc.sid, '#test', [('o', irc.pseudoclient.uid)]) + """ channel = utils.toLower(irc, channel) server = server or irc.sid assert users, "sjoinServer: No users sent?" @@ -92,7 +107,7 @@ def sjoinServer(irc, server, channel, users, ts=None): p.clear() log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts, time.strftime("%c", time.localtime(ts))) - # Strip out list-modes, they shouldn't be ever sent in FJOIN. + # Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules). modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']] uids = [] changedmodes = [] @@ -119,6 +134,15 @@ def sjoinServer(irc, server, channel, users, ts=None): irc.channels[channel].users.update(uids) def _operUp(irc, target, opertype=None): + """Opers a client up (internal function specific to InspIRCd). + + This should be called whenever user mode +o is set on anyone, because + InspIRCd requires a special command (OPERTYPE) to be sent in order to + recognize ANY non-burst oper ups. + + Plugins don't have to call this function themselves, but they can + set the opertype attribute of an IrcUser object (in irc.users), + and the change will be reflected here.""" userobj = irc.users[target] try: otype = opertype or userobj.opertype @@ -133,6 +157,7 @@ def _operUp(irc, target, opertype=None): _send(irc, target, 'OPERTYPE %s' % otype) def _sendModes(irc, numeric, target, modes, ts=None): + """Internal function to send modes from a PyLink client/server.""" # -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA # -> :9PYAAAAAA MODE 9PYAAAAAA -i+w log.debug('(%s) inspircd._sendModes: received %r for mode list', irc.name, modes) @@ -150,20 +175,18 @@ def _sendModes(irc, numeric, target, modes, ts=None): _send(irc, numeric, 'MODE %s %s' % (target, joinedmodes)) def modeClient(irc, numeric, target, modes, ts=None): - """ - - Sends modes from a PyLink PseudoClient. should be - a list of (mode, arg) tuples, in the format of utils.parseModes() output. + """ + Sends modes from a PyLink client. should be + a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output. """ if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _sendModes(irc, numeric, target, modes, ts=ts) def modeServer(irc, numeric, target, modes, ts=None): - """ - - Sends modes from a PyLink PseudoServer. should be - a list of (mode, arg) tuples, in the format of utils.parseModes() output. + """ + Sends modes from a PyLink server. should be + a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output. """ if not utils.isInternalServer(irc, numeric): raise LookupError('No such PyLink PseudoServer exists.') @@ -192,6 +215,7 @@ def killClient(irc, numeric, target, reason): # will send a QUIT from the target if the command succeeds. def topicServer(irc, numeric, target, text): + """Sends a burst topic from a PyLink server. This is usally used on burst.""" if not utils.isInternalServer(irc, numeric): raise LookupError('No such PyLink PseudoServer exists.') ts = int(time.time()) @@ -201,25 +225,19 @@ def topicServer(irc, numeric, target, text): irc.channels[target].topicset = True def inviteClient(irc, numeric, target, channel): - """ - - Invites to to from PyLink client .""" + """Sends an INVITE from a PyLink client..""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'INVITE %s %s' % (target, channel)) def knockClient(irc, numeric, target, text): - """ - - Knocks on with from PyLink client .""" + """Sends a KNOCK from a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'ENCAP * KNOCK %s :%s' % (target, text)) def updateClient(irc, numeric, field, text): - """ - - Changes the field of PyLink PseudoClient .""" + """Updates the ident, host, or realname of a PyLink client.""" field = field.upper() if field == 'IDENT': irc.users[numeric].ident = text @@ -234,6 +252,8 @@ def updateClient(irc, numeric, field, text): raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field) def pingServer(irc, source=None, target=None): + """Sends a PING to a target server. Periodic PINGs are sent to our uplink + automatically by the Irc() internals; plugins shouldn't have to use this.""" source = source or irc.sid target = target or irc.uplink if not (target is None or source is None): @@ -246,16 +266,46 @@ def numericServer(irc, source, numeric, text): "need for PyLink to send numerics directly yet.") def awayClient(irc, source, text): - """ - - Sends an AWAY message with text from PyLink client . - can be an empty string to unset AWAY status.""" + """Sends an AWAY message from a PyLink client. can be an empty string + to unset AWAY status.""" if text: _send(irc, source, 'AWAY %s :%s' % (int(time.time()), text)) else: _send(irc, source, 'AWAY') +def spawnServer(irc, name, sid=None, uplink=None, desc=None): + """Spawns a server off a PyLink server.""" + # -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver + uplink = uplink or irc.sid + name = name.lower() + # "desc" defaults to the configured server description. + desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc'] + if sid is None: # No sid given; generate one! + irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"]) + sid = irc.sidgen.next_sid() + assert len(sid) == 3, "Incorrect SID length" + if sid in irc.servers: + raise ValueError('A server with SID %r already exists!' % sid) + for server in irc.servers.values(): + if name == server.name: + raise ValueError('A server named %r already exists!' % name) + if not utils.isInternalServer(irc, uplink): + raise ValueError('Server %r is not a PyLink internal PseudoServer!' % uplink) + if not utils.isServerName(name): + raise ValueError('Invalid server name %r' % name) + _send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc)) + irc.servers[sid] = IrcServer(uplink, name, internal=True) + _send(irc, sid, 'ENDBURST') + return sid + +def squitServer(irc, source, target, text='No reason given'): + """SQUITs a PyLink server.""" + # -> :9PY SQUIT 9PZ :blah, blah + _send(irc, source, 'SQUIT %s :%s' % (target, text)) + handle_squit(irc, source, 'SQUIT', [target, text]) + def connect(irc): + """Initializes a connection to a server.""" ts = irc.start_ts f = irc.send @@ -268,84 +318,10 @@ def connect(irc): f(':%s BURST %s' % (irc.sid, ts)) f(':%s ENDBURST' % (irc.sid)) -def handle_ping(irc, source, command, args): - # <- :70M PING 70M 0AL - # -> :0AL PONG 0AL 70M - if utils.isInternalServer(irc, args[1]): - _send(irc, args[1], 'PONG %s %s' % (args[1], source)) - -def handle_pong(irc, source, command, args): - if source == irc.uplink and args[1] == irc.sid: - irc.lastping = time.time() - -def handle_fjoin(irc, servernumeric, command, args): - # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...> - channel = utils.toLower(irc, args[0]) - # InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID' - userlist = args[-1].split() - our_ts = irc.channels[channel].ts - their_ts = int(args[1]) - if their_ts < our_ts: - # Channel timestamp was reset on burst - log.debug('(%s) Setting channel TS of %s to %s from %s', - irc.name, channel, their_ts, our_ts) - irc.channels[channel].ts = their_ts - irc.channels[channel].modes.clear() - for p in irc.channels[channel].prefixmodes.values(): - p.clear() - modestring = args[2:-1] or args[2] - parsedmodes = utils.parseModes(irc, channel, modestring) - utils.applyModes(irc, channel, parsedmodes) - namelist = [] - for user in userlist: - modeprefix, user = user.split(',', 1) - namelist.append(user) - irc.users[user].channels.add(channel) - if their_ts <= our_ts: - utils.applyModes(irc, channel, [('+%s' % mode, user) for mode in modeprefix]) - irc.channels[channel].users.add(user) - return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts} - -def handle_uid(irc, numeric, command, args): - # :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname - uid, ts, nick, realhost, host, ident, ip = args[0:7] - realname = args[-1] - irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip) - parsedmodes = utils.parseModes(irc, uid, [args[8], args[9]]) - log.debug('Applying modes %s for %s', parsedmodes, uid) - utils.applyModes(irc, uid, parsedmodes) - irc.servers[numeric].users.add(uid) - return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} - -def handle_server(irc, numeric, command, args): - # SERVER is sent by our uplink or any other server to introduce others. - # <- :00A SERVER test.server * 1 00C :testing raw message syntax - # <- :70M SERVER millennium.overdrive.pw * 1 1ML :a relatively long period of time... (Fremont, California) - servername = args[0].lower() - sid = args[3] - sdesc = args[-1] - irc.servers[sid] = IrcServer(numeric, servername) - return {'name': servername, 'sid': args[3], 'text': sdesc} - -def handle_fmode(irc, numeric, command, args): - # <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD - channel = utils.toLower(irc, args[0]) - modes = args[2:] - changedmodes = utils.parseModes(irc, channel, modes) - utils.applyModes(irc, channel, changedmodes) - ts = int(args[1]) - return {'target': channel, 'modes': changedmodes, 'ts': ts} - - -def handle_idle(irc, numeric, command, args): - """Handle the IDLE command, sent between servers in remote WHOIS queries.""" - # <- :70MAAAAAA IDLE 1MLAAAAIG - # -> :1MLAAAAIG IDLE 70MAAAAAA 1433036797 319 - sourceuser = numeric - targetuser = args[0] - _send(irc, targetuser, 'IDLE %s %s 0' % (sourceuser, irc.users[targetuser].ts)) - def handle_events(irc, data): + """Event handler for the InspIRCd protocol. This passes most commands to + the various handle_ABCD() functions elsewhere in this module, but also + handles commands sent in the initial server linking phase.""" # Each server message looks something like this: # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE # : ... :final multi word argument @@ -425,35 +401,92 @@ def handle_events(irc, data): if parsed_args is not None: return [numeric, command, parsed_args] -def spawnServer(irc, name, sid=None, uplink=None, desc=None): - # -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver - uplink = uplink or irc.sid - name = name.lower() - desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc'] - if sid is None: # No sid given; generate one! - irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"]) - sid = irc.sidgen.next_sid() - assert len(sid) == 3, "Incorrect SID length" - if sid in irc.servers: - raise ValueError('A server with SID %r already exists!' % sid) - for server in irc.servers.values(): - if name == server.name: - raise ValueError('A server named %r already exists!' % name) - if not utils.isInternalServer(irc, uplink): - raise ValueError('Server %r is not a PyLink internal PseudoServer!' % uplink) - if not utils.isServerName(name): - raise ValueError('Invalid server name %r' % name) - _send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc)) - irc.servers[sid] = IrcServer(uplink, name, internal=True) - _send(irc, sid, 'ENDBURST') - return sid +def handle_ping(irc, source, command, args): + """Handles incoming PING commands, so we don't time out.""" + # <- :70M PING 70M 0AL + # -> :0AL PONG 0AL 70M + if utils.isInternalServer(irc, args[1]): + _send(irc, args[1], 'PONG %s %s' % (args[1], source)) -def squitServer(irc, source, target, text='No reason given'): - # -> :9PY SQUIT 9PZ :blah, blah - _send(irc, source, 'SQUIT %s :%s' % (target, text)) - handle_squit(irc, source, 'SQUIT', [target, text]) +def handle_pong(irc, source, command, args): + """Handles incoming PONG commands. This is used to keep track of whether + the uplink is alive by the Irc() internals - a server that fails to reply + to our PINGs eventually times out and is disconnected.""" + if source == irc.uplink and args[1] == irc.sid: + irc.lastping = time.time() + +def handle_fjoin(irc, servernumeric, command, args): + """Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN).""" + # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...> + channel = utils.toLower(irc, args[0]) + # InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID' + userlist = args[-1].split() + our_ts = irc.channels[channel].ts + their_ts = int(args[1]) + if their_ts < our_ts: + # Channel timestamp was reset on burst + log.debug('(%s) Setting channel TS of %s to %s from %s', + irc.name, channel, their_ts, our_ts) + irc.channels[channel].ts = their_ts + irc.channels[channel].modes.clear() + for p in irc.channels[channel].prefixmodes.values(): + p.clear() + modestring = args[2:-1] or args[2] + parsedmodes = utils.parseModes(irc, channel, modestring) + utils.applyModes(irc, channel, parsedmodes) + namelist = [] + for user in userlist: + modeprefix, user = user.split(',', 1) + namelist.append(user) + irc.users[user].channels.add(channel) + if their_ts <= our_ts: + utils.applyModes(irc, channel, [('+%s' % mode, user) for mode in modeprefix]) + irc.channels[channel].users.add(user) + return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts} + +def handle_uid(irc, numeric, command, args): + """Handles incoming UID commands (user introduction).""" + # :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname + uid, ts, nick, realhost, host, ident, ip = args[0:7] + realname = args[-1] + irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip) + parsedmodes = utils.parseModes(irc, uid, [args[8], args[9]]) + log.debug('Applying modes %s for %s', parsedmodes, uid) + utils.applyModes(irc, uid, parsedmodes) + irc.servers[numeric].users.add(uid) + return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} + +def handle_server(irc, numeric, command, args): + """Handles incoming SERVER commands (introduction of servers).""" + # SERVER is sent by our uplink or any other server to introduce others. + # <- :00A SERVER test.server * 1 00C :testing raw message syntax + # <- :70M SERVER millennium.overdrive.pw * 1 1ML :a relatively long period of time... (Fremont, California) + servername = args[0].lower() + sid = args[3] + sdesc = args[-1] + irc.servers[sid] = IrcServer(numeric, servername) + return {'name': servername, 'sid': args[3], 'text': sdesc} + +def handle_fmode(irc, numeric, command, args): + """Handles the FMODE command, used for channel mode changes.""" + # <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD + channel = utils.toLower(irc, args[0]) + modes = args[2:] + changedmodes = utils.parseModes(irc, channel, modes) + utils.applyModes(irc, channel, changedmodes) + ts = int(args[1]) + return {'target': channel, 'modes': changedmodes, 'ts': ts} + +def handle_idle(irc, numeric, command, args): + """Handles the IDLE command, sent between servers in remote WHOIS queries.""" + # <- :70MAAAAAA IDLE 1MLAAAAIG + # -> :1MLAAAAIG IDLE 70MAAAAAA 1433036797 319 + sourceuser = numeric + targetuser = args[0] + _send(irc, targetuser, 'IDLE %s %s 0' % (sourceuser, irc.users[targetuser].ts)) def handle_ftopic(irc, numeric, command, args): + """Handles incoming FTOPIC (sets topic on burst).""" # <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic channel = utils.toLower(irc, args[0]) ts = args[1] @@ -464,13 +497,19 @@ def handle_ftopic(irc, numeric, command, args): return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic} def handle_invite(irc, numeric, command, args): + """Handles incoming INVITEs.""" # <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0 target = args[0] channel = utils.toLower(irc, args[1]) - # We don't actually need to process this; it's just something plugins/hooks can use + # We don't actually need to process this; just send the hook so plugins can use it return {'target': target, 'channel': channel} def handle_encap(irc, numeric, command, args): + """Handles incoming encapsulated commands (ENCAP). Hook arguments + returned by this should have a parse_as field, that sets the correct + hook name for the message. + + For InspIRCd, the only ENCAP command we handle right now is KNOCK.""" # <- :70MAAAAAA ENCAP * KNOCK #blah :agsdfas # From charybdis TS6 docs: https://github.com/grawity/irc-docs/blob/03ba884a54f1cef2193cd62b6a86803d89c1ac41/server/ts6.txt @@ -490,6 +529,10 @@ def handle_encap(irc, numeric, command, args): 'text': text} def handle_opertype(irc, numeric, command, args): + """Handles incoming OPERTYPE, which is used to denote an oper up. + + This calls the internal hook PYLINK_CLIENT_OPERED, sets the internal + opertype of the client, and assumes setting user mode +o on the caller.""" # This is used by InspIRCd to denote an oper up; there is no MODE # command sent for it. # <- :70MAAAAAB OPERTYPE Network_Owner @@ -502,24 +545,29 @@ def handle_opertype(irc, numeric, command, args): return {'target': numeric, 'modes': omode} def handle_fident(irc, numeric, command, args): - # :70MAAAAAB FHOST test - # :70MAAAAAB FNAME :afdsafasf - # :70MAAAAAB FIDENT test + """Handles FIDENT, used for denoting ident changes.""" + # <- :70MAAAAAB FIDENT test irc.users[numeric].ident = newident = args[0] return {'target': numeric, 'newident': newident} def handle_fhost(irc, numeric, command, args): + """Handles FHOST, used for denoting hostname changes.""" + # <- :70MAAAAAB FIDENT some.host irc.users[numeric].host = newhost = args[0] return {'target': numeric, 'newhost': newhost} def handle_fname(irc, numeric, command, args): + """Handles FNAME, used for denoting real name/gecos changes.""" + # <- :70MAAAAAB FNAME :afdsafasf irc.users[numeric].realname = newgecos = args[0] return {'target': numeric, 'newgecos': newgecos} def handle_endburst(irc, numeric, command, args): + """ENDBURST handler; sends a hook with empty contents.""" return {} def handle_away(irc, numeric, command, args): + """Handles incoming AWAY messages.""" # <- :1MLAAAAIG AWAY 1439371390 :Auto-away try: ts = args[0] diff --git a/protocols/ts6.py b/protocols/ts6.py index 7dd49d4..3e0f852 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -9,7 +9,7 @@ import utils from log import log from classes import * -# Shared with inspircd module because the output is the same. +# Some functions are shared with the InspIRCd module (ts6_common) from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \ removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \ diff --git a/protocols/ts6_common.py b/protocols/ts6_common.py index 4af45f1..47666a6 100644 --- a/protocols/ts6_common.py +++ b/protocols/ts6_common.py @@ -7,12 +7,12 @@ import utils from log import log from classes import * -def _send(irc, sid, msg): - irc.send(':%s %s' % (sid, msg)) +def _send(irc, source, msg): + """Sends a TS6-style raw command from a source numeric to the IRC connection given.""" + irc.send(':%s %s' % (source, msg)) def parseArgs(args): - """ - Parses a string of RFC1459-style arguments split into a list, where ":" may + """Parses a string of RFC1459-style arguments split into a list, where ":" may be used for multi-word arguments that last until the end of a line. """ real_args = [] @@ -35,9 +35,7 @@ def parseArgs(args): return real_args def parseTS6Args(args): - """ - - Similar to parseArgs(), but stripping leading colons from the first argument + """Similar to parseArgs(), but stripping leading colons from the first argument of a line (usually the sender field).""" args = parseArgs(args) args[0] = args[0].split(':', 1)[1] @@ -46,9 +44,7 @@ def parseTS6Args(args): ### OUTGOING COMMANDS def _sendKick(irc, numeric, channel, target, reason=None): - """ - - Sends a kick from a PyLink PseudoClient.""" + """Internal function to send kicks from a PyLink client/server.""" channel = utils.toLower(irc, channel) if not reason: reason = 'No reason given' @@ -59,29 +55,26 @@ def _sendKick(irc, numeric, channel, target, reason=None): handle_part(irc, target, 'KICK', [channel]) def kickClient(irc, numeric, channel, target, reason=None): + """Sends a kick from a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _sendKick(irc, numeric, channel, target, reason=reason) def kickServer(irc, numeric, channel, target, reason=None): + """Sends a kick from a PyLink server.""" if not utils.isInternalServer(irc, numeric): raise LookupError('No such PyLink PseudoServer exists.') _sendKick(irc, numeric, channel, target, reason=reason) def nickClient(irc, numeric, newnick): - """ - - Changes the nick of a PyLink PseudoClient.""" + """Changes the nick of a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'NICK %s %s' % (newnick, int(time.time()))) irc.users[numeric].nick = newnick def removeClient(irc, numeric): - """ - - Removes a client from our internal databases, regardless - of whether it's one of our pseudoclients or not.""" + """Internal function to remove a client from our internal state.""" for c, v in irc.channels.copy().items(): v.removeuser(numeric) # Clear empty non-permanent channels. @@ -95,6 +88,7 @@ def removeClient(irc, numeric): irc.servers[sid].users.discard(numeric) def partClient(irc, client, channel, reason=None): + """Sends a part from a PyLink client.""" channel = utils.toLower(irc, channel) if not utils.isInternalClient(irc, client): log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', irc.name, client, channel) @@ -106,9 +100,7 @@ def partClient(irc, client, channel, reason=None): handle_part(irc, client, 'PART', [channel]) def quitClient(irc, numeric, reason): - """ - - Quits a PyLink PseudoClient.""" + """Quits a PyLink client.""" if utils.isInternalClient(irc, numeric): _send(irc, numeric, "QUIT :%s" % reason) removeClient(irc, numeric) @@ -116,22 +108,19 @@ def quitClient(irc, numeric, reason): raise LookupError("No such PyLink PseudoClient exists.") def messageClient(irc, numeric, target, text): - """ - - Sends PRIVMSG from PyLink client .""" + """Sends a PRIVMSG from a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'PRIVMSG %s :%s' % (target, text)) def noticeClient(irc, numeric, target, text): - """ - - Sends NOTICE from PyLink client .""" + """Sends a NOTICE from a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'NOTICE %s :%s' % (target, text)) def topicClient(irc, numeric, target, text): + """Sends a ROPIC from a PyLink client.""" if not utils.isInternalClient(irc, numeric): raise LookupError('No such PyLink PseudoClient exists.') _send(irc, numeric, 'TOPIC %s :%s' % (target, text)) @@ -141,6 +130,7 @@ def topicClient(irc, numeric, target, text): ### HANDLERS def handle_privmsg(irc, source, command, args): + """Handles incoming PRIVMSG/NOTICE.""" # <- :70MAAAAAA PRIVMSG #dev :afasfsa # <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa target = args[0] @@ -152,13 +142,18 @@ def handle_privmsg(irc, source, command, args): handle_notice = handle_privmsg def handle_kill(irc, source, command, args): + """Handles incoming KILLs.""" killed = args[0] + # Depending on whether the IRCd sends explicit QUIT messages for + # KILLed clients, the user may or may not have automatically been removed. + # If not, we have to assume that KILL = QUIT and remove them ourselves. data = irc.users.get(killed) if data: removeClient(irc, killed) return {'target': killed, 'text': args[1], 'userdata': data} def handle_kick(irc, source, command, args): + """Handles incoming KICKs.""" # :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason channel = utils.toLower(irc, args[0]) kicked = args[1] @@ -166,24 +161,29 @@ def handle_kick(irc, source, command, args): return {'channel': channel, 'target': kicked, 'text': args[2]} def handle_error(irc, numeric, command, args): + """Handles ERROR messages - these mean that our uplink has disconnected us!""" irc.connected.clear() - raise ProtocolError('Received an ERROR, killing!') + raise ProtocolError('Received an ERROR, disconnecting!') def handle_nick(irc, numeric, command, args): + """Handles incoming NICK changes.""" # <- :70MAAAAAA NICK GL-devel 1434744242 oldnick = irc.users[numeric].nick newnick = irc.users[numeric].nick = args[0] return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])} def handle_quit(irc, numeric, command, args): + """Handles incoming QUITs.""" # <- :1SRAAGB4T QUIT :Quit: quit message goes here removeClient(irc, numeric) return {'text': args[0]} def handle_save(irc, numeric, command, args): - # This is used to handle nick collisions. Here, the client Derp_ already exists, - # so trying to change nick to it will cause a nick collision. On InspIRCd, - # this will simply set the collided user's nick to its UID. + """Handles incoming SAVE messages, used to handle nick collisions.""" + # In this below example, the client Derp_ already exists, + # and trying to change someone's nick to it will cause a nick + # collision. On TS6 IRCds, this will simply set the collided user's + # nick to its UID. # <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_ # -> :0AL000001 NICK Derp_ 1433728673 @@ -194,6 +194,7 @@ def handle_save(irc, numeric, command, args): return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick} def handle_squit(irc, numeric, command, args): + """Handles incoming SQUITs (netsplits).""" # :70M SQUIT 1ML :Server quit by GL!gl@0::1 split_server = args[0] affected_users = [] @@ -214,6 +215,8 @@ def handle_squit(irc, numeric, command, args): return {'target': split_server, 'users': affected_users} def handle_mode(irc, numeric, command, args): + """Handles incoming user mode changes. For channel mode changes, + TMODE (TS6/charybdis) and FMODE (InspIRCd) are used instead.""" # In InspIRCd, MODE is used for setting user modes and # FMODE is used for channel modes: # <- :70MAAAAAA MODE 70MAAAAAA -i+xc @@ -224,6 +227,8 @@ def handle_mode(irc, numeric, command, args): return {'target': target, 'modes': changedmodes} def handle_topic(irc, numeric, command, args): + """Handles incoming TOPIC changes from clients. For topic bursts, + TB (TS6/charybdis) and FTOPIC (InspIRCd) are used instead.""" # <- :70MAAAAAA TOPIC #test :test channel = utils.toLower(irc, args[0]) topic = args[1] @@ -233,6 +238,7 @@ def handle_topic(irc, numeric, command, args): return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic} def handle_part(irc, source, command, args): + """Handles incoming PART commands.""" channels = utils.toLower(irc, args[0]).split(',') for channel in channels: # We should only get PART commands for channels that exist, right??