mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-25 12:12:53 +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 *
|
from log import *
|
||||||
import world
|
import world
|
||||||
import utils
|
import utils
|
||||||
|
import structures
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ class Irc():
|
|||||||
internal=True, desc=self.serverdata.get('serverdesc')
|
internal=True, desc=self.serverdata.get('serverdesc')
|
||||||
or self.botdata['serverdesc'])}
|
or self.botdata['serverdesc'])}
|
||||||
self.users = {}
|
self.users = {}
|
||||||
self.channels = utils.KeyedDefaultdict(IrcChannel)
|
self.channels = structures.KeyedDefaultdict(IrcChannel)
|
||||||
|
|
||||||
# This sets the list of supported channel and user modes: the default
|
# This sets the list of supported channel and user modes: the default
|
||||||
# RFC1459 modes are implied. Named modes are used here to make
|
# RFC1459 modes are implied. Named modes are used here to make
|
||||||
@ -451,7 +452,7 @@ class Irc():
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<classes.Irc object for %r>" % self.name
|
return "<classes.Irc object for %r>" % self.name
|
||||||
|
|
||||||
### Utility functions
|
### General utility functions
|
||||||
def callCommand(self, source, text):
|
def callCommand(self, source, text):
|
||||||
"""
|
"""
|
||||||
Calls a PyLink bot command. source is the caller's UID, and text is the
|
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)."""
|
"""Replies to the last caller in the right context (channel or PM)."""
|
||||||
self.msg(self.called_by, text, notice=notice, source=source)
|
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):
|
def nickToUid(self, nick):
|
||||||
"""Looks up the UID of a user with the given nick, if one is present."""
|
"""Looks up the UID of a user with the given nick, if one is present."""
|
||||||
nick = utils.toLower(self, nick)
|
nick = utils.toLower(self, nick)
|
||||||
|
@ -9,7 +9,7 @@ import utils
|
|||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
from classes import *
|
from classes import *
|
||||||
from ts6 import TS6Protocol
|
from ts6 import *
|
||||||
|
|
||||||
class HybridProtocol(TS6Protocol):
|
class HybridProtocol(TS6Protocol):
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
@ -92,25 +92,26 @@ class HybridProtocol(TS6Protocol):
|
|||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
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,
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None,
|
||||||
manipulatable=False):
|
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
|
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
|
server = server or self.irc.sid
|
||||||
if not self.irc.isInternalServer(server):
|
if not self.irc.isInternalServer(server):
|
||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % 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[server].next_uid()
|
||||||
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
|
|
||||||
ts = ts or int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or self.irc.botdata['realname']
|
realname = realname or self.irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, manipulatable=manipulatable)
|
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.irc.servers[server].users.add(uid)
|
||||||
self._send(server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
self._send(server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||||
"* :{realname}".format(ts=ts, host=host,
|
"* :{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)
|
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)
|
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)
|
self.irc.servers[numeric].users.add(uid)
|
||||||
|
|
||||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||||
@ -220,7 +221,7 @@ class HybridProtocol(TS6Protocol):
|
|||||||
target = args[0]
|
target = args[0]
|
||||||
ts = args[1]
|
ts = args[1]
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
parsedmodes = utils.parseModes(self.irc, target, modes)
|
parsedmodes = self.irc.parseModes(target, modes)
|
||||||
|
|
||||||
for modepair in parsedmodes:
|
for modepair in parsedmodes:
|
||||||
if modepair[0] == '+d':
|
if modepair[0] == '+d':
|
||||||
@ -258,7 +259,7 @@ class HybridProtocol(TS6Protocol):
|
|||||||
parsedmodes.remove(modepair)
|
parsedmodes.remove(modepair)
|
||||||
|
|
||||||
if parsedmodes:
|
if parsedmodes:
|
||||||
utils.applyModes(self.irc, target, parsedmodes)
|
self.irc.applyModes(target, parsedmodes)
|
||||||
|
|
||||||
return {'target': target, 'modes': parsedmodes}
|
return {'target': target, 'modes': parsedmodes}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import utils
|
|||||||
from log import log
|
from log import log
|
||||||
from classes import *
|
from classes import *
|
||||||
|
|
||||||
from ts6_common import TS6BaseProtocol
|
from ts6_common import *
|
||||||
|
|
||||||
class InspIRCdProtocol(TS6BaseProtocol):
|
class InspIRCdProtocol(TS6BaseProtocol):
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
@ -39,24 +39,29 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
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',
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||||
manipulatable=False):
|
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
|
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
|
server = server or self.irc.sid
|
||||||
|
|
||||||
if not self.irc.isInternalServer(server):
|
if not self.irc.isInternalServer(server):
|
||||||
raise ValueError('Server %r is not a PyLink server!' % 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[server].next_uid()
|
||||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
|
||||||
ts = ts or int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or self.irc.botdata['realname']
|
realname = realname or self.irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
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.irc.servers[server].users.add(uid)
|
||||||
|
|
||||||
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
||||||
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
|
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
|
||||||
nick=nick, ident=ident, uid=uid,
|
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)
|
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
|
||||||
if ts <= orig_ts:
|
if ts <= orig_ts:
|
||||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
# 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)
|
namelist = ' '.join(namelist)
|
||||||
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
||||||
ts=ts, users=namelist, channel=channel,
|
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
|
# 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.
|
# Servers need a special command to set umode +o on people.
|
||||||
self._operUp(target)
|
self._operUp(target)
|
||||||
utils.applyModes(self.irc, target, modes)
|
self.irc.applyModes(target, modes)
|
||||||
joinedmodes = utils.joinModes(modes)
|
joinedmodes = utils.joinModes(modes)
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
|
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
|
||||||
@ -486,8 +491,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
self.updateTS(channel, their_ts)
|
self.updateTS(channel, their_ts)
|
||||||
|
|
||||||
modestring = args[2:-1] or args[2]
|
modestring = args[2:-1] or args[2]
|
||||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||||
utils.applyModes(self.irc, channel, parsedmodes)
|
self.irc.applyModes(channel, parsedmodes)
|
||||||
namelist = []
|
namelist = []
|
||||||
for user in userlist:
|
for user in userlist:
|
||||||
modeprefix, user = user.split(',', 1)
|
modeprefix, user = user.split(',', 1)
|
||||||
@ -501,7 +506,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
namelist.append(user)
|
namelist.append(user)
|
||||||
self.irc.users[user].channels.add(channel)
|
self.irc.users[user].channels.add(channel)
|
||||||
if their_ts <= our_ts:
|
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)
|
self.irc.channels[channel].users.add(user)
|
||||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
|
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]
|
uid, ts, nick, realhost, host, ident, ip = args[0:7]
|
||||||
realname = args[-1]
|
realname = args[-1]
|
||||||
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
|
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)
|
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)
|
self.irc.servers[numeric].users.add(uid)
|
||||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
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])
|
channel = utils.toLower(self.irc, args[0])
|
||||||
oldobj = self.irc.channels[channel].deepcopy()
|
oldobj = self.irc.channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = utils.parseModes(self.irc, channel, modes)
|
changedmodes = self.irc.parseModes(channel, modes)
|
||||||
utils.applyModes(self.irc, channel, changedmodes)
|
self.irc.applyModes(channel, changedmodes)
|
||||||
ts = int(args[1])
|
ts = int(args[1])
|
||||||
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||||
'oldchan': oldobj}
|
'oldchan': oldobj}
|
||||||
@ -563,8 +568,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||||
target = args[0]
|
target = args[0]
|
||||||
modestrings = args[1:]
|
modestrings = args[1:]
|
||||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
changedmodes = self.irc.parseModes(target, modestrings)
|
||||||
utils.applyModes(self.irc, target, changedmodes)
|
self.irc.applyModes(target, changedmodes)
|
||||||
return {'target': target, 'modes': changedmodes}
|
return {'target': target, 'modes': changedmodes}
|
||||||
|
|
||||||
def handle_idle(self, numeric, command, args):
|
def handle_idle(self, numeric, command, args):
|
||||||
@ -631,7 +636,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# <- :70MAAAAAB OPERTYPE Network_Owner
|
# <- :70MAAAAAB OPERTYPE Network_Owner
|
||||||
omode = [('+o', None)]
|
omode = [('+o', None)]
|
||||||
self.irc.users[numeric].opertype = opertype = args[0].replace("_", " ")
|
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;
|
# OPERTYPE is essentially umode +o and metadata in one command;
|
||||||
# we'll call that too.
|
# we'll call that too.
|
||||||
self.irc.callHooks([numeric, 'CLIENT_OPERED', {'text': opertype}])
|
self.irc.callHooks([numeric, 'CLIENT_OPERED', {'text': opertype}])
|
||||||
|
@ -16,6 +16,14 @@ import utils
|
|||||||
from log import log
|
from log import log
|
||||||
from classes import *
|
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):
|
def p10b64encode(num, length=2):
|
||||||
"""
|
"""
|
||||||
Encodes a given numeric using P10 Base64 numeric nicks, as documented at
|
Encodes a given numeric using P10 Base64 numeric nicks, as documented at
|
||||||
@ -60,8 +68,8 @@ class P10Protocol(Protocol):
|
|||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
super().__init__(irc)
|
super().__init__(irc)
|
||||||
|
|
||||||
# Dictionary of UID generators (one for each server) that the protocol module will fill in.
|
# Dictionary of UID generators (one for each server).
|
||||||
self.uidgen = {}
|
self.uidgen = structures.KeyedDefaultdict(P10UIDGenerator)
|
||||||
|
|
||||||
# SID generator for P10.
|
# SID generator for P10.
|
||||||
self.sidgen = P10SIDGenerator(irc)
|
self.sidgen = P10SIDGenerator(irc)
|
||||||
@ -224,6 +232,12 @@ class P10Protocol(Protocol):
|
|||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
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',
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||||
manipulatable=False):
|
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
|
# {7N} *** NICK
|
||||||
# 1 <nickname>
|
# 1 <nickname>
|
||||||
# 2 <hops>
|
# 2 <hops>
|
||||||
@ -242,7 +256,7 @@ class P10Protocol(Protocol):
|
|||||||
|
|
||||||
# Create an UIDGenerator instance for every SID, so that each gets
|
# Create an UIDGenerator instance for every SID, so that each gets
|
||||||
# distinct values.
|
# 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
|
# Fill in all the values we need
|
||||||
ts = ts or int(time.time())
|
ts = ts or int(time.time())
|
||||||
@ -256,7 +270,7 @@ class P10Protocol(Protocol):
|
|||||||
opertype=opertype)
|
opertype=opertype)
|
||||||
|
|
||||||
# Fill in modes and add it to our users index
|
# 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)
|
self.irc.servers[server].users.add(uid)
|
||||||
|
|
||||||
# Encode IPs when sending
|
# Encode IPs when sending
|
||||||
@ -363,7 +377,7 @@ class P10Protocol(Protocol):
|
|||||||
(not self.irc.isInternalServer(numeric)):
|
(not self.irc.isInternalServer(numeric)):
|
||||||
raise LookupError('No such PyLink client/server exists.')
|
raise LookupError('No such PyLink client/server exists.')
|
||||||
|
|
||||||
utils.applyModes(self.irc, target, modes)
|
self.irc.applyModes(target, modes)
|
||||||
modes = list(modes)
|
modes = list(modes)
|
||||||
|
|
||||||
# According to the P10 specification:
|
# According to the P10 specification:
|
||||||
@ -521,7 +535,7 @@ class P10Protocol(Protocol):
|
|||||||
|
|
||||||
if ts <= orig_ts:
|
if ts <= orig_ts:
|
||||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
# 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):
|
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))
|
self._send(self.irc.sid, 'FA %s %s' % (target, text))
|
||||||
# Save the host change as a user mode (this is what P10 does),
|
# Save the host change as a user mode (this is what P10 does),
|
||||||
# so further host checks work.
|
# 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
|
# P10 cloaks aren't as simple as just replacing the displayed host with the one we're
|
||||||
# sending. Check for cloak changes properly.
|
# sending. Check for cloak changes properly.
|
||||||
@ -774,8 +788,8 @@ class P10Protocol(Protocol):
|
|||||||
# parameters attached.
|
# parameters attached.
|
||||||
if args[5].startswith('+'):
|
if args[5].startswith('+'):
|
||||||
modes = args[5:-3]
|
modes = args[5:-3]
|
||||||
parsedmodes = utils.parseModes(self.irc, uid, modes)
|
parsedmodes = self.irc.parseModes(uid, modes)
|
||||||
utils.applyModes(self.irc, uid, parsedmodes)
|
self.irc.applyModes(uid, parsedmodes)
|
||||||
|
|
||||||
for modepair in parsedmodes:
|
for modepair in parsedmodes:
|
||||||
if modepair[0][-1] == 'r':
|
if modepair[0][-1] == 'r':
|
||||||
@ -936,7 +950,7 @@ class P10Protocol(Protocol):
|
|||||||
# If no modes are given, this will simply be empty.
|
# If no modes are given, this will simply be empty.
|
||||||
modestring = args[2:-1]
|
modestring = args[2:-1]
|
||||||
if modestring:
|
if modestring:
|
||||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||||
else:
|
else:
|
||||||
parsedmodes = []
|
parsedmodes = []
|
||||||
|
|
||||||
@ -944,7 +958,7 @@ class P10Protocol(Protocol):
|
|||||||
parsedmodes.extend(bans)
|
parsedmodes.extend(bans)
|
||||||
|
|
||||||
if parsedmodes:
|
if parsedmodes:
|
||||||
utils.applyModes(self.irc, channel, parsedmodes)
|
self.irc.applyModes(channel, parsedmodes)
|
||||||
|
|
||||||
namelist = []
|
namelist = []
|
||||||
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
|
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)
|
self.irc.users[user].channels.add(channel)
|
||||||
|
|
||||||
if their_ts <= our_ts:
|
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)
|
self.irc.channels[channel].users.add(user)
|
||||||
|
|
||||||
@ -1044,8 +1058,8 @@ class P10Protocol(Protocol):
|
|||||||
target = utils.toLower(self.irc, target)
|
target = utils.toLower(self.irc, target)
|
||||||
|
|
||||||
modestrings = args[1:]
|
modestrings = args[1:]
|
||||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
changedmodes = self.irc.parseModes(target, modestrings)
|
||||||
utils.applyModes(self.irc, target, changedmodes)
|
self.irc.applyModes(target, changedmodes)
|
||||||
|
|
||||||
# Call the CLIENT_OPERED hook if +o is being set.
|
# Call the CLIENT_OPERED hook if +o is being set.
|
||||||
if ('+o', None) in changedmodes and target in self.irc.users:
|
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.
|
# Mode does not take an argument when unsetting.
|
||||||
changedmodes.append(('-%s' % modechar, None))
|
changedmodes.append(('-%s' % modechar, None))
|
||||||
|
|
||||||
utils.applyModes(self.irc, channel, changedmodes)
|
self.irc.applyModes(channel, changedmodes)
|
||||||
return {'target': channel, 'modes': changedmodes, 'oldchan': oldobj}
|
return {'target': channel, 'modes': changedmodes, 'oldchan': oldobj}
|
||||||
|
|
||||||
def handle_account(self, numeric, command, args):
|
def handle_account(self, numeric, command, args):
|
||||||
@ -1265,7 +1279,7 @@ class P10Protocol(Protocol):
|
|||||||
text = args[1]
|
text = args[1]
|
||||||
|
|
||||||
# Assume a usermode +f change, and then update the cloak checking.
|
# 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)
|
self.checkCloakChange(target)
|
||||||
# We don't need to send any hooks here, checkCloakChange does that for us.
|
# 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 log import log
|
||||||
|
|
||||||
from classes import *
|
from classes import *
|
||||||
from ts6_common import TS6BaseProtocol
|
from ts6_common import *
|
||||||
|
|
||||||
class TS6Protocol(TS6BaseProtocol):
|
class TS6Protocol(TS6BaseProtocol):
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
@ -31,16 +31,19 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
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',
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||||
manipulatable=False):
|
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
|
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
|
server = server or self.irc.sid
|
||||||
|
|
||||||
if not self.irc.isInternalServer(server):
|
if not self.irc.isInternalServer(server):
|
||||||
raise ValueError('Server %r is not a PyLink server!' % 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[server].next_uid()
|
||||||
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
|
||||||
# EUID:
|
# EUID:
|
||||||
# parameters: nickname, hopcount, nickTS, umodes, username,
|
# parameters: nickname, hopcount, nickTS, umodes, username,
|
||||||
# visible hostname, IP address, UID, real hostname, account name, gecos
|
# visible hostname, IP address, UID, real hostname, account name, gecos
|
||||||
@ -50,13 +53,16 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
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.irc.servers[server].users.add(uid)
|
||||||
|
|
||||||
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||||
"{realhost} * :{realname}".format(ts=ts, host=host,
|
"{realhost} * :{realname}".format(ts=ts, host=host,
|
||||||
nick=nick, ident=ident, uid=uid,
|
nick=nick, ident=ident, uid=uid,
|
||||||
modes=raw_modes, ip=ip, realname=realname,
|
modes=raw_modes, ip=ip, realname=realname,
|
||||||
realhost=realhost))
|
realhost=realhost))
|
||||||
|
|
||||||
return u
|
return u
|
||||||
|
|
||||||
def join(self, client, channel):
|
def join(self, client, channel):
|
||||||
@ -133,7 +139,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
self.irc.channels[channel].users.update(uids)
|
self.irc.channels[channel].users.update(uids)
|
||||||
if ts <= orig_ts:
|
if ts <= orig_ts:
|
||||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
# 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):
|
def mode(self, numeric, target, modes, ts=None):
|
||||||
"""Sends mode changes from a PyLink client/server."""
|
"""Sends mode changes from a PyLink client/server."""
|
||||||
@ -144,7 +150,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
(not self.irc.isInternalServer(numeric)):
|
(not self.irc.isInternalServer(numeric)):
|
||||||
raise LookupError('No such PyLink client/server exists.')
|
raise LookupError('No such PyLink client/server exists.')
|
||||||
|
|
||||||
utils.applyModes(self.irc, target, modes)
|
self.irc.applyModes(target, modes)
|
||||||
modes = list(modes)
|
modes = list(modes)
|
||||||
|
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
@ -421,8 +427,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
self.updateTS(channel, their_ts)
|
self.updateTS(channel, their_ts)
|
||||||
|
|
||||||
modestring = args[2:-1] or args[2]
|
modestring = args[2:-1] or args[2]
|
||||||
parsedmodes = utils.parseModes(self.irc, channel, modestring)
|
parsedmodes = self.irc.parseModes(channel, modestring)
|
||||||
utils.applyModes(self.irc, channel, parsedmodes)
|
self.irc.applyModes(channel, parsedmodes)
|
||||||
namelist = []
|
namelist = []
|
||||||
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
|
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
|
||||||
for userpair in userlist:
|
for userpair in userlist:
|
||||||
@ -449,7 +455,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
namelist.append(user)
|
namelist.append(user)
|
||||||
self.irc.users[user].channels.add(channel)
|
self.irc.users[user].channels.add(channel)
|
||||||
if their_ts <= our_ts:
|
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)
|
self.irc.channels[channel].users.add(user)
|
||||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
|
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)
|
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)
|
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)
|
self.irc.servers[numeric].users.add(uid)
|
||||||
|
|
||||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
# 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])
|
channel = utils.toLower(self.irc, args[1])
|
||||||
oldobj = self.irc.channels[channel].deepcopy()
|
oldobj = self.irc.channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = utils.parseModes(self.irc, channel, modes)
|
changedmodes = self.irc.parseModes(channel, modes)
|
||||||
utils.applyModes(self.irc, channel, changedmodes)
|
self.irc.applyModes(channel, changedmodes)
|
||||||
ts = int(args[0])
|
ts = int(args[0])
|
||||||
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||||
'oldchan': oldobj}
|
'oldchan': oldobj}
|
||||||
@ -568,8 +574,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||||
target = args[0]
|
target = args[0]
|
||||||
modestrings = args[1:]
|
modestrings = args[1:]
|
||||||
changedmodes = utils.parseModes(self.irc, target, modestrings)
|
changedmodes = self.irc.parseModes(target, modestrings)
|
||||||
utils.applyModes(self.irc, target, changedmodes)
|
self.irc.applyModes(target, changedmodes)
|
||||||
# Call the OPERED UP hook if +o is being set.
|
# Call the OPERED UP hook if +o is being set.
|
||||||
if ('+o', None) in changedmodes:
|
if ('+o', None) in changedmodes:
|
||||||
otype = 'Server Administrator' if ('a', None) in self.irc.users[target].modes else 'IRC Operator'
|
otype = 'Server Administrator' if ('a', None) in self.irc.users[target].modes else 'IRC Operator'
|
||||||
@ -615,7 +621,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
modes = []
|
modes = []
|
||||||
for ban in args[-1].split():
|
for ban in args[-1].split():
|
||||||
modes.append(('+%s' % mode, ban))
|
modes.append(('+%s' % mode, ban))
|
||||||
utils.applyModes(self.irc, channel, modes)
|
self.irc.applyModes(channel, modes)
|
||||||
return {'target': channel, 'modes': modes, 'ts': ts}
|
return {'target': channel, 'modes': modes, 'ts': ts}
|
||||||
|
|
||||||
def handle_whois(self, numeric, command, args):
|
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 sys
|
||||||
import os
|
import os
|
||||||
|
import string
|
||||||
|
|
||||||
# Import hacks to access utils and classes...
|
# Import hacks to access utils and classes...
|
||||||
curdir = os.path.dirname(__file__)
|
curdir = os.path.dirname(__file__)
|
||||||
@ -12,17 +13,106 @@ sys.path += [curdir, os.path.dirname(curdir)]
|
|||||||
import utils
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
from classes import *
|
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):
|
class TS6BaseProtocol(Protocol):
|
||||||
|
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
super().__init__(irc)
|
super().__init__(irc)
|
||||||
|
|
||||||
# Dictionary of UID generators (one for each server) that the protocol module will fill in.
|
# Dictionary of UID generators (one for each server).
|
||||||
self.uidgen = {}
|
self.uidgen = structures.KeyedDefaultdict(TS6UIDGenerator)
|
||||||
|
|
||||||
# SID generator for TS6.
|
# SID generator for TS6.
|
||||||
self.sidgen = utils.TS6SIDGenerator(irc)
|
self.sidgen = TS6SIDGenerator(irc)
|
||||||
|
|
||||||
def _send(self, source, msg):
|
def _send(self, source, msg):
|
||||||
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
|
"""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
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
from classes import *
|
from classes import *
|
||||||
from ts6_common import TS6BaseProtocol
|
from ts6_common import *
|
||||||
|
|
||||||
class UnrealProtocol(TS6BaseProtocol):
|
class UnrealProtocol(TS6BaseProtocol):
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
@ -63,23 +63,27 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
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',
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype='IRC Operator',
|
||||||
manipulatable=False):
|
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
|
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
|
server = server or self.irc.sid
|
||||||
if not self.irc.isInternalServer(server):
|
if not self.irc.isInternalServer(server):
|
||||||
raise ValueError('Server %r is not a PyLink server!' % 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
|
# 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...
|
# 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())
|
ts = ts or int(time.time())
|
||||||
realname = realname or self.irc.botdata['realname']
|
realname = realname or self.irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
|
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.irc.servers[server].users.add(uid)
|
||||||
|
|
||||||
# UnrealIRCd requires encoding the IP by first packing it into a binary format,
|
# 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)
|
self.irc.channels[channel].users.update(uids)
|
||||||
if ts <= orig_ts:
|
if ts <= orig_ts:
|
||||||
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
|
# 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):
|
def ping(self, source=None, target=None):
|
||||||
"""Sends a PING to a target server. Periodic PINGs are sent to our uplink
|
"""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)):
|
(not self.irc.isInternalServer(numeric)):
|
||||||
raise LookupError('No such PyLink client/server exists.')
|
raise LookupError('No such PyLink client/server exists.')
|
||||||
|
|
||||||
utils.applyModes(self.irc, target, modes)
|
self.irc.applyModes(target, modes)
|
||||||
joinedmodes = utils.joinModes(modes)
|
joinedmodes = utils.joinModes(modes)
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
# The MODE command is used for channel mode changes only
|
# The MODE command is used for channel mode changes only
|
||||||
@ -374,8 +378,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
self.irc.servers[numeric].users.add(uid)
|
self.irc.servers[numeric].users.add(uid)
|
||||||
|
|
||||||
# Handle user modes
|
# Handle user modes
|
||||||
parsedmodes = utils.parseModes(self.irc, uid, [modestring])
|
parsedmodes = self.irc.parseModes(uid, [modestring])
|
||||||
utils.applyModes(self.irc, uid, parsedmodes)
|
self.irc.applyModes(uid, parsedmodes)
|
||||||
|
|
||||||
# The cloaked (+x) host is completely separate from the displayed host
|
# 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
|
# 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)
|
self.irc.users[user].channels.add(channel)
|
||||||
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
|
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
|
||||||
if their_ts <= our_ts:
|
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)
|
self.irc.channels[channel].users.add(user)
|
||||||
return {'channel': channel, 'users': namelist, 'modes': self.irc.channels[channel].modes, 'ts': their_ts}
|
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])
|
channel = utils.toLower(self.irc, args[0])
|
||||||
oldobj = self.irc.channels[channel].deepcopy()
|
oldobj = self.irc.channels[channel].deepcopy()
|
||||||
modes = list(filter(None, args[1:])) # normalize whitespace
|
modes = list(filter(None, args[1:])) # normalize whitespace
|
||||||
parsedmodes = utils.parseModes(self.irc, channel, modes)
|
parsedmodes = self.irc.parseModes(channel, modes)
|
||||||
if parsedmodes:
|
if parsedmodes:
|
||||||
utils.applyModes(self.irc, channel, parsedmodes)
|
self.irc.applyModes(channel, parsedmodes)
|
||||||
if numeric in self.irc.servers and args[-1].isdigit():
|
if numeric in self.irc.servers and args[-1].isdigit():
|
||||||
# Sender is a server AND last arg is number. Perform TS updates.
|
# Sender is a server AND last arg is number. Perform TS updates.
|
||||||
their_ts = int(args[-1])
|
their_ts = int(args[-1])
|
||||||
@ -691,8 +695,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
target = self._getUid(args[0])
|
target = self._getUid(args[0])
|
||||||
modes = args[1:]
|
modes = args[1:]
|
||||||
|
|
||||||
parsedmodes = utils.parseModes(self.irc, target, modes)
|
parsedmodes = self.irc.parseModes(target, modes)
|
||||||
utils.applyModes(self.irc, target, parsedmodes)
|
self.irc.applyModes(target, parsedmodes)
|
||||||
|
|
||||||
# If +x/-x is being set, update cloaked host info.
|
# If +x/-x is being set, update cloaked host info.
|
||||||
self.checkCloakChange(target, parsedmodes)
|
self.checkCloakChange(target, parsedmodes)
|
||||||
@ -733,8 +737,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
def handle_umode2(self, numeric, command, args):
|
def handle_umode2(self, numeric, command, args):
|
||||||
"""Handles UMODE2, used to set user modes on oneself."""
|
"""Handles UMODE2, used to set user modes on oneself."""
|
||||||
# <- :GL UMODE2 +W
|
# <- :GL UMODE2 +W
|
||||||
parsedmodes = utils.parseModes(self.irc, numeric, args)
|
parsedmodes = self.irc.parseModes(numeric, args)
|
||||||
utils.applyModes(self.irc, numeric, parsedmodes)
|
self.irc.applyModes(numeric, parsedmodes)
|
||||||
|
|
||||||
if ('+o', None) in parsedmodes:
|
if ('+o', None) in parsedmodes:
|
||||||
# If +o being set, call the CLIENT_OPERED internal hook.
|
# 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
|
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
|
||||||
# target.
|
# target.
|
||||||
utils.applyModes(self.irc, numeric, [('+x', None), ('+t', None)])
|
self.irc.applyModes(numeric, [('+x', None), ('+t', None)])
|
||||||
|
|
||||||
return {'target': numeric, 'newhost': newhost}
|
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
|
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
|
||||||
# target.
|
# target.
|
||||||
utils.applyModes(self.irc, target, [('+x', None), ('+t', None)])
|
self.irc.applyModes(target, [('+x', None), ('+t', None)])
|
||||||
|
|
||||||
return {'target': target, 'newhost': newhost}
|
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 inspect
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import collections
|
|
||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import world
|
import world
|
||||||
import conf
|
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):
|
class NotAuthenticatedError(Exception):
|
||||||
"""
|
"""
|
||||||
Exception raised by checkAuthenticated() when a user fails authentication
|
Exception raised by checkAuthenticated() when a user fails authentication
|
||||||
@ -43,9 +29,6 @@ class IncrementalUIDGenerator():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sid):
|
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')):
|
if not (hasattr(self, 'allowedchars') and hasattr(self, 'length')):
|
||||||
raise RuntimeError("Allowed characters list not defined. Subclass "
|
raise RuntimeError("Allowed characters list not defined. Subclass "
|
||||||
"%s by defining self.allowedchars and self.length "
|
"%s by defining self.allowedchars and self.length "
|
||||||
@ -80,99 +63,6 @@ class IncrementalUIDGenerator():
|
|||||||
self.increment()
|
self.increment()
|
||||||
return uid
|
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):
|
def add_cmd(func, name=None):
|
||||||
"""Binds an IRC command function to the given command name."""
|
"""Binds an IRC command function to the given command name."""
|
||||||
if name is None:
|
if name is None:
|
||||||
@ -226,164 +116,21 @@ def isHostmask(text):
|
|||||||
def parseModes(irc, target, args):
|
def parseModes(irc, target, args):
|
||||||
"""Parses a modestring list into a list of (mode, argument) tuples.
|
"""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')]
|
['+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
|
log.warning("(%s) utils.parseModes is deprecated. Use irc.parseModes() instead!", irc.name)
|
||||||
# A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
return irc.parseModes(target, args)
|
||||||
# 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
|
|
||||||
|
|
||||||
def applyModes(irc, target, changedmodes):
|
def applyModes(irc, target, changedmodes):
|
||||||
"""Takes a list of parsed IRC modes, and applies them on the given target.
|
"""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."""
|
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)
|
|
||||||
|
|
||||||
try:
|
This method is deprecated. Use irc.applyModes() instead.
|
||||||
if usermodes:
|
"""
|
||||||
old_modelist = irc.users[target].modes
|
log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name)
|
||||||
supported_modes = irc.umodes
|
return irc.applyModes(target, changedmodes)
|
||||||
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
|
|
||||||
|
|
||||||
def joinModes(modes):
|
def joinModes(modes):
|
||||||
"""Takes a list of (mode, arg) tuples in parseModes() format, and
|
"""Takes a list of (mode, arg) tuples in parseModes() format, and
|
||||||
|
Loading…
Reference in New Issue
Block a user