mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-01 01:09:22 +01:00
Merge branch 'wip/split-utils' into devel
This commit is contained in:
commit
bd0874a484
168
classes.py
168
classes.py
@ -19,6 +19,7 @@ from copy import deepcopy
|
||||
from log import *
|
||||
import world
|
||||
import utils
|
||||
import structures
|
||||
|
||||
### Exceptions
|
||||
|
||||
@ -113,7 +114,7 @@ class Irc():
|
||||
internal=True, desc=self.serverdata.get('serverdesc')
|
||||
or self.botdata['serverdesc'])}
|
||||
self.users = {}
|
||||
self.channels = utils.KeyedDefaultdict(IrcChannel)
|
||||
self.channels = structures.KeyedDefaultdict(IrcChannel)
|
||||
|
||||
# This sets the list of supported channel and user modes: the default
|
||||
# RFC1459 modes are implied. Named modes are used here to make
|
||||
@ -451,7 +452,7 @@ class Irc():
|
||||
def __repr__(self):
|
||||
return "<classes.Irc object for %r>" % self.name
|
||||
|
||||
### Utility functions
|
||||
### General utility functions
|
||||
def callCommand(self, source, text):
|
||||
"""
|
||||
Calls a PyLink bot command. source is the caller's UID, and text is the
|
||||
@ -490,6 +491,169 @@ class Irc():
|
||||
"""Replies to the last caller in the right context (channel or PM)."""
|
||||
self.msg(self.called_by, text, notice=notice, source=source)
|
||||
|
||||
def parseModes(self, target, args):
|
||||
"""Parses a modestring list into a list of (mode, argument) tuples.
|
||||
['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')]
|
||||
"""
|
||||
# http://www.irc.org/tech_docs/005.html
|
||||
# A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||
# B = Mode that changes a setting and always has a parameter.
|
||||
# C = Mode that changes a setting and only has a parameter when set.
|
||||
# D = Mode that changes a setting and never has a parameter.
|
||||
assert args, 'No valid modes were supplied!'
|
||||
usermodes = not utils.isChannel(target)
|
||||
prefix = ''
|
||||
modestring = args[0]
|
||||
args = args[1:]
|
||||
if usermodes:
|
||||
log.debug('(%s) Using self.umodes for this query: %s', self.name, self.umodes)
|
||||
|
||||
if target not in self.users:
|
||||
log.warning('(%s) Possible desync! Mode target %s is not in the users index.', self.name, target)
|
||||
return [] # Return an empty mode list
|
||||
|
||||
supported_modes = self.umodes
|
||||
oldmodes = self.users[target].modes
|
||||
else:
|
||||
log.debug('(%s) Using self.cmodes for this query: %s', self.name, self.cmodes)
|
||||
|
||||
if target not in self.channels:
|
||||
log.warning('(%s) Possible desync! Mode target %s is not in the channels index.', self.name, target)
|
||||
return []
|
||||
|
||||
supported_modes = self.cmodes
|
||||
oldmodes = self.channels[target].modes
|
||||
res = []
|
||||
for mode in modestring:
|
||||
if mode in '+-':
|
||||
prefix = mode
|
||||
else:
|
||||
if not prefix:
|
||||
prefix = '+'
|
||||
arg = None
|
||||
log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
|
||||
try:
|
||||
if mode in (supported_modes['*A'] + supported_modes['*B']):
|
||||
# Must have parameter.
|
||||
log.debug('Mode %s: This mode must have parameter.', mode)
|
||||
arg = args.pop(0)
|
||||
if prefix == '-' and mode in supported_modes['*B'] and arg == '*':
|
||||
# Charybdis allows unsetting +k without actually
|
||||
# knowing the key by faking the argument when unsetting
|
||||
# as a single "*".
|
||||
# We'd need to know the real argument of +k for us to
|
||||
# be able to unset the mode.
|
||||
oldargs = [m[1] for m in oldmodes if m[0] == mode]
|
||||
if oldargs:
|
||||
# Set the arg to the old one on the channel.
|
||||
arg = oldargs[0]
|
||||
log.debug("Mode %s: coersing argument of '*' to %r.", mode, arg)
|
||||
elif mode in self.prefixmodes and not usermodes:
|
||||
# We're setting a prefix mode on someone (e.g. +o user1)
|
||||
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
||||
arg = args.pop(0)
|
||||
# Convert nicks to UIDs implicitly; most IRCds will want
|
||||
# this already.
|
||||
arg = self.nickToUid(arg) or arg
|
||||
if arg not in self.users: # Target doesn't exist, skip it.
|
||||
log.debug('(%s) Skipping setting mode "%s %s"; the '
|
||||
'target doesn\'t seem to exist!', self.name,
|
||||
mode, arg)
|
||||
continue
|
||||
elif prefix == '+' and mode in supported_modes['*C']:
|
||||
# Only has parameter when setting.
|
||||
log.debug('Mode %s: Only has parameter when setting.', mode)
|
||||
arg = args.pop(0)
|
||||
except IndexError:
|
||||
log.warning('(%s/%s) Error while parsing mode %r: mode requires an '
|
||||
'argument but none was found. (modestring: %r)',
|
||||
self.name, target, mode, modestring)
|
||||
continue # Skip this mode; don't error out completely.
|
||||
res.append((prefix + mode, arg))
|
||||
return res
|
||||
|
||||
def applyModes(self, target, changedmodes):
|
||||
"""Takes a list of parsed IRC modes, and applies them on the given target.
|
||||
|
||||
The target can be either a channel or a user; this is handled automatically."""
|
||||
usermodes = not utils.isChannel(target)
|
||||
log.debug('(%s) Using usermodes for this query? %s', self.name, usermodes)
|
||||
|
||||
try:
|
||||
if usermodes:
|
||||
old_modelist = self.users[target].modes
|
||||
supported_modes = self.umodes
|
||||
else:
|
||||
old_modelist = self.channels[target].modes
|
||||
supported_modes = self.cmodes
|
||||
except KeyError:
|
||||
log.warning('(%s) Possible desync? Mode target %s is unknown.', self.name, target)
|
||||
return
|
||||
|
||||
modelist = set(old_modelist)
|
||||
log.debug('(%s) Applying modes %r on %s (initial modelist: %s)', self.name, changedmodes, target, modelist)
|
||||
for mode in changedmodes:
|
||||
# Chop off the +/- part that parseModes gives; it's meaningless for a mode list.
|
||||
try:
|
||||
real_mode = (mode[0][1], mode[1])
|
||||
except IndexError:
|
||||
real_mode = mode
|
||||
|
||||
if not usermodes:
|
||||
# We only handle +qaohv for now. Iterate over every supported mode:
|
||||
# if the IRCd supports this mode and it is the one being set, add/remove
|
||||
# the person from the corresponding prefix mode list (e.g. c.prefixmodes['op']
|
||||
# for ops).
|
||||
for pmode, pmodelist in self.channels[target].prefixmodes.items():
|
||||
if pmode in self.cmodes and real_mode[0] == self.cmodes[pmode]:
|
||||
log.debug('(%s) Initial prefixmodes list: %s', self.name, pmodelist)
|
||||
if mode[0][0] == '+':
|
||||
pmodelist.add(mode[1])
|
||||
else:
|
||||
pmodelist.discard(mode[1])
|
||||
|
||||
log.debug('(%s) Final prefixmodes list: %s', self.name, pmodelist)
|
||||
|
||||
if real_mode[0] in self.prefixmodes:
|
||||
# Don't add prefix modes to IrcChannel.modes; they belong in the
|
||||
# prefixmodes mapping handled above.
|
||||
log.debug('(%s) Not adding mode %s to IrcChannel.modes because '
|
||||
'it\'s a prefix mode.', self.name, str(mode))
|
||||
continue
|
||||
|
||||
if mode[0][0] == '+':
|
||||
# We're adding a mode
|
||||
existing = [m for m in modelist if m[0] == real_mode[0] and m[1] != real_mode[1]]
|
||||
if existing and real_mode[1] and real_mode[0] not in self.cmodes['*A']:
|
||||
# The mode we're setting takes a parameter, but is not a list mode (like +beI).
|
||||
# Therefore, only one version of it can exist at a time, and we must remove
|
||||
# any old modepairs using the same letter. Otherwise, we'll get duplicates when,
|
||||
# for example, someone sets mode "+l 30" on a channel already set "+l 25".
|
||||
log.debug('(%s) Old modes for mode %r exist on %s, removing them: %s',
|
||||
self.name, real_mode, target, str(existing))
|
||||
[modelist.discard(oldmode) for oldmode in existing]
|
||||
modelist.add(real_mode)
|
||||
log.debug('(%s) Adding mode %r on %s', self.name, real_mode, target)
|
||||
else:
|
||||
log.debug('(%s) Removing mode %r on %s', self.name, real_mode, target)
|
||||
# We're removing a mode
|
||||
if real_mode[1] is None:
|
||||
# We're removing a mode that only takes arguments when setting.
|
||||
# Remove all mode entries that use the same letter as the one
|
||||
# we're unsetting.
|
||||
for oldmode in modelist.copy():
|
||||
if oldmode[0] == real_mode[0]:
|
||||
modelist.discard(oldmode)
|
||||
else:
|
||||
# Swap the - for a + and then remove it from the list.
|
||||
modelist.discard(real_mode)
|
||||
log.debug('(%s) Final modelist: %s', self.name, modelist)
|
||||
if usermodes:
|
||||
self.users[target].modes = modelist
|
||||
else:
|
||||
self.channels[target].modes = modelist
|
||||
|
||||
### State checking functions
|
||||
def nickToUid(self, nick):
|
||||
"""Looks up the UID of a user with the given nick, if one is present."""
|
||||
nick = utils.toLower(self, nick)
|
||||
|
@ -9,7 +9,7 @@ import utils
|
||||
from log import log
|
||||
|
||||
from classes import *
|
||||
from ts6 import TS6Protocol
|
||||
from ts6 import *
|
||||
|
||||
class HybridProtocol(TS6Protocol):
|
||||
def __init__(self, irc):
|
||||
@ -92,25 +92,26 @@ class HybridProtocol(TS6Protocol):
|
||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
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.
|
||||
"""
|
||||
Spawns a new client with the given options.
|
||||
|
||||
Note: No nick collision / valid nickname checks are done here; it is
|
||||
up to plugins to make sure they don't introduce anything invalid."""
|
||||
up to plugins to make sure they don't introduce anything invalid.
|
||||
"""
|
||||
|
||||
server = server or self.irc.sid
|
||||
if not self.irc.isInternalServer(server):
|
||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||
# Create an UIDGenerator instance for every SID, so that each gets
|
||||
# distinct values.
|
||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||
# EUID:
|
||||
# parameters: nickname, hopcount, nickTS, umodes, username,
|
||||
# visible hostname, IP address, UID, real hostname, account name, gecos
|
||||
raise ValueError('Server %r is not a PyLink server!' % server)
|
||||
|
||||
uid = self.uidgen[server].next_uid()
|
||||
|
||||
ts = ts or int(time.time())
|
||||
realname = realname or self.irc.botdata['realname']
|
||||
realhost = realhost or host
|
||||
raw_modes = utils.joinModes(modes)
|
||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||
realhost=realhost, ip=ip, manipulatable=manipulatable)
|
||||
utils.applyModes(self.irc, uid, modes)
|
||||
self.irc.applyModes(uid, modes)
|
||||
self.irc.servers[server].users.add(uid)
|
||||
self._send(server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||
"* :{realname}".format(ts=ts, host=host,
|
||||
@ -181,9 +182,9 @@ class HybridProtocol(TS6Protocol):
|
||||
|
||||
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, host, ip)
|
||||
|
||||
parsedmodes = utils.parseModes(self.irc, uid, [modes])
|
||||
parsedmodes = self.irc.parseModes(uid, [modes])
|
||||
log.debug('(%s) handle_uid: Applying modes %s for %s', self.irc.name, parsedmodes, uid)
|
||||
utils.applyModes(self.irc, uid, parsedmodes)
|
||||
self.irc.applyModes(uid, parsedmodes)
|
||||
self.irc.servers[numeric].users.add(uid)
|
||||
|
||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||
@ -220,7 +221,7 @@ class HybridProtocol(TS6Protocol):
|
||||
target = args[0]
|
||||
ts = args[1]
|
||||
modes = args[2:]
|
||||
parsedmodes = utils.parseModes(self.irc, target, modes)
|
||||
parsedmodes = self.irc.parseModes(target, modes)
|
||||
|
||||
for modepair in parsedmodes:
|
||||
if modepair[0] == '+d':
|
||||
@ -258,7 +259,7 @@ class HybridProtocol(TS6Protocol):
|
||||
parsedmodes.remove(modepair)
|
||||
|
||||
if parsedmodes:
|
||||
utils.applyModes(self.irc, target, parsedmodes)
|
||||
self.irc.applyModes(target, parsedmodes)
|
||||
|
||||
return {'target': target, 'modes': parsedmodes}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import utils
|
||||
from log import log
|
||||
from classes import *
|
||||
|
||||
from ts6_common import TS6BaseProtocol
|
||||
from ts6_common import *
|
||||
|
||||
class InspIRCdProtocol(TS6BaseProtocol):
|
||||
def __init__(self, irc):
|
||||
@ -39,24 +39,29 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||
manipulatable=False):
|
||||
"""Spawns a client with nick <nick> on the given IRC connection.
|
||||
"""
|
||||
Spawns a new client with the given options.
|
||||
|
||||
Note: No nick collision / valid nickname checks are done here; it is
|
||||
up to plugins to make sure they don't introduce anything invalid."""
|
||||
up to plugins to make sure they don't introduce anything invalid.
|
||||
"""
|
||||
server = server or self.irc.sid
|
||||
|
||||
if not self.irc.isInternalServer(server):
|
||||
raise ValueError('Server %r is not a PyLink server!' % server)
|
||||
# Create an UIDGenerator instance for every SID, so that each gets
|
||||
# distinct values.
|
||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||
|
||||
uid = self.uidgen[server].next_uid()
|
||||
|
||||
ts = ts or int(time.time())
|
||||
realname = realname or self.irc.botdata['realname']
|
||||
realhost = realhost or host
|
||||
raw_modes = utils.joinModes(modes)
|
||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
||||
utils.applyModes(self.irc, uid, modes)
|
||||
|
||||
self.irc.applyModes(uid, modes)
|
||||
self.irc.servers[server].users.add(uid)
|
||||
|
||||
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
||||
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
|
||||
nick=nick, ident=ident, uid=uid,
|
||||
@ -127,7 +132,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
|
||||
if ts <= orig_ts:
|
||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
namelist = ' '.join(namelist)
|
||||
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
||||
ts=ts, users=namelist, channel=channel,
|
||||
@ -172,7 +177,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
# https://github.com/inspircd/inspircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
|
||||
# Servers need a special command to set umode +o on people.
|
||||
self._operUp(target)
|
||||
utils.applyModes(self.irc, target, modes)
|
||||
self.irc.applyModes(target, modes)
|
||||
joinedmodes = utils.joinModes(modes)
|
||||
if utils.isChannel(target):
|
||||
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
|
||||
@ -486,8 +491,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
self.updateTS(channel, their_ts)
|
||||
|
||||
modestring = args[2:-1] or args[2]
|
||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
||||
utils.applyModes(self.irc, channel, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||
self.irc.applyModes(channel, parsedmodes)
|
||||
namelist = []
|
||||
for user in userlist:
|
||||
modeprefix, user = user.split(',', 1)
|
||||
@ -501,7 +506,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
namelist.append(user)
|
||||
self.irc.users[user].channels.add(channel)
|
||||
if their_ts <= our_ts:
|
||||
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in modeprefix])
|
||||
self.irc.applyModes(channel, [('+%s' % mode, user) for mode in modeprefix])
|
||||
self.irc.channels[channel].users.add(user)
|
||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
|
||||
|
||||
@ -511,9 +516,9 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
uid, ts, nick, realhost, host, ident, ip = args[0:7]
|
||||
realname = args[-1]
|
||||
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
|
||||
parsedmodes = utils.parseModes(self.irc, uid, [args[8], args[9]])
|
||||
parsedmodes = self.irc.parseModes(uid, [args[8], args[9]])
|
||||
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
||||
utils.applyModes(self.irc, uid, parsedmodes)
|
||||
self.irc.applyModes(uid, parsedmodes)
|
||||
self.irc.servers[numeric].users.add(uid)
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
||||
|
||||
@ -550,8 +555,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
channel = utils.toLower(self.irc, args[0])
|
||||
oldobj = self.irc.channels[channel].deepcopy()
|
||||
modes = args[2:]
|
||||
changedmodes = utils.parseModes(self.irc, channel, modes)
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
changedmodes = self.irc.parseModes(channel, modes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
ts = int(args[1])
|
||||
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||
'oldchan': oldobj}
|
||||
@ -563,8 +568,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||
target = args[0]
|
||||
modestrings = args[1:]
|
||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
||||
utils.applyModes(self.irc, target, changedmodes)
|
||||
changedmodes = self.irc.parseModes(target, modestrings)
|
||||
self.irc.applyModes(target, changedmodes)
|
||||
return {'target': target, 'modes': changedmodes}
|
||||
|
||||
def handle_idle(self, numeric, command, args):
|
||||
@ -631,7 +636,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
# <- :70MAAAAAB OPERTYPE Network_Owner
|
||||
omode = [('+o', None)]
|
||||
self.irc.users[numeric].opertype = opertype = args[0].replace("_", " ")
|
||||
utils.applyModes(self.irc, numeric, omode)
|
||||
self.irc.applyModes(numeric, omode)
|
||||
# OPERTYPE is essentially umode +o and metadata in one command;
|
||||
# we'll call that too.
|
||||
self.irc.callHooks([numeric, 'CLIENT_OPERED', {'text': opertype}])
|
||||
|
@ -16,6 +16,14 @@ import utils
|
||||
from log import log
|
||||
from classes import *
|
||||
|
||||
class P10UIDGenerator(utils.IncrementalUIDGenerator):
|
||||
"""Implements an incremental P10 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]'
|
||||
self.length = 3
|
||||
super().__init__(sid)
|
||||
|
||||
def p10b64encode(num, length=2):
|
||||
"""
|
||||
Encodes a given numeric using P10 Base64 numeric nicks, as documented at
|
||||
@ -60,8 +68,8 @@ class P10Protocol(Protocol):
|
||||
def __init__(self, irc):
|
||||
super().__init__(irc)
|
||||
|
||||
# Dictionary of UID generators (one for each server) that the protocol module will fill in.
|
||||
self.uidgen = {}
|
||||
# Dictionary of UID generators (one for each server).
|
||||
self.uidgen = structures.KeyedDefaultdict(P10UIDGenerator)
|
||||
|
||||
# SID generator for P10.
|
||||
self.sidgen = P10SIDGenerator(irc)
|
||||
@ -224,6 +232,12 @@ class P10Protocol(Protocol):
|
||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||
manipulatable=False):
|
||||
"""
|
||||
Spawns a new client with the given options.
|
||||
|
||||
Note: No nick collision / valid nickname checks are done here; it is
|
||||
up to plugins to make sure they don't introduce anything invalid.
|
||||
"""
|
||||
# {7N} *** NICK
|
||||
# 1 <nickname>
|
||||
# 2 <hops>
|
||||
@ -242,7 +256,7 @@ class P10Protocol(Protocol):
|
||||
|
||||
# Create an UIDGenerator instance for every SID, so that each gets
|
||||
# distinct values.
|
||||
uid = self.uidgen.setdefault(server, utils.P10UIDGenerator(server)).next_uid()
|
||||
uid = self.uidgen.setdefault(server, P10UIDGenerator(server)).next_uid()
|
||||
|
||||
# Fill in all the values we need
|
||||
ts = ts or int(time.time())
|
||||
@ -256,7 +270,7 @@ class P10Protocol(Protocol):
|
||||
opertype=opertype)
|
||||
|
||||
# Fill in modes and add it to our users index
|
||||
utils.applyModes(self.irc, uid, modes)
|
||||
self.irc.applyModes(uid, modes)
|
||||
self.irc.servers[server].users.add(uid)
|
||||
|
||||
# Encode IPs when sending
|
||||
@ -363,7 +377,7 @@ class P10Protocol(Protocol):
|
||||
(not self.irc.isInternalServer(numeric)):
|
||||
raise LookupError('No such PyLink client/server exists.')
|
||||
|
||||
utils.applyModes(self.irc, target, modes)
|
||||
self.irc.applyModes(target, modes)
|
||||
modes = list(modes)
|
||||
|
||||
# According to the P10 specification:
|
||||
@ -521,7 +535,7 @@ class P10Protocol(Protocol):
|
||||
|
||||
if ts <= orig_ts:
|
||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
|
||||
def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0):
|
||||
"""
|
||||
@ -611,7 +625,7 @@ class P10Protocol(Protocol):
|
||||
self._send(self.irc.sid, 'FA %s %s' % (target, text))
|
||||
# Save the host change as a user mode (this is what P10 does),
|
||||
# so further host checks work.
|
||||
utils.applyModes(self.irc, target, [('+f', text)])
|
||||
self.irc.applyModes(target, [('+f', text)])
|
||||
|
||||
# P10 cloaks aren't as simple as just replacing the displayed host with the one we're
|
||||
# sending. Check for cloak changes properly.
|
||||
@ -774,8 +788,8 @@ class P10Protocol(Protocol):
|
||||
# parameters attached.
|
||||
if args[5].startswith('+'):
|
||||
modes = args[5:-3]
|
||||
parsedmodes = utils.parseModes(self.irc, uid, modes)
|
||||
utils.applyModes(self.irc, uid, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(uid, modes)
|
||||
self.irc.applyModes(uid, parsedmodes)
|
||||
|
||||
for modepair in parsedmodes:
|
||||
if modepair[0][-1] == 'r':
|
||||
@ -936,7 +950,7 @@ class P10Protocol(Protocol):
|
||||
# If no modes are given, this will simply be empty.
|
||||
modestring = args[2:-1]
|
||||
if modestring:
|
||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
||||
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||
else:
|
||||
parsedmodes = []
|
||||
|
||||
@ -944,7 +958,7 @@ class P10Protocol(Protocol):
|
||||
parsedmodes.extend(bans)
|
||||
|
||||
if parsedmodes:
|
||||
utils.applyModes(self.irc, channel, parsedmodes)
|
||||
self.irc.applyModes(channel, parsedmodes)
|
||||
|
||||
namelist = []
|
||||
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
|
||||
@ -975,7 +989,7 @@ class P10Protocol(Protocol):
|
||||
self.irc.users[user].channels.add(channel)
|
||||
|
||||
if their_ts <= our_ts:
|
||||
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in prefixes])
|
||||
self.irc.applyModes(channel, [('+%s' % mode, user) for mode in prefixes])
|
||||
|
||||
self.irc.channels[channel].users.add(user)
|
||||
|
||||
@ -1044,8 +1058,8 @@ class P10Protocol(Protocol):
|
||||
target = utils.toLower(self.irc, target)
|
||||
|
||||
modestrings = args[1:]
|
||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
||||
utils.applyModes(self.irc, target, changedmodes)
|
||||
changedmodes = self.irc.parseModes(target, modestrings)
|
||||
self.irc.applyModes(target, changedmodes)
|
||||
|
||||
# Call the CLIENT_OPERED hook if +o is being set.
|
||||
if ('+o', None) in changedmodes and target in self.irc.users:
|
||||
@ -1220,7 +1234,7 @@ class P10Protocol(Protocol):
|
||||
# Mode does not take an argument when unsetting.
|
||||
changedmodes.append(('-%s' % modechar, None))
|
||||
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
return {'target': channel, 'modes': changedmodes, 'oldchan': oldobj}
|
||||
|
||||
def handle_account(self, numeric, command, args):
|
||||
@ -1265,7 +1279,7 @@ class P10Protocol(Protocol):
|
||||
text = args[1]
|
||||
|
||||
# Assume a usermode +f change, and then update the cloak checking.
|
||||
utils.applyModes(self.irc, target, [('+f', text)])
|
||||
self.irc.applyModes(target, [('+f', text)])
|
||||
|
||||
self.checkCloakChange(target)
|
||||
# We don't need to send any hooks here, checkCloakChange does that for us.
|
||||
|
@ -14,7 +14,7 @@ import utils
|
||||
from log import log
|
||||
|
||||
from classes import *
|
||||
from ts6_common import TS6BaseProtocol
|
||||
from ts6_common import *
|
||||
|
||||
class TS6Protocol(TS6BaseProtocol):
|
||||
def __init__(self, irc):
|
||||
@ -31,16 +31,19 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||
manipulatable=False):
|
||||
"""Spawns a client with nick <nick> on the given IRC connection.
|
||||
"""
|
||||
Spawns a new client with the given options.
|
||||
|
||||
Note: No nick collision / valid nickname checks are done here; it is
|
||||
up to plugins to make sure they don't introduce anything invalid."""
|
||||
up to plugins to make sure they don't introduce anything invalid.
|
||||
"""
|
||||
server = server or self.irc.sid
|
||||
|
||||
if not self.irc.isInternalServer(server):
|
||||
raise ValueError('Server %r is not a PyLink server!' % server)
|
||||
# Create an UIDGenerator instance for every SID, so that each gets
|
||||
# distinct values.
|
||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||
|
||||
uid = self.uidgen[server].next_uid()
|
||||
|
||||
# EUID:
|
||||
# parameters: nickname, hopcount, nickTS, umodes, username,
|
||||
# visible hostname, IP address, UID, real hostname, account name, gecos
|
||||
@ -50,13 +53,16 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
raw_modes = utils.joinModes(modes)
|
||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
||||
utils.applyModes(self.irc, uid, modes)
|
||||
|
||||
self.irc.applyModes(uid, modes)
|
||||
self.irc.servers[server].users.add(uid)
|
||||
|
||||
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||
"{realhost} * :{realname}".format(ts=ts, host=host,
|
||||
nick=nick, ident=ident, uid=uid,
|
||||
modes=raw_modes, ip=ip, realname=realname,
|
||||
realhost=realhost))
|
||||
|
||||
return u
|
||||
|
||||
def join(self, client, channel):
|
||||
@ -133,7 +139,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self.irc.channels[channel].users.update(uids)
|
||||
if ts <= orig_ts:
|
||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
|
||||
def mode(self, numeric, target, modes, ts=None):
|
||||
"""Sends mode changes from a PyLink client/server."""
|
||||
@ -144,7 +150,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
(not self.irc.isInternalServer(numeric)):
|
||||
raise LookupError('No such PyLink client/server exists.')
|
||||
|
||||
utils.applyModes(self.irc, target, modes)
|
||||
self.irc.applyModes(target, modes)
|
||||
modes = list(modes)
|
||||
|
||||
if utils.isChannel(target):
|
||||
@ -421,8 +427,8 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self.updateTS(channel, their_ts)
|
||||
|
||||
modestring = args[2:-1] or args[2]
|
||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
||||
utils.applyModes(self.irc, channel, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||
self.irc.applyModes(channel, parsedmodes)
|
||||
namelist = []
|
||||
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
|
||||
for userpair in userlist:
|
||||
@ -449,7 +455,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
namelist.append(user)
|
||||
self.irc.users[user].channels.add(channel)
|
||||
if their_ts <= our_ts:
|
||||
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in finalprefix])
|
||||
self.irc.applyModes(channel, [('+%s' % mode, user) for mode in finalprefix])
|
||||
self.irc.channels[channel].users.add(user)
|
||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
|
||||
|
||||
@ -496,9 +502,9 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
|
||||
|
||||
parsedmodes = utils.parseModes(self.irc, uid, [modes])
|
||||
parsedmodes = self.irc.parseModes(uid, [modes])
|
||||
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
||||
utils.applyModes(self.irc, uid, parsedmodes)
|
||||
self.irc.applyModes(uid, parsedmodes)
|
||||
self.irc.servers[numeric].users.add(uid)
|
||||
|
||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||
@ -557,8 +563,8 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
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)
|
||||
changedmodes = self.irc.parseModes(channel, modes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
ts = int(args[0])
|
||||
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||
'oldchan': oldobj}
|
||||
@ -568,8 +574,8 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||
target = args[0]
|
||||
modestrings = args[1:]
|
||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
||||
utils.applyModes(self.irc, target, changedmodes)
|
||||
changedmodes = self.irc.parseModes(target, modestrings)
|
||||
self.irc.applyModes(target, changedmodes)
|
||||
# Call the OPERED UP hook if +o is being set.
|
||||
if ('+o', None) in changedmodes:
|
||||
otype = 'Server Administrator' if ('a', None) in self.irc.users[target].modes else 'IRC Operator'
|
||||
@ -615,7 +621,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
modes = []
|
||||
for ban in args[-1].split():
|
||||
modes.append(('+%s' % mode, ban))
|
||||
utils.applyModes(self.irc, channel, modes)
|
||||
self.irc.applyModes(channel, modes)
|
||||
return {'target': channel, 'modes': modes, 'ts': ts}
|
||||
|
||||
def handle_whois(self, numeric, command, args):
|
||||
|
@ -4,6 +4,7 @@ ts6_common.py: Common base protocol class with functions shared by the UnrealIRC
|
||||
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
|
||||
# Import hacks to access utils and classes...
|
||||
curdir = os.path.dirname(__file__)
|
||||
@ -12,17 +13,106 @@ sys.path += [curdir, os.path.dirname(curdir)]
|
||||
import utils
|
||||
from log import log
|
||||
from classes import *
|
||||
import structures
|
||||
|
||||
class TS6SIDGenerator():
|
||||
"""
|
||||
TS6 SID Generator. <query> is a 3 character string with any combination of
|
||||
uppercase letters, digits, and #'s. it must contain at least one #,
|
||||
which are used by the generator as a wildcard. On every next_sid() call,
|
||||
the first available wildcard character (from the right) will be
|
||||
incremented to generate the next SID.
|
||||
|
||||
When there are no more available SIDs left (SIDs are not reused, only
|
||||
incremented), RuntimeError is raised.
|
||||
|
||||
Example queries:
|
||||
"1#A" would give: 10A, 11A, 12A ... 19A, 1AA, 1BA ... 1ZA (36 total results)
|
||||
"#BQ" would give: 0BQ, 1BQ, 2BQ ... 9BQ (10 total results)
|
||||
"6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results)
|
||||
"""
|
||||
|
||||
def __init__(self, irc):
|
||||
self.irc = irc
|
||||
try:
|
||||
self.query = query = list(irc.serverdata["sidrange"])
|
||||
except KeyError:
|
||||
raise RuntimeError('(%s) "sidrange" is missing from your server configuration block!' % irc.name)
|
||||
|
||||
self.iters = self.query.copy()
|
||||
self.output = self.query.copy()
|
||||
self.allowedchars = {}
|
||||
qlen = len(query)
|
||||
|
||||
assert qlen == 3, 'Incorrect length for a SID (must be 3, got %s)' % qlen
|
||||
assert '#' in query, "Must be at least one wildcard (#) in query"
|
||||
|
||||
for idx, char in enumerate(query):
|
||||
# Iterate over each character in the query string we got, along
|
||||
# with its index in the string.
|
||||
assert char in (string.digits+string.ascii_uppercase+"#"), \
|
||||
"Invalid character %r found." % char
|
||||
if char == '#':
|
||||
if idx == 0: # The first char be only digits
|
||||
self.allowedchars[idx] = string.digits
|
||||
else:
|
||||
self.allowedchars[idx] = string.digits+string.ascii_uppercase
|
||||
self.iters[idx] = iter(self.allowedchars[idx])
|
||||
self.output[idx] = self.allowedchars[idx][0]
|
||||
next(self.iters[idx])
|
||||
|
||||
|
||||
def increment(self, pos=2):
|
||||
"""
|
||||
Increments the SID generator to the next available SID.
|
||||
"""
|
||||
if pos < 0:
|
||||
# Oh no, we've wrapped back to the start!
|
||||
raise RuntimeError('No more available SIDs!')
|
||||
it = self.iters[pos]
|
||||
try:
|
||||
self.output[pos] = next(it)
|
||||
except TypeError: # This position is not an iterator, but a string.
|
||||
self.increment(pos-1)
|
||||
except StopIteration:
|
||||
self.output[pos] = self.allowedchars[pos][0]
|
||||
self.iters[pos] = iter(self.allowedchars[pos])
|
||||
next(self.iters[pos])
|
||||
self.increment(pos-1)
|
||||
|
||||
def next_sid(self):
|
||||
"""
|
||||
Returns the next unused TS6 SID for the server.
|
||||
"""
|
||||
while ''.join(self.output) in self.irc.servers:
|
||||
# Increment until the SID we have doesn't already exist.
|
||||
self.increment()
|
||||
sid = ''.join(self.output)
|
||||
return sid
|
||||
|
||||
class TS6UIDGenerator(utils.IncrementalUIDGenerator):
|
||||
"""Implements an incremental TS6 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
# Define the options for IncrementalUIDGenerator, and then
|
||||
# initialize its functions.
|
||||
# TS6 UIDs are 6 characters in length (9 including the SID).
|
||||
# They go from ABCDEFGHIJKLMNOPQRSTUVWXYZ -> 0123456789 -> wrap around:
|
||||
# e.g. AAAAAA, AAAAAB ..., AAAAA8, AAAAA9, AAAABA, etc.
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456879'
|
||||
self.length = 6
|
||||
super().__init__(sid)
|
||||
|
||||
class TS6BaseProtocol(Protocol):
|
||||
|
||||
def __init__(self, irc):
|
||||
super().__init__(irc)
|
||||
|
||||
# Dictionary of UID generators (one for each server) that the protocol module will fill in.
|
||||
self.uidgen = {}
|
||||
# Dictionary of UID generators (one for each server).
|
||||
self.uidgen = structures.KeyedDefaultdict(TS6UIDGenerator)
|
||||
|
||||
# SID generator for TS6.
|
||||
self.sidgen = utils.TS6SIDGenerator(irc)
|
||||
self.sidgen = TS6SIDGenerator(irc)
|
||||
|
||||
def _send(self, source, msg):
|
||||
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
|
||||
|
@ -16,7 +16,7 @@ sys.path += [curdir, os.path.dirname(curdir)]
|
||||
import utils
|
||||
from log import log
|
||||
from classes import *
|
||||
from ts6_common import TS6BaseProtocol
|
||||
from ts6_common import *
|
||||
|
||||
class UnrealProtocol(TS6BaseProtocol):
|
||||
def __init__(self, irc):
|
||||
@ -63,23 +63,27 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||
manipulatable=False):
|
||||
"""Spawns a client with nick <nick> on the given IRC connection.
|
||||
"""
|
||||
Spawns a new client with the given options.
|
||||
|
||||
Note: No nick collision / valid nickname checks are done here; it is
|
||||
up to plugins to make sure they don't introduce anything invalid."""
|
||||
up to plugins to make sure they don't introduce anything invalid.
|
||||
"""
|
||||
server = server or self.irc.sid
|
||||
if not self.irc.isInternalServer(server):
|
||||
raise ValueError('Server %r is not a PyLink server!' % server)
|
||||
|
||||
# Unreal 4.0 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
|
||||
# do, but that doesn't matter to us...
|
||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||
uid = self.uidgen[server].next_uid()
|
||||
|
||||
ts = ts or int(time.time())
|
||||
realname = realname or self.irc.botdata['realname']
|
||||
realhost = realhost or host
|
||||
raw_modes = utils.joinModes(modes)
|
||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
||||
utils.applyModes(self.irc, uid, modes)
|
||||
self.irc.applyModes(uid, modes)
|
||||
self.irc.servers[server].users.add(uid)
|
||||
|
||||
# UnrealIRCd requires encoding the IP by first packing it into a binary format,
|
||||
@ -176,7 +180,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.irc.channels[channel].users.update(uids)
|
||||
if ts <= orig_ts:
|
||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
||||
utils.applyModes(self.irc, channel, changedmodes)
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
|
||||
def ping(self, source=None, target=None):
|
||||
"""Sends a PING to a target server. Periodic PINGs are sent to our uplink
|
||||
@ -211,7 +215,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
(not self.irc.isInternalServer(numeric)):
|
||||
raise LookupError('No such PyLink client/server exists.')
|
||||
|
||||
utils.applyModes(self.irc, target, modes)
|
||||
self.irc.applyModes(target, modes)
|
||||
joinedmodes = utils.joinModes(modes)
|
||||
if utils.isChannel(target):
|
||||
# The MODE command is used for channel mode changes only
|
||||
@ -374,8 +378,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.irc.servers[numeric].users.add(uid)
|
||||
|
||||
# Handle user modes
|
||||
parsedmodes = utils.parseModes(self.irc, uid, [modestring])
|
||||
utils.applyModes(self.irc, uid, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(uid, [modestring])
|
||||
self.irc.applyModes(uid, parsedmodes)
|
||||
|
||||
# The cloaked (+x) host is completely separate from the displayed host
|
||||
# and real host in that it is ONLY shown if the user is +x (cloak mode
|
||||
@ -575,7 +579,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.irc.users[user].channels.add(channel)
|
||||
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
|
||||
if their_ts <= our_ts:
|
||||
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in finalprefix])
|
||||
self.irc.applyModes(channel, [('+%s' % mode, user) for mode in finalprefix])
|
||||
self.irc.channels[channel].users.add(user)
|
||||
return {'channel': channel, 'users': namelist, 'modes': self.irc.channels[channel].modes, 'ts': their_ts}
|
||||
|
||||
@ -637,9 +641,9 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
channel = utils.toLower(self.irc, args[0])
|
||||
oldobj = self.irc.channels[channel].deepcopy()
|
||||
modes = list(filter(None, args[1:])) # normalize whitespace
|
||||
parsedmodes = utils.parseModes(self.irc, channel, modes)
|
||||
parsedmodes = self.irc.parseModes(channel, modes)
|
||||
if parsedmodes:
|
||||
utils.applyModes(self.irc, channel, parsedmodes)
|
||||
self.irc.applyModes(channel, parsedmodes)
|
||||
if numeric in self.irc.servers and args[-1].isdigit():
|
||||
# Sender is a server AND last arg is number. Perform TS updates.
|
||||
their_ts = int(args[-1])
|
||||
@ -691,8 +695,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
target = self._getUid(args[0])
|
||||
modes = args[1:]
|
||||
|
||||
parsedmodes = utils.parseModes(self.irc, target, modes)
|
||||
utils.applyModes(self.irc, target, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(target, modes)
|
||||
self.irc.applyModes(target, parsedmodes)
|
||||
|
||||
# If +x/-x is being set, update cloaked host info.
|
||||
self.checkCloakChange(target, parsedmodes)
|
||||
@ -733,8 +737,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
def handle_umode2(self, numeric, command, args):
|
||||
"""Handles UMODE2, used to set user modes on oneself."""
|
||||
# <- :GL UMODE2 +W
|
||||
parsedmodes = utils.parseModes(self.irc, numeric, args)
|
||||
utils.applyModes(self.irc, numeric, parsedmodes)
|
||||
parsedmodes = self.irc.parseModes(numeric, args)
|
||||
self.irc.applyModes(numeric, parsedmodes)
|
||||
|
||||
if ('+o', None) in parsedmodes:
|
||||
# If +o being set, call the CLIENT_OPERED internal hook.
|
||||
@ -785,7 +789,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
|
||||
# target.
|
||||
utils.applyModes(self.irc, numeric, [('+x', None), ('+t', None)])
|
||||
self.irc.applyModes(numeric, [('+x', None), ('+t', None)])
|
||||
|
||||
return {'target': numeric, 'newhost': newhost}
|
||||
|
||||
@ -810,7 +814,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
|
||||
# target.
|
||||
utils.applyModes(self.irc, target, [('+x', None), ('+t', None)])
|
||||
self.irc.applyModes(target, [('+x', None), ('+t', None)])
|
||||
|
||||
return {'target': target, 'newhost': newhost}
|
||||
|
||||
|
19
structures.py
Normal file
19
structures.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""
|
||||
structures.py - PyLink data structures module.
|
||||
|
||||
This module contains custom data structures that may be useful in various situations.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
class KeyedDefaultdict(collections.defaultdict):
|
||||
"""
|
||||
Subclass of defaultdict allowing the key to be passed to the default factory.
|
||||
"""
|
||||
def __missing__(self, key):
|
||||
if self.default_factory is None:
|
||||
# If there is no default factory, just let defaultdict handle it
|
||||
super().__missing__(self, key)
|
||||
else:
|
||||
value = self[key] = self.default_factory(key)
|
||||
return value
|
271
utils.py
271
utils.py
@ -10,25 +10,11 @@ import re
|
||||
import inspect
|
||||
import importlib
|
||||
import os
|
||||
import collections
|
||||
|
||||
from log import log
|
||||
import world
|
||||
import conf
|
||||
|
||||
class KeyedDefaultdict(collections.defaultdict):
|
||||
"""
|
||||
Subclass of defaultdict allowing the key to be passed to the default factory.
|
||||
"""
|
||||
def __missing__(self, key):
|
||||
if self.default_factory is None:
|
||||
# If there is no default factory, just let defaultdict handle it
|
||||
super().__missing__(self, key)
|
||||
else:
|
||||
value = self[key] = self.default_factory(key)
|
||||
return value
|
||||
|
||||
|
||||
class NotAuthenticatedError(Exception):
|
||||
"""
|
||||
Exception raised by checkAuthenticated() when a user fails authentication
|
||||
@ -43,9 +29,6 @@ class IncrementalUIDGenerator():
|
||||
"""
|
||||
|
||||
def __init__(self, sid):
|
||||
# TS6 UIDs are 6 characters in length (9 including the SID).
|
||||
# They wrap from ABCDEFGHIJKLMNOPQRSTUVWXYZ -> 0123456789 -> wrap around:
|
||||
# (e.g. AAAAAA, AAAAAB ..., AAAAA8, AAAAA9, AAAABA)
|
||||
if not (hasattr(self, 'allowedchars') and hasattr(self, 'length')):
|
||||
raise RuntimeError("Allowed characters list not defined. Subclass "
|
||||
"%s by defining self.allowedchars and self.length "
|
||||
@ -80,99 +63,6 @@ class IncrementalUIDGenerator():
|
||||
self.increment()
|
||||
return uid
|
||||
|
||||
class TS6UIDGenerator(IncrementalUIDGenerator):
|
||||
"""Implements an incremental TS6 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
# Define the options for IncrementalUIDGenerator, and then
|
||||
# initialize its functions.
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456879'
|
||||
self.length = 6
|
||||
super().__init__(sid)
|
||||
|
||||
class P10UIDGenerator(IncrementalUIDGenerator):
|
||||
"""Implements an incremental P10 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]'
|
||||
self.length = 3
|
||||
super().__init__(sid)
|
||||
|
||||
class TS6SIDGenerator():
|
||||
"""
|
||||
TS6 SID Generator. <query> is a 3 character string with any combination of
|
||||
uppercase letters, digits, and #'s. it must contain at least one #,
|
||||
which are used by the generator as a wildcard. On every next_sid() call,
|
||||
the first available wildcard character (from the right) will be
|
||||
incremented to generate the next SID.
|
||||
|
||||
When there are no more available SIDs left (SIDs are not reused, only
|
||||
incremented), RuntimeError is raised.
|
||||
|
||||
Example queries:
|
||||
"1#A" would give: 10A, 11A, 12A ... 19A, 1AA, 1BA ... 1ZA (36 total results)
|
||||
"#BQ" would give: 0BQ, 1BQ, 2BQ ... 9BQ (10 total results)
|
||||
"6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results)
|
||||
"""
|
||||
|
||||
def __init__(self, irc):
|
||||
self.irc = irc
|
||||
try:
|
||||
self.query = query = list(irc.serverdata["sidrange"])
|
||||
except KeyError:
|
||||
raise RuntimeError('(%s) "sidrange" is missing from your server configuration block!' % irc.name)
|
||||
|
||||
self.iters = self.query.copy()
|
||||
self.output = self.query.copy()
|
||||
self.allowedchars = {}
|
||||
qlen = len(query)
|
||||
|
||||
assert qlen == 3, 'Incorrect length for a SID (must be 3, got %s)' % qlen
|
||||
assert '#' in query, "Must be at least one wildcard (#) in query"
|
||||
|
||||
for idx, char in enumerate(query):
|
||||
# Iterate over each character in the query string we got, along
|
||||
# with its index in the string.
|
||||
assert char in (string.digits+string.ascii_uppercase+"#"), \
|
||||
"Invalid character %r found." % char
|
||||
if char == '#':
|
||||
if idx == 0: # The first char be only digits
|
||||
self.allowedchars[idx] = string.digits
|
||||
else:
|
||||
self.allowedchars[idx] = string.digits+string.ascii_uppercase
|
||||
self.iters[idx] = iter(self.allowedchars[idx])
|
||||
self.output[idx] = self.allowedchars[idx][0]
|
||||
next(self.iters[idx])
|
||||
|
||||
|
||||
def increment(self, pos=2):
|
||||
"""
|
||||
Increments the SID generator to the next available SID.
|
||||
"""
|
||||
if pos < 0:
|
||||
# Oh no, we've wrapped back to the start!
|
||||
raise RuntimeError('No more available SIDs!')
|
||||
it = self.iters[pos]
|
||||
try:
|
||||
self.output[pos] = next(it)
|
||||
except TypeError: # This position is not an iterator, but a string.
|
||||
self.increment(pos-1)
|
||||
except StopIteration:
|
||||
self.output[pos] = self.allowedchars[pos][0]
|
||||
self.iters[pos] = iter(self.allowedchars[pos])
|
||||
next(self.iters[pos])
|
||||
self.increment(pos-1)
|
||||
|
||||
def next_sid(self):
|
||||
"""
|
||||
Returns the next unused TS6 SID for the server.
|
||||
"""
|
||||
while ''.join(self.output) in self.irc.servers:
|
||||
# Increment until the SID we have doesn't already exist.
|
||||
self.increment()
|
||||
sid = ''.join(self.output)
|
||||
return sid
|
||||
|
||||
def add_cmd(func, name=None):
|
||||
"""Binds an IRC command function to the given command name."""
|
||||
if name is None:
|
||||
@ -226,164 +116,21 @@ def isHostmask(text):
|
||||
def parseModes(irc, target, args):
|
||||
"""Parses a modestring list into a list of (mode, argument) tuples.
|
||||
['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')]
|
||||
|
||||
This method is deprecated. Use irc.parseModes() instead.
|
||||
"""
|
||||
# http://www.irc.org/tech_docs/005.html
|
||||
# A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||
# B = Mode that changes a setting and always has a parameter.
|
||||
# C = Mode that changes a setting and only has a parameter when set.
|
||||
# D = Mode that changes a setting and never has a parameter.
|
||||
assert args, 'No valid modes were supplied!'
|
||||
usermodes = not isChannel(target)
|
||||
prefix = ''
|
||||
modestring = args[0]
|
||||
args = args[1:]
|
||||
if usermodes:
|
||||
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
|
||||
|
||||
if target not in irc.users:
|
||||
log.warning('(%s) Possible desync! Mode target %s is not in the users index.', irc.name, target)
|
||||
return [] # Return an empty mode list
|
||||
|
||||
supported_modes = irc.umodes
|
||||
oldmodes = irc.users[target].modes
|
||||
else:
|
||||
log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes)
|
||||
|
||||
if target not in irc.channels:
|
||||
log.warning('(%s) Possible desync! Mode target %s is not in the channels index.', irc.name, target)
|
||||
return []
|
||||
|
||||
supported_modes = irc.cmodes
|
||||
oldmodes = irc.channels[target].modes
|
||||
res = []
|
||||
for mode in modestring:
|
||||
if mode in '+-':
|
||||
prefix = mode
|
||||
else:
|
||||
if not prefix:
|
||||
prefix = '+'
|
||||
arg = None
|
||||
log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
|
||||
try:
|
||||
if mode in (supported_modes['*A'] + supported_modes['*B']):
|
||||
# Must have parameter.
|
||||
log.debug('Mode %s: This mode must have parameter.', mode)
|
||||
arg = args.pop(0)
|
||||
if prefix == '-' and mode in supported_modes['*B'] and arg == '*':
|
||||
# Charybdis allows unsetting +k without actually
|
||||
# knowing the key by faking the argument when unsetting
|
||||
# as a single "*".
|
||||
# We'd need to know the real argument of +k for us to
|
||||
# be able to unset the mode.
|
||||
oldargs = [m[1] for m in oldmodes if m[0] == mode]
|
||||
if oldargs:
|
||||
# Set the arg to the old one on the channel.
|
||||
arg = oldargs[0]
|
||||
log.debug("Mode %s: coersing argument of '*' to %r.", mode, arg)
|
||||
elif mode in irc.prefixmodes and not usermodes:
|
||||
# We're setting a prefix mode on someone (e.g. +o user1)
|
||||
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
||||
arg = args.pop(0)
|
||||
# Convert nicks to UIDs implicitly; most IRCds will want
|
||||
# this already.
|
||||
arg = irc.nickToUid(arg) or arg
|
||||
if arg not in irc.users: # Target doesn't exist, skip it.
|
||||
log.debug('(%s) Skipping setting mode "%s %s"; the '
|
||||
'target doesn\'t seem to exist!', irc.name,
|
||||
mode, arg)
|
||||
continue
|
||||
elif prefix == '+' and mode in supported_modes['*C']:
|
||||
# Only has parameter when setting.
|
||||
log.debug('Mode %s: Only has parameter when setting.', mode)
|
||||
arg = args.pop(0)
|
||||
except IndexError:
|
||||
log.warning('(%s/%s) Error while parsing mode %r: mode requires an '
|
||||
'argument but none was found. (modestring: %r)',
|
||||
irc.name, target, mode, modestring)
|
||||
continue # Skip this mode; don't error out completely.
|
||||
res.append((prefix + mode, arg))
|
||||
return res
|
||||
log.warning("(%s) utils.parseModes is deprecated. Use irc.parseModes() instead!", irc.name)
|
||||
return irc.parseModes(target, args)
|
||||
|
||||
def applyModes(irc, target, changedmodes):
|
||||
"""Takes a list of parsed IRC modes, and applies them on the given target.
|
||||
|
||||
The target can be either a channel or a user; this is handled automatically."""
|
||||
usermodes = not isChannel(target)
|
||||
log.debug('(%s) Using usermodes for this query? %s', irc.name, usermodes)
|
||||
The target can be either a channel or a user; this is handled automatically.
|
||||
|
||||
try:
|
||||
if usermodes:
|
||||
old_modelist = irc.users[target].modes
|
||||
supported_modes = irc.umodes
|
||||
else:
|
||||
old_modelist = irc.channels[target].modes
|
||||
supported_modes = irc.cmodes
|
||||
except KeyError:
|
||||
log.warning('(%s) Possible desync? Mode target %s is unknown.', irc.name, target)
|
||||
return
|
||||
|
||||
modelist = set(old_modelist)
|
||||
log.debug('(%s) Applying modes %r on %s (initial modelist: %s)', irc.name, changedmodes, target, modelist)
|
||||
for mode in changedmodes:
|
||||
# Chop off the +/- part that parseModes gives; it's meaningless for a mode list.
|
||||
try:
|
||||
real_mode = (mode[0][1], mode[1])
|
||||
except IndexError:
|
||||
real_mode = mode
|
||||
|
||||
if not usermodes:
|
||||
# We only handle +qaohv for now. Iterate over every supported mode:
|
||||
# if the IRCd supports this mode and it is the one being set, add/remove
|
||||
# the person from the corresponding prefix mode list (e.g. c.prefixmodes['op']
|
||||
# for ops).
|
||||
for pmode, pmodelist in irc.channels[target].prefixmodes.items():
|
||||
if pmode in irc.cmodes and real_mode[0] == irc.cmodes[pmode]:
|
||||
log.debug('(%s) Initial prefixmodes list: %s', irc.name, pmodelist)
|
||||
if mode[0][0] == '+':
|
||||
pmodelist.add(mode[1])
|
||||
else:
|
||||
pmodelist.discard(mode[1])
|
||||
|
||||
log.debug('(%s) Final prefixmodes list: %s', irc.name, pmodelist)
|
||||
|
||||
if real_mode[0] in irc.prefixmodes:
|
||||
# Don't add prefix modes to IrcChannel.modes; they belong in the
|
||||
# prefixmodes mapping handled above.
|
||||
log.debug('(%s) Not adding mode %s to IrcChannel.modes because '
|
||||
'it\'s a prefix mode.', irc.name, str(mode))
|
||||
continue
|
||||
|
||||
if mode[0][0] == '+':
|
||||
# We're adding a mode
|
||||
existing = [m for m in modelist if m[0] == real_mode[0] and m[1] != real_mode[1]]
|
||||
if existing and real_mode[1] and real_mode[0] not in irc.cmodes['*A']:
|
||||
# The mode we're setting takes a parameter, but is not a list mode (like +beI).
|
||||
# Therefore, only one version of it can exist at a time, and we must remove
|
||||
# any old modepairs using the same letter. Otherwise, we'll get duplicates when,
|
||||
# for example, someone sets mode "+l 30" on a channel already set "+l 25".
|
||||
log.debug('(%s) Old modes for mode %r exist on %s, removing them: %s',
|
||||
irc.name, real_mode, target, str(existing))
|
||||
[modelist.discard(oldmode) for oldmode in existing]
|
||||
modelist.add(real_mode)
|
||||
log.debug('(%s) Adding mode %r on %s', irc.name, real_mode, target)
|
||||
else:
|
||||
log.debug('(%s) Removing mode %r on %s', irc.name, real_mode, target)
|
||||
# We're removing a mode
|
||||
if real_mode[1] is None:
|
||||
# We're removing a mode that only takes arguments when setting.
|
||||
# Remove all mode entries that use the same letter as the one
|
||||
# we're unsetting.
|
||||
for oldmode in modelist.copy():
|
||||
if oldmode[0] == real_mode[0]:
|
||||
modelist.discard(oldmode)
|
||||
else:
|
||||
# Swap the - for a + and then remove it from the list.
|
||||
modelist.discard(real_mode)
|
||||
log.debug('(%s) Final modelist: %s', irc.name, modelist)
|
||||
if usermodes:
|
||||
irc.users[target].modes = modelist
|
||||
else:
|
||||
irc.channels[target].modes = modelist
|
||||
This method is deprecated. Use irc.applyModes() instead.
|
||||
"""
|
||||
log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name)
|
||||
return irc.applyModes(target, changedmodes)
|
||||
|
||||
def joinModes(modes):
|
||||
"""Takes a list of (mode, arg) tuples in parseModes() format, and
|
||||
|
Loading…
Reference in New Issue
Block a user