3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-23 19:19:31 +01:00

hybrid: inherit from proto/ts6, drastically reducing the module size

This commit is contained in:
James Lu 2016-04-07 18:11:13 -07:00
parent d585b60507
commit 3ae9155e78

View File

@ -9,17 +9,15 @@ import utils
from log import log
from classes import *
from ts6_common import TS6BaseProtocol
from ts6 import TS6Protocol
class HybridProtocol(TS6BaseProtocol):
class HybridProtocol(TS6Protocol):
def __init__(self, irc):
super(HybridProtocol, self).__init__(irc)
# This protocol module inherits from the TS6 protocol.
super().__init__(irc)
self.casemapping = 'ascii'
self.sidgen = utils.TS6SIDGenerator(self.irc)
self.uidgen = {}
self.caps = {}
self.hook_map = {'EOB': 'ENDBURST', 'TBURST': 'TOPIC'}
def connect(self):
@ -105,7 +103,6 @@ class HybridProtocol(TS6BaseProtocol):
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None,
manipulatable=False):
"""Spawns a client with nick <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 self.irc.sid
@ -146,119 +143,7 @@ class HybridProtocol(TS6BaseProtocol):
else:
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
def join(self, client, channel):
"""Joins a PyLink client to a channel."""
channel = utils.toLower(self.irc, channel)
if not self.irc.isInternalClient(client):
raise LookupError('No such PyLink client exists.')
self._send(client, "JOIN 0 %s +" % channel)
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
def invite(self, numeric, target, channel):
"""Sends an INVITE from a PyLink client.."""
if not self.irc.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send(numeric, 'INVITE %s %s %d' % (target, channel, int(time.time())))
def mode(self, numeric, target, modes, ts=None):
"""Sends mode changes from a PyLink client/server."""
# c <- :0UYAAAAAA TMODE 0 #a +o 0T4AAAAAC
# u <- :0UYAAAAAA MODE 0UYAAAAAA :-Facdefklnou
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
utils.applyModes(self.irc, target, modes)
modes = list(modes)
if utils.isChannel(target):
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
# TMODE:
# parameters: channelTS, channel, cmode changes, opt. cmode parameters...
# On output, at most ten cmode parameters should be sent; if there are more,
# multiple TMODE messages should be sent.
# TODO(dan): this may not be required on Hybrid?
while modes[:9]:
# Seriously, though. If you send more than 10 mode parameters in
# a line, charybdis will silently REJECT the entire command!
joinedmodes = utils.joinModes(modes = [m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']])
modes = modes[9:]
self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes))
else:
joinedmodes = utils.joinModes(modes)
self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
def ping(self, 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 self.irc.sid
target = target or self.irc.uplink
if not (target is None or source is None):
self._send(source, 'PING :%s' % (source,))
def handle_events(self, data):
"""Event handler for the Hybrid protocol.
This passes most commands to the various handle_ABCD() functions
elsewhere defined protocol modules, coersing various sender prefixes
from nicks and server names to UIDs and SIDs respectively,
whenever possible.
Commands sent without an explicit sender prefix will have them set to
the SID of the uplink server.
"""
data = data.split(" ")
try: # Message starts with a SID/UID prefix.
args = self.parseTS6Args(data)
sender = args[0]
command = args[1]
args = args[2:]
# If the sender isn't in UID format, try to convert it automatically.
# Unreal's protocol, for example, isn't quite consistent with this yet!
sender_server = self._getSid(sender)
if sender_server in self.irc.servers:
# Sender is a server when converted from name to SID.
numeric = sender_server
else:
# Sender is a user.
numeric = self._getNick(sender)
# parseTS6Args() will raise IndexError if the TS6 sender prefix is missing.
except IndexError:
# Raw command without an explicit sender; assume it's being sent by our uplink.
args = self.parseArgs(data)
numeric = self.irc.uplink
command = args[0]
args = args[1:]
try:
command = self.hook_map.get(command.upper(), command)
func = getattr(self, 'handle_'+command.lower())
except AttributeError: # unhandled command
# self._send(self.irc.sid, 'ERROR', 'Unknown Command')
print('Unknown command.\nOffending line: {}'.format(data))
exit(1)
else:
log.debug('(%s) Handling event %s - %s - %s', self.irc.name, numeric, command, str(args))
parsed_args = func(numeric, command, args)
if parsed_args is not None:
return [numeric, command, parsed_args]
# command handlers
def handle_pass(self, numeric, command, args):
# <- PASS $somepassword TS 6 42X
if args[0] != self.irc.serverdata['recvpass']:
raise ProtocolError("Error: RECVPASS from uplink does not match configuration!")
ver = args[args.index('TS') + 1]
if ver != '6':
raise ProtocolError("Remote protocol version {} is too old! Is this even TS6?".format())
numeric = args[3]
log.debug('(%s) Found uplink SID as %r', self.irc.name, numeric)
self.irc.servers[numeric] = IrcServer(None, 'unknown')
self.irc.uplink = numeric
def handle_capab(self, numeric, command, args):
# We only get a list of keywords here. Hybrid obviously assumes that
@ -272,16 +157,6 @@ class HybridProtocol(TS6BaseProtocol):
log.debug('(%s) self.irc.connected set!', self.irc.name)
self.irc.connected.set()
def handle_server(self, numeric, command, args):
# <- SERVER charybdis.midnight.vpn 1 :charybdis test server
sname = args[0].lower()
log.debug('(%s) Found uplink server name as %r', self.irc.name, sname)
self.irc.servers[self.irc.uplink].name = sname
self.irc.servers[self.irc.uplink].desc = args[2]
# According to the TS6 protocol documentation, we should send SVINFO
# when we get our uplink's SERVER command.
self.irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
def handle_uid(self, numeric, command, args):
"""Handles incoming UID commands (user introduction)."""
# <- :0UY UID dan 1 1451041551 +Facdeiklosuw ~ident localhost 127.0.0.1 0UYAAAAAB * :realname
@ -303,66 +178,6 @@ class HybridProtocol(TS6BaseProtocol):
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC_Operator'}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realname': realname, 'host': host, 'ident': ident, 'ip': ip}
def handle_join(self, numeric, command, args):
"""Handles incoming channel JOINs."""
# parameters: channelTS, channel, '+' (a plus sign)
# <- :0UYAAAAAF JOIN 0 #channel +
ts = int(args[0])
uid = numeric
channel = args[1]
if channel == '0':
# /join 0; part the user from all channels
oldchans = self.irc.users[uid].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, uid, oldchans)
for channel in oldchans:
self.irc.channels[channel].users.discard(uid)
self.irc.users[uid].channels.discard(channel)
else:
channel = utils.toLower(self.irc, args[1])
self.updateTS(channel, ts)
return {'channel': channel, 'users': [uid], 'modes':
self.irc.channels[channel].modes, 'ts': ts}
def handle_sjoin(self, numeric, command, args):
"""Handles incoming channel SJOINs."""
# parameters: channelTS, channel, modes, prefixed uids
# <- :0UY SJOIN 1451041566 #channel +nt :@0UYAAAAAB
ts = int(args[0])
uids = args[3].split()
users = {}
umodes = ['+']
modeprefixes = {v: k for k, v in self.irc.prefixmodes.items()}
for uid in uids:
modes = ''
while uid and uid[0] in modeprefixes:
modes += uid[0]
uid = uid[1:]
for mode in modes:
umodes[0] += modeprefixes[mode]
umodes.append(uid)
if args[1] == '0':
# /join 0; part the user from all channels
for uid in users:
oldchans = self.irc.users[uid].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, uid, oldchans)
for channel in oldchans:
self.irc.channels[channel].users.discard(uid)
self.irc.users[uid].channels.discard(channel)
# return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else:
channel = utils.toLower(self.irc, args[1])
self.updateTS(channel, ts)
parsedmodes = utils.parseModes(self.irc, channel, [args[2]])
utils.applyModes(self.irc, channel, parsedmodes)
parsedmodes = utils.parseModes(self.irc, channel, umodes)
utils.applyModes(self.irc, channel, parsedmodes)
# We send users and modes here because SJOIN and JOIN both use one hook,
# for simplicity's sake (with plugins).
return {'channel': channel, 'users': uids, 'modes':
self.irc.channels[channel].modes, 'ts': ts}
def handle_tburst(self, numeric, command, args):
"""Handles incoming topic burst (TBURST) commands."""
# <- :0UY TBURST 1459308205 #testchan 1459309379 dan!~d@localhost :sdf
@ -374,77 +189,6 @@ class HybridProtocol(TS6BaseProtocol):
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
def handle_whois(self, numeric, command, args):
"""Handles incoming WHOIS commands.
Note: The core of WHOIS handling is done by coreplugin.py
(IRCd-independent), and not here."""
# <- :0UYAAAAAH WHOIS 0T4AAAAAA :pylink
return {'target': args[0]}
def handle_ping(self, source, command, args):
"""Handles incoming PING commands."""
# PING:
# source: any
# parameters: origin, opt. destination server
# PONG:
# source: server
# parameters: origin, destination
# Sends a PING to the destination server, which will reply with a PONG. If the
# destination server parameter is not present, the server receiving the message
# must reply.
try:
destination = args[1]
except IndexError:
destination = self.irc.sid
if self.irc.isInternalServer(destination):
self._send(destination, 'PONG %s :%s' % (self.irc.servers[destination].name, source))
def handle_pong(self, source, command, args):
"""Handles incoming PONG commands."""
log.debug('(%s) Ping received from %s for %s.', self.irc.name, source, args[1])
if source == self.irc.uplink and args[1] == self.irc.sid:
log.debug('(%s) Set self.irc.lastping.', self.irc.name)
self.irc.lastping = time.time()
def handle_mode(self, numeric, command, args):
# <- :0UYAAAAAD MODE 0UYAAAAAD :-i
target = args[0]
modestrings = args[1:]
changedmodes = utils.parseModes(self.irc, target, modestrings)
utils.applyModes(self.irc, target, changedmodes)
# Call the OPERED UP hook if +o is being set.
if ('+o', None) in changedmodes:
otype = 'IRC Operator'
self.irc.callHooks([target, 'CLIENT_OPERED', {'text': otype}])
return {'target': target, 'modes': changedmodes}
def handle_tmode(self, numeric, command, args):
# <- :0UYAAAAAD TMODE 0 #a +h 0UYAAAAAD
channel = utils.toLower(self.irc, args[1])
oldobj = self.irc.channels[channel].deepcopy()
modes = args[2:]
changedmodes = utils.parseModes(self.irc, channel, modes)
utils.applyModes(self.irc, channel, changedmodes)
ts = int(args[0])
if ts > 0:
self.updateTS(channel, ts)
return {'target': channel, 'modes': changedmodes, 'ts': ts,
'oldchan': oldobj}
def handle_invite(self, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :0UYAAAAAA INVITE 0T4AAAAAA #testchan 1459316899
target = args[0]
channel = args[1].lower()
try:
ts = args[3]
except IndexError:
ts = int(time.time())
# We don't actually need to process this; it's just something plugins/hooks can use
return {'target': target, 'channel': channel, 'ts': ts}
def handle_svstag(self, numeric, command, args):
tag = args[2]
if tag in ['313']:
@ -455,12 +199,5 @@ class HybridProtocol(TS6BaseProtocol):
log.debug('(%s) end of burst received', self.irc.name)
return {}
# empty handlers
# TODO: there's a better way to do this
def handle_globops(self, numeric, command, args):
pass
def handle_svinfo(self, numeric, command, args):
pass
Class = HybridProtocol