mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-26 04:32:51 +01:00
Merge commit '320de2079a78202e99c7b6aeb53c28c13f43ba47'
Many things here, including: - New 'exec' plugin - INVITE, umode +H (hideoper) support for relay - New and improved 'showuser' command, now with internals that support multiple binds to one command name. - relay: bug fixes, like not sending empty user mode changes.
This commit is contained in:
commit
d6cb9d45c7
305
classes.py
305
classes.py
@ -1,9 +1,263 @@
|
|||||||
import threading
|
import threading
|
||||||
from random import randint
|
from random import randint
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import ssl
|
||||||
|
from collections import defaultdict
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import main
|
from conf import conf
|
||||||
import time
|
import world
|
||||||
|
|
||||||
|
### Exceptions
|
||||||
|
|
||||||
|
class ProtocolError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
### Internal classes (users, servers, channels)
|
||||||
|
|
||||||
|
class Irc():
|
||||||
|
def initVars(self):
|
||||||
|
self.pseudoclient = None
|
||||||
|
self.connected = threading.Event()
|
||||||
|
self.lastping = time.time()
|
||||||
|
# Server, channel, and user indexes to be populated by our protocol module
|
||||||
|
self.servers = {self.sid: IrcServer(None, self.serverdata['hostname'], internal=True)}
|
||||||
|
self.users = {}
|
||||||
|
self.channels = defaultdict(IrcChannel)
|
||||||
|
# Sets flags such as whether to use halfops, etc. The default RFC1459
|
||||||
|
# modes are implied.
|
||||||
|
self.cmodes = {'op': 'o', 'secret': 's', 'private': 'p',
|
||||||
|
'noextmsg': 'n', 'moderated': 'm', 'inviteonly': 'i',
|
||||||
|
'topiclock': 't', 'limit': 'l', 'ban': 'b',
|
||||||
|
'voice': 'v', 'key': 'k',
|
||||||
|
# Type A, B, and C modes
|
||||||
|
'*A': 'b',
|
||||||
|
'*B': 'k',
|
||||||
|
'*C': 'l',
|
||||||
|
'*D': 'imnpstr'}
|
||||||
|
self.umodes = {'invisible': 'i', 'snomask': 's', 'wallops': 'w',
|
||||||
|
'oper': 'o',
|
||||||
|
'*A': '', '*B': '', '*C': 's', '*D': 'iow'}
|
||||||
|
|
||||||
|
# This max nick length starts off as the config value, but may be
|
||||||
|
# overwritten later by the protocol module if such information is
|
||||||
|
# received. Note that only some IRCds (InspIRCd) give us nick length
|
||||||
|
# during link, so it is still required that the config value be set!
|
||||||
|
self.maxnicklen = self.serverdata['maxnicklen']
|
||||||
|
self.prefixmodes = {'o': '@', 'v': '+'}
|
||||||
|
|
||||||
|
# Uplink SID (filled in by protocol module)
|
||||||
|
self.uplink = None
|
||||||
|
self.start_ts = int(time.time())
|
||||||
|
|
||||||
|
# UID generators, for servers that need it
|
||||||
|
self.uidgen = {}
|
||||||
|
|
||||||
|
def __init__(self, netname, proto):
|
||||||
|
# Initialize some variables
|
||||||
|
self.name = netname.lower()
|
||||||
|
self.conf = conf
|
||||||
|
self.serverdata = conf['servers'][netname]
|
||||||
|
self.sid = self.serverdata["sid"]
|
||||||
|
self.botdata = conf['bot']
|
||||||
|
self.proto = proto
|
||||||
|
self.pingfreq = self.serverdata.get('pingfreq') or 30
|
||||||
|
self.pingtimeout = self.pingfreq * 2
|
||||||
|
|
||||||
|
self.initVars()
|
||||||
|
|
||||||
|
if world.testing:
|
||||||
|
# HACK: Don't thread if we're running tests.
|
||||||
|
self.connect()
|
||||||
|
else:
|
||||||
|
self.connection_thread = threading.Thread(target = self.connect)
|
||||||
|
self.connection_thread.start()
|
||||||
|
self.pingTimer = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
ip = self.serverdata["ip"]
|
||||||
|
port = self.serverdata["port"]
|
||||||
|
while True:
|
||||||
|
self.initVars()
|
||||||
|
checks_ok = True
|
||||||
|
try:
|
||||||
|
self.socket = socket.socket()
|
||||||
|
self.socket.setblocking(0)
|
||||||
|
# Initial connection timeout is a lot smaller than the timeout after
|
||||||
|
# we've connected; this is intentional.
|
||||||
|
self.socket.settimeout(self.pingfreq)
|
||||||
|
self.ssl = self.serverdata.get('ssl')
|
||||||
|
if self.ssl:
|
||||||
|
log.info('(%s) Attempting SSL for this connection...', self.name)
|
||||||
|
certfile = self.serverdata.get('ssl_certfile')
|
||||||
|
keyfile = self.serverdata.get('ssl_keyfile')
|
||||||
|
if certfile and keyfile:
|
||||||
|
try:
|
||||||
|
self.socket = ssl.wrap_socket(self.socket,
|
||||||
|
certfile=certfile,
|
||||||
|
keyfile=keyfile)
|
||||||
|
except OSError:
|
||||||
|
log.exception('(%s) Caught OSError trying to '
|
||||||
|
'initialize the SSL connection; '
|
||||||
|
'are "ssl_certfile" and '
|
||||||
|
'"ssl_keyfile" set correctly?',
|
||||||
|
self.name)
|
||||||
|
checks_ok = False
|
||||||
|
else:
|
||||||
|
log.error('(%s) SSL certfile/keyfile was not set '
|
||||||
|
'correctly, aborting... ', self.name)
|
||||||
|
checks_ok = False
|
||||||
|
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
||||||
|
self.socket.connect((ip, port))
|
||||||
|
self.socket.settimeout(self.pingtimeout)
|
||||||
|
|
||||||
|
if self.ssl and checks_ok:
|
||||||
|
peercert = self.socket.getpeercert(binary_form=True)
|
||||||
|
sha1fp = hashlib.sha1(peercert).hexdigest()
|
||||||
|
expected_fp = self.serverdata.get('ssl_fingerprint')
|
||||||
|
if expected_fp:
|
||||||
|
if sha1fp != expected_fp:
|
||||||
|
log.error('(%s) Uplink\'s SSL certificate '
|
||||||
|
'fingerprint (SHA1) does not match the '
|
||||||
|
'one configured: expected %r, got %r; '
|
||||||
|
'disconnecting...', self.name,
|
||||||
|
expected_fp, sha1fp)
|
||||||
|
checks_ok = False
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink SSL certificate fingerprint '
|
||||||
|
'(SHA1) verified: %r', self.name, sha1fp)
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink\'s SSL certificate fingerprint '
|
||||||
|
'is %r. You can enhance the security of your '
|
||||||
|
'link by specifying this in a "ssl_fingerprint"'
|
||||||
|
' option in your server block.', self.name,
|
||||||
|
sha1fp)
|
||||||
|
|
||||||
|
if checks_ok:
|
||||||
|
self.proto.connect(self)
|
||||||
|
self.spawnMain()
|
||||||
|
log.info('(%s) Starting ping schedulers....', self.name)
|
||||||
|
self.schedulePing()
|
||||||
|
log.info('(%s) Server ready; listening for data.', self.name)
|
||||||
|
self.run()
|
||||||
|
else:
|
||||||
|
log.error('(%s) A configuration error was encountered '
|
||||||
|
'trying to set up this connection. Please check'
|
||||||
|
' your configuration file and try again.',
|
||||||
|
self.name)
|
||||||
|
except (socket.error, ProtocolError, ConnectionError) as e:
|
||||||
|
log.warning('(%s) Disconnected from IRC: %s: %s',
|
||||||
|
self.name, type(e).__name__, str(e))
|
||||||
|
self.disconnect()
|
||||||
|
autoconnect = self.serverdata.get('autoconnect')
|
||||||
|
log.debug('(%s) Autoconnect delay set to %s seconds.', self.name, autoconnect)
|
||||||
|
if autoconnect is not None and autoconnect >= 0:
|
||||||
|
log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect)
|
||||||
|
time.sleep(autoconnect)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
log.debug('(%s) Canceling pingTimer at %s due to disconnect() call', self.name, time.time())
|
||||||
|
self.connected.clear()
|
||||||
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
self.pingTimer.cancel()
|
||||||
|
except: # Socket timed out during creation; ignore
|
||||||
|
pass
|
||||||
|
# Internal hook signifying that a network has disconnected.
|
||||||
|
self.callHooks([None, 'PYLINK_DISCONNECT', {}])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
buf = b""
|
||||||
|
data = b""
|
||||||
|
while True:
|
||||||
|
data = self.socket.recv(2048)
|
||||||
|
buf += data
|
||||||
|
if self.connected.is_set() and not data:
|
||||||
|
log.warning('(%s) No data received and self.connected is set; disconnecting!', self.name)
|
||||||
|
return
|
||||||
|
elif (time.time() - self.lastping) > self.pingtimeout:
|
||||||
|
log.warning('(%s) Connection timed out.', self.name)
|
||||||
|
return
|
||||||
|
while b'\n' in buf:
|
||||||
|
line, buf = buf.split(b'\n', 1)
|
||||||
|
line = line.strip(b'\r')
|
||||||
|
# TODO: respect other encodings?
|
||||||
|
line = line.decode("utf-8", "replace")
|
||||||
|
log.debug("(%s) <- %s", self.name, line)
|
||||||
|
hook_args = None
|
||||||
|
try:
|
||||||
|
hook_args = self.proto.handle_events(self, line)
|
||||||
|
except Exception:
|
||||||
|
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
|
||||||
|
return
|
||||||
|
# Only call our hooks if there's data to process. Handlers that support
|
||||||
|
# hooks will return a dict of parsed arguments, which can be passed on
|
||||||
|
# to plugins and the like. For example, the JOIN handler will return
|
||||||
|
# something like: {'channel': '#whatever', 'users': ['UID1', 'UID2',
|
||||||
|
# 'UID3']}, etc.
|
||||||
|
if hook_args is not None:
|
||||||
|
self.callHooks(hook_args)
|
||||||
|
|
||||||
|
def callHooks(self, hook_args):
|
||||||
|
numeric, command, parsed_args = hook_args
|
||||||
|
# Always make sure TS is sent.
|
||||||
|
if 'ts' not in parsed_args:
|
||||||
|
parsed_args['ts'] = int(time.time())
|
||||||
|
hook_cmd = command
|
||||||
|
hook_map = self.proto.hook_map
|
||||||
|
# Handlers can return a 'parse_as' key to send their payload to a
|
||||||
|
# different hook. An example of this is "/join 0" being interpreted
|
||||||
|
# as leaving all channels (PART).
|
||||||
|
if command in hook_map:
|
||||||
|
hook_cmd = hook_map[command]
|
||||||
|
hook_cmd = parsed_args.get('parse_as') or hook_cmd
|
||||||
|
log.debug('Parsed args %r received from %s handler (calling hook %s)', parsed_args, command, hook_cmd)
|
||||||
|
# Iterate over hooked functions, catching errors accordingly
|
||||||
|
for hook_func in world.command_hooks[hook_cmd]:
|
||||||
|
try:
|
||||||
|
log.debug('Calling function %s', hook_func)
|
||||||
|
hook_func(self, numeric, command, parsed_args)
|
||||||
|
except Exception:
|
||||||
|
# We don't want plugins to crash our servers...
|
||||||
|
log.exception('Unhandled exception caught in %r' % hook_func)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
# Safeguard against newlines in input!! Otherwise, each line gets
|
||||||
|
# treated as a separate command, which is particularly nasty.
|
||||||
|
data = data.replace('\n', ' ')
|
||||||
|
data = data.encode("utf-8") + b"\n"
|
||||||
|
stripped_data = data.decode("utf-8").strip("\n")
|
||||||
|
log.debug("(%s) -> %s", self.name, stripped_data)
|
||||||
|
try:
|
||||||
|
self.socket.send(data)
|
||||||
|
except (OSError, AttributeError):
|
||||||
|
log.debug("(%s) Dropping message %r; network isn't connected!", self.name, stripped_data)
|
||||||
|
|
||||||
|
def schedulePing(self):
|
||||||
|
self.proto.pingServer(self)
|
||||||
|
self.pingTimer = threading.Timer(self.pingfreq, self.schedulePing)
|
||||||
|
self.pingTimer.daemon = True
|
||||||
|
self.pingTimer.start()
|
||||||
|
log.debug('(%s) Ping scheduled at %s', self.name, time.time())
|
||||||
|
|
||||||
|
def spawnMain(self):
|
||||||
|
nick = self.botdata.get('nick') or 'PyLink'
|
||||||
|
ident = self.botdata.get('ident') or 'pylink'
|
||||||
|
host = self.serverdata["hostname"]
|
||||||
|
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
|
||||||
|
olduserobj = self.pseudoclient
|
||||||
|
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)})
|
||||||
|
for chan in self.serverdata['channels']:
|
||||||
|
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
||||||
|
# PyLink internal hook called when spawnMain is called and the
|
||||||
|
# contents of Irc().pseudoclient change.
|
||||||
|
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
|
||||||
|
|
||||||
class IrcUser():
|
class IrcUser():
|
||||||
def __init__(self, nick, ts, uid, ident='null', host='null',
|
def __init__(self, nick, ts, uid, ident='null', host='null',
|
||||||
@ -36,7 +290,7 @@ class IrcServer():
|
|||||||
"""
|
"""
|
||||||
def __init__(self, uplink, name, internal=False):
|
def __init__(self, uplink, name, internal=False):
|
||||||
self.uplink = uplink
|
self.uplink = uplink
|
||||||
self.users = []
|
self.users = set()
|
||||||
self.internal = internal
|
self.internal = internal
|
||||||
self.name = name.lower()
|
self.name = name.lower()
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -45,7 +299,7 @@ class IrcServer():
|
|||||||
class IrcChannel():
|
class IrcChannel():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.users = set()
|
self.users = set()
|
||||||
self.modes = set()
|
self.modes = {('n', None), ('t', None)}
|
||||||
self.topic = ''
|
self.topic = ''
|
||||||
self.ts = int(time.time())
|
self.ts = int(time.time())
|
||||||
self.topicset = False
|
self.topicset = False
|
||||||
@ -60,33 +314,9 @@ class IrcChannel():
|
|||||||
s.discard(target)
|
s.discard(target)
|
||||||
self.users.discard(target)
|
self.users.discard(target)
|
||||||
|
|
||||||
class ProtocolError(Exception):
|
### FakeIRC classes, used for test cases
|
||||||
pass
|
|
||||||
|
|
||||||
global testconf
|
class FakeIRC(Irc):
|
||||||
testconf = {'bot':
|
|
||||||
{
|
|
||||||
'nick': 'PyLink',
|
|
||||||
'user': 'pylink',
|
|
||||||
'realname': 'PyLink Service Client',
|
|
||||||
'loglevel': 'DEBUG',
|
|
||||||
},
|
|
||||||
'servers':
|
|
||||||
{'unittest':
|
|
||||||
{
|
|
||||||
'ip': '0.0.0.0',
|
|
||||||
'port': 7000,
|
|
||||||
'recvpass': "abcd",
|
|
||||||
'sendpass': "abcd",
|
|
||||||
'protocol': "null",
|
|
||||||
'hostname': "pylink.unittest",
|
|
||||||
'sid': "9PY",
|
|
||||||
'channels': ["#pylink"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
class FakeIRC(main.Irc):
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.messages = []
|
self.messages = []
|
||||||
self.hookargs = []
|
self.hookargs = []
|
||||||
@ -100,7 +330,10 @@ class FakeIRC(main.Irc):
|
|||||||
def run(self, data):
|
def run(self, data):
|
||||||
"""Queues a message to the fake IRC server."""
|
"""Queues a message to the fake IRC server."""
|
||||||
log.debug('<- ' + data)
|
log.debug('<- ' + data)
|
||||||
self.proto.handle_events(self, data)
|
hook_args = self.proto.handle_events(self, data)
|
||||||
|
if hook_args is not None:
|
||||||
|
self.hookmsgs.append(hook_args)
|
||||||
|
self.callHooks(hook_args)
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
self.messages.append(data)
|
self.messages.append(data)
|
||||||
@ -132,13 +365,13 @@ class FakeIRC(main.Irc):
|
|||||||
self.hookmsgs = []
|
self.hookmsgs = []
|
||||||
return hookmsgs
|
return hookmsgs
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def dummyhook(irc, source, command, parsed_args):
|
|
||||||
"""Dummy function to bind to hooks."""
|
|
||||||
irc.hookmsgs.append(parsed_args)
|
|
||||||
|
|
||||||
class FakeProto():
|
class FakeProto():
|
||||||
"""Dummy protocol module for testing purposes."""
|
"""Dummy protocol module for testing purposes."""
|
||||||
|
def __init__(self):
|
||||||
|
self.hook_map = {}
|
||||||
|
self.casemapping = 'rfc1459'
|
||||||
|
self.__name__ = 'FakeProto'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_events(irc, data):
|
def handle_events(irc, data):
|
||||||
pass
|
pass
|
||||||
|
40
conf.py
40
conf.py
@ -1,7 +1,39 @@
|
|||||||
import yaml
|
import yaml
|
||||||
import sys
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
global confname
|
import world
|
||||||
|
|
||||||
|
global testconf
|
||||||
|
testconf = {'bot':
|
||||||
|
{
|
||||||
|
'nick': 'PyLink',
|
||||||
|
'user': 'pylink',
|
||||||
|
'realname': 'PyLink Service Client',
|
||||||
|
# Suppress logging in the test output for the most part.
|
||||||
|
'loglevel': 'CRITICAL',
|
||||||
|
'serverdesc': 'PyLink unit tests'
|
||||||
|
},
|
||||||
|
'servers':
|
||||||
|
# Wildcard defaultdict! This means that
|
||||||
|
# any network name you try will work and return
|
||||||
|
# this basic template:
|
||||||
|
defaultdict(lambda: {
|
||||||
|
'ip': '0.0.0.0',
|
||||||
|
'port': 7000,
|
||||||
|
'recvpass': "abcd",
|
||||||
|
'sendpass': "chucknorris",
|
||||||
|
'protocol': "null",
|
||||||
|
'hostname': "pylink.unittest",
|
||||||
|
'sid': "9PY",
|
||||||
|
'channels': ["#pylink"],
|
||||||
|
'maxnicklen': 20
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if world.testing:
|
||||||
|
conf = testconf
|
||||||
|
confname = 'testconf'
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
# Get the config name from the command line, falling back to config.yml
|
# Get the config name from the command line, falling back to config.yml
|
||||||
# if not given.
|
# if not given.
|
||||||
@ -13,7 +45,9 @@ except IndexError:
|
|||||||
# we load.
|
# we load.
|
||||||
confname = 'pylink'
|
confname = 'pylink'
|
||||||
fname = 'config.yml'
|
fname = 'config.yml'
|
||||||
|
|
||||||
with open(fname, 'r') as f:
|
with open(fname, 'r') as f:
|
||||||
global conf
|
try:
|
||||||
conf = yaml.load(f)
|
conf = yaml.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR: Failed to load config from %r: %s: %s' % (fname, type(e).__name__, e))
|
||||||
|
sys.exit(4)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import utils
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
|
import world
|
||||||
|
|
||||||
# Handle KILLs sent to the PyLink client and respawn
|
# Handle KILLs sent to the PyLink client and respawn
|
||||||
def handle_kill(irc, source, command, args):
|
def handle_kill(irc, source, command, args):
|
||||||
@ -24,13 +25,12 @@ def handle_commands(irc, source, command, args):
|
|||||||
cmd_args = text.split(' ')
|
cmd_args = text.split(' ')
|
||||||
cmd = cmd_args[0].lower()
|
cmd = cmd_args[0].lower()
|
||||||
cmd_args = cmd_args[1:]
|
cmd_args = cmd_args[1:]
|
||||||
try:
|
if cmd not in world.bot_commands:
|
||||||
func = utils.bot_commands[cmd]
|
utils.msg(irc, source, 'Error: Unknown command %r.' % cmd)
|
||||||
except KeyError:
|
|
||||||
utils.msg(irc, source, 'Unknown command %r.' % cmd)
|
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
log.info('(%s) Calling command %r for %s', irc.name, cmd, utils.getHostmask(irc, source))
|
log.info('(%s) Calling command %r for %s', irc.name, cmd, utils.getHostmask(irc, source))
|
||||||
|
for func in world.bot_commands[cmd]:
|
||||||
|
try:
|
||||||
func(irc, source, cmd_args)
|
func(irc, source, cmd_args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception('Unhandled exception caught in command %r', cmd)
|
log.exception('Unhandled exception caught in command %r', cmd)
|
||||||
@ -75,7 +75,13 @@ def handle_whois(irc, source, command, args):
|
|||||||
# 313: sends a string denoting the target's operator privilege,
|
# 313: sends a string denoting the target's operator privilege,
|
||||||
# only if they have umode +o.
|
# only if they have umode +o.
|
||||||
if ('o', None) in user.modes:
|
if ('o', None) in user.modes:
|
||||||
f(irc, server, 313, source, "%s :is an IRC Operator" % nick)
|
if hasattr(user, 'opertype'):
|
||||||
|
opertype = user.opertype.replace("_", " ")
|
||||||
|
else:
|
||||||
|
opertype = "IRC Operator"
|
||||||
|
# Let's be gramatically correct.
|
||||||
|
n = 'n' if opertype[0].lower() in 'aeiou' else ''
|
||||||
|
f(irc, server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
|
||||||
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
|
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
|
||||||
# Only show this to opers!
|
# Only show this to opers!
|
||||||
if sourceisOper:
|
if sourceisOper:
|
||||||
@ -84,7 +90,7 @@ def handle_whois(irc, source, command, args):
|
|||||||
# idle time, so we simply return 0.
|
# idle time, so we simply return 0.
|
||||||
# <- 317 GL GL 15 1437632859 :seconds idle, signon time
|
# <- 317 GL GL 15 1437632859 :seconds idle, signon time
|
||||||
f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
|
f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
|
||||||
for func in utils.whois_handlers:
|
for func in world.whois_handlers:
|
||||||
# Iterate over custom plugin WHOIS handlers. They return a tuple
|
# Iterate over custom plugin WHOIS handlers. They return a tuple
|
||||||
# or list with two arguments: the numeric, and the text to send.
|
# or list with two arguments: the numeric, and the text to send.
|
||||||
try:
|
try:
|
||||||
|
261
main.py
261
main.py
@ -2,258 +2,17 @@
|
|||||||
|
|
||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
|
||||||
import threading
|
|
||||||
import ssl
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from log import log
|
# This must be done before conf imports, so we get the real conf instead of testing one.
|
||||||
|
import world
|
||||||
|
world.testing = False
|
||||||
|
|
||||||
import conf
|
import conf
|
||||||
|
from log import log
|
||||||
import classes
|
import classes
|
||||||
import utils
|
|
||||||
import coreplugin
|
import coreplugin
|
||||||
|
|
||||||
class Irc():
|
|
||||||
|
|
||||||
def initVars(self):
|
|
||||||
self.pseudoclient = None
|
|
||||||
self.connected = threading.Event()
|
|
||||||
self.lastping = time.time()
|
|
||||||
# Server, channel, and user indexes to be populated by our protocol module
|
|
||||||
self.servers = {self.sid: classes.IrcServer(None, self.serverdata['hostname'], internal=True)}
|
|
||||||
self.users = {}
|
|
||||||
self.channels = defaultdict(classes.IrcChannel)
|
|
||||||
# Sets flags such as whether to use halfops, etc. The default RFC1459
|
|
||||||
# modes are implied.
|
|
||||||
self.cmodes = {'op': 'o', 'secret': 's', 'private': 'p',
|
|
||||||
'noextmsg': 'n', 'moderated': 'm', 'inviteonly': 'i',
|
|
||||||
'topiclock': 't', 'limit': 'l', 'ban': 'b',
|
|
||||||
'voice': 'v', 'key': 'k',
|
|
||||||
# Type A, B, and C modes
|
|
||||||
'*A': 'b',
|
|
||||||
'*B': 'k',
|
|
||||||
'*C': 'l',
|
|
||||||
'*D': 'imnpstr'}
|
|
||||||
self.umodes = {'invisible': 'i', 'snomask': 's', 'wallops': 'w',
|
|
||||||
'oper': 'o',
|
|
||||||
'*A': '', '*B': '', '*C': 's', '*D': 'iow'}
|
|
||||||
|
|
||||||
# This max nick length starts off as the config value, but may be
|
|
||||||
# overwritten later by the protocol module if such information is
|
|
||||||
# received. Note that only some IRCds (InspIRCd) give us nick length
|
|
||||||
# during link, so it is still required that the config value be set!
|
|
||||||
self.maxnicklen = self.serverdata['maxnicklen']
|
|
||||||
self.prefixmodes = {'o': '@', 'v': '+'}
|
|
||||||
|
|
||||||
# Uplink SID (filled in by protocol module)
|
|
||||||
self.uplink = None
|
|
||||||
self.start_ts = int(time.time())
|
|
||||||
|
|
||||||
# UID generators, for servers that need it
|
|
||||||
self.uidgen = {}
|
|
||||||
|
|
||||||
def __init__(self, netname, proto, conf):
|
|
||||||
# Initialize some variables
|
|
||||||
self.name = netname.lower()
|
|
||||||
self.conf = conf
|
|
||||||
self.serverdata = conf['servers'][netname]
|
|
||||||
self.sid = self.serverdata["sid"]
|
|
||||||
self.botdata = conf['bot']
|
|
||||||
self.proto = proto
|
|
||||||
self.pingfreq = self.serverdata.get('pingfreq') or 30
|
|
||||||
self.pingtimeout = self.pingfreq * 2
|
|
||||||
|
|
||||||
self.initVars()
|
|
||||||
|
|
||||||
self.connection_thread = threading.Thread(target = self.connect)
|
|
||||||
self.connection_thread.start()
|
|
||||||
self.pingTimer = None
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
ip = self.serverdata["ip"]
|
|
||||||
port = self.serverdata["port"]
|
|
||||||
while True:
|
|
||||||
self.initVars()
|
|
||||||
checks_ok = True
|
|
||||||
try:
|
|
||||||
self.socket = socket.socket()
|
|
||||||
self.socket.setblocking(0)
|
|
||||||
# Initial connection timeout is a lot smaller than the timeout after
|
|
||||||
# we've connected; this is intentional.
|
|
||||||
self.socket.settimeout(self.pingfreq)
|
|
||||||
self.ssl = self.serverdata.get('ssl')
|
|
||||||
if self.ssl:
|
|
||||||
log.info('(%s) Attempting SSL for this connection...', self.name)
|
|
||||||
certfile = self.serverdata.get('ssl_certfile')
|
|
||||||
keyfile = self.serverdata.get('ssl_keyfile')
|
|
||||||
if certfile and keyfile:
|
|
||||||
try:
|
|
||||||
self.socket = ssl.wrap_socket(self.socket,
|
|
||||||
certfile=certfile,
|
|
||||||
keyfile=keyfile)
|
|
||||||
except OSError:
|
|
||||||
log.exception('(%s) Caught OSError trying to '
|
|
||||||
'initialize the SSL connection; '
|
|
||||||
'are "ssl_certfile" and '
|
|
||||||
'"ssl_keyfile" set correctly?',
|
|
||||||
self.name)
|
|
||||||
checks_ok = False
|
|
||||||
else:
|
|
||||||
log.error('(%s) SSL certfile/keyfile was not set '
|
|
||||||
'correctly, aborting... ', self.name)
|
|
||||||
checks_ok = False
|
|
||||||
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
|
||||||
self.socket.connect((ip, port))
|
|
||||||
self.socket.settimeout(self.pingtimeout)
|
|
||||||
|
|
||||||
if self.ssl and checks_ok:
|
|
||||||
peercert = self.socket.getpeercert(binary_form=True)
|
|
||||||
sha1fp = hashlib.sha1(peercert).hexdigest()
|
|
||||||
expected_fp = self.serverdata.get('ssl_fingerprint')
|
|
||||||
if expected_fp:
|
|
||||||
if sha1fp != expected_fp:
|
|
||||||
log.error('(%s) Uplink\'s SSL certificate '
|
|
||||||
'fingerprint (SHA1) does not match the '
|
|
||||||
'one configured: expected %r, got %r; '
|
|
||||||
'disconnecting...', self.name,
|
|
||||||
expected_fp, sha1fp)
|
|
||||||
checks_ok = False
|
|
||||||
else:
|
|
||||||
log.info('(%s) Uplink SSL certificate fingerprint '
|
|
||||||
'(SHA1) verified: %r', self.name, sha1fp)
|
|
||||||
else:
|
|
||||||
log.info('(%s) Uplink\'s SSL certificate fingerprint '
|
|
||||||
'is %r. You can enhance the security of your '
|
|
||||||
'link by specifying this in a "ssl_fingerprint"'
|
|
||||||
' option in your server block.', self.name,
|
|
||||||
sha1fp)
|
|
||||||
|
|
||||||
if checks_ok:
|
|
||||||
self.proto.connect(self)
|
|
||||||
self.spawnMain()
|
|
||||||
log.info('(%s) Starting ping schedulers....', self.name)
|
|
||||||
self.schedulePing()
|
|
||||||
log.info('(%s) Server ready; listening for data.', self.name)
|
|
||||||
self.run()
|
|
||||||
else:
|
|
||||||
log.error('(%s) A configuration error was encountered '
|
|
||||||
'trying to set up this connection. Please check'
|
|
||||||
' your configuration file and try again.',
|
|
||||||
self.name)
|
|
||||||
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
|
||||||
log.warning('(%s) Disconnected from IRC: %s: %s',
|
|
||||||
self.name, type(e).__name__, str(e))
|
|
||||||
self.disconnect()
|
|
||||||
autoconnect = self.serverdata.get('autoconnect')
|
|
||||||
log.debug('(%s) Autoconnect delay set to %s seconds.', self.name, autoconnect)
|
|
||||||
if autoconnect is not None and autoconnect >= 0:
|
|
||||||
log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect)
|
|
||||||
time.sleep(autoconnect)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
log.debug('(%s) Canceling pingTimer at %s due to disconnect() call', self.name, time.time())
|
|
||||||
self.connected.clear()
|
|
||||||
try:
|
|
||||||
self.socket.close()
|
|
||||||
self.pingTimer.cancel()
|
|
||||||
except: # Socket timed out during creation; ignore
|
|
||||||
pass
|
|
||||||
# Internal hook signifying that a network has disconnected.
|
|
||||||
self.callHooks([None, 'PYLINK_DISCONNECT', {}])
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
buf = b""
|
|
||||||
data = b""
|
|
||||||
while True:
|
|
||||||
data = self.socket.recv(2048)
|
|
||||||
buf += data
|
|
||||||
if self.connected.is_set() and not data:
|
|
||||||
log.warning('(%s) No data received and self.connected is set; disconnecting!', self.name)
|
|
||||||
return
|
|
||||||
elif (time.time() - self.lastping) > self.pingtimeout:
|
|
||||||
log.warning('(%s) Connection timed out.', self.name)
|
|
||||||
return
|
|
||||||
while b'\n' in buf:
|
|
||||||
line, buf = buf.split(b'\n', 1)
|
|
||||||
line = line.strip(b'\r')
|
|
||||||
# TODO: respect other encodings?
|
|
||||||
line = line.decode("utf-8", "replace")
|
|
||||||
log.debug("(%s) <- %s", self.name, line)
|
|
||||||
hook_args = None
|
|
||||||
try:
|
|
||||||
hook_args = self.proto.handle_events(self, line)
|
|
||||||
except Exception:
|
|
||||||
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
|
|
||||||
return
|
|
||||||
# Only call our hooks if there's data to process. Handlers that support
|
|
||||||
# hooks will return a dict of parsed arguments, which can be passed on
|
|
||||||
# to plugins and the like. For example, the JOIN handler will return
|
|
||||||
# something like: {'channel': '#whatever', 'users': ['UID1', 'UID2',
|
|
||||||
# 'UID3']}, etc.
|
|
||||||
if hook_args is not None:
|
|
||||||
self.callHooks(hook_args)
|
|
||||||
|
|
||||||
def callHooks(self, hook_args):
|
|
||||||
numeric, command, parsed_args = hook_args
|
|
||||||
# Always make sure TS is sent.
|
|
||||||
if 'ts' not in parsed_args:
|
|
||||||
parsed_args['ts'] = int(time.time())
|
|
||||||
hook_cmd = command
|
|
||||||
hook_map = self.proto.hook_map
|
|
||||||
# Handlers can return a 'parse_as' key to send their payload to a
|
|
||||||
# different hook. An example of this is "/join 0" being interpreted
|
|
||||||
# as leaving all channels (PART).
|
|
||||||
if command in hook_map:
|
|
||||||
hook_cmd = hook_map[command]
|
|
||||||
hook_cmd = parsed_args.get('parse_as') or hook_cmd
|
|
||||||
log.debug('Parsed args %r received from %s handler (calling hook %s)', parsed_args, command, hook_cmd)
|
|
||||||
# Iterate over hooked functions, catching errors accordingly
|
|
||||||
for hook_func in utils.command_hooks[hook_cmd]:
|
|
||||||
try:
|
|
||||||
log.debug('Calling function %s', hook_func)
|
|
||||||
hook_func(self, numeric, command, parsed_args)
|
|
||||||
except Exception:
|
|
||||||
# We don't want plugins to crash our servers...
|
|
||||||
log.exception('Unhandled exception caught in %r' % hook_func)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def send(self, data):
|
|
||||||
# Safeguard against newlines in input!! Otherwise, each line gets
|
|
||||||
# treated as a separate command, which is particularly nasty.
|
|
||||||
data = data.replace('\n', ' ')
|
|
||||||
data = data.encode("utf-8") + b"\n"
|
|
||||||
stripped_data = data.decode("utf-8").strip("\n")
|
|
||||||
log.debug("(%s) -> %s", self.name, stripped_data)
|
|
||||||
try:
|
|
||||||
self.socket.send(data)
|
|
||||||
except (OSError, AttributeError):
|
|
||||||
log.debug("(%s) Dropping message %r; network isn't connected!", self.name, stripped_data)
|
|
||||||
|
|
||||||
def schedulePing(self):
|
|
||||||
self.proto.pingServer(self)
|
|
||||||
self.pingTimer = threading.Timer(self.pingfreq, self.schedulePing)
|
|
||||||
self.pingTimer.daemon = True
|
|
||||||
self.pingTimer.start()
|
|
||||||
log.debug('(%s) Ping scheduled at %s', self.name, time.time())
|
|
||||||
|
|
||||||
def spawnMain(self):
|
|
||||||
nick = self.botdata.get('nick') or 'PyLink'
|
|
||||||
ident = self.botdata.get('ident') or 'pylink'
|
|
||||||
host = self.serverdata["hostname"]
|
|
||||||
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
|
|
||||||
olduserobj = self.pseudoclient
|
|
||||||
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)})
|
|
||||||
for chan in self.serverdata['channels']:
|
|
||||||
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
|
||||||
# PyLink internal hook called when spawnMain is called and the
|
|
||||||
# contents of Irc().pseudoclient change.
|
|
||||||
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log.info('PyLink starting...')
|
log.info('PyLink starting...')
|
||||||
if conf.conf['login']['password'] == 'changeme':
|
if conf.conf['login']['password'] == 'changeme':
|
||||||
@ -267,7 +26,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Import plugins first globally, because they can listen for events
|
# Import plugins first globally, because they can listen for events
|
||||||
# that happen before the connection phase.
|
# that happen before the connection phase.
|
||||||
utils.plugins.append(coreplugin)
|
world.plugins.append(coreplugin)
|
||||||
to_load = conf.conf['plugins']
|
to_load = conf.conf['plugins']
|
||||||
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
|
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
|
||||||
# Here, we override the module lookup and import the plugins
|
# Here, we override the module lookup and import the plugins
|
||||||
@ -276,7 +35,7 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
moduleinfo = imp.find_module(plugin, plugins_folder)
|
moduleinfo = imp.find_module(plugin, plugins_folder)
|
||||||
pl = imp.load_source(plugin, moduleinfo[1])
|
pl = imp.load_source(plugin, moduleinfo[1])
|
||||||
utils.plugins.append(pl)
|
world.plugins.append(pl)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if str(e) == ('No module named %r' % plugin):
|
if str(e) == ('No module named %r' % plugin):
|
||||||
log.error('Failed to load plugin %r: The plugin could not be found.', plugin)
|
log.error('Failed to load plugin %r: The plugin could not be found.', plugin)
|
||||||
@ -299,7 +58,7 @@ if __name__ == '__main__':
|
|||||||
log.critical('Failed to load protocol module: ImportError: %s', protoname, str(e))
|
log.critical('Failed to load protocol module: ImportError: %s', protoname, str(e))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
else:
|
||||||
utils.networkobjects[network] = Irc(network, proto, conf.conf)
|
world.networkobjects[network] = classes.Irc(network, proto)
|
||||||
utils.started.set()
|
world.started.set()
|
||||||
log.info("loaded plugins: %s", utils.plugins)
|
log.info("loaded plugins: %s", world.plugins)
|
||||||
|
|
||||||
|
@ -1,47 +1,22 @@
|
|||||||
# admin.py: PyLink administrative commands
|
# admin.py: PyLink administrative commands
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import inspect
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
class NotAuthenticatedError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def checkauthenticated(irc, source):
|
|
||||||
lastfunc = inspect.stack()[1][3]
|
|
||||||
if not irc.users[source].identified:
|
|
||||||
log.warning('(%s) Access denied for %s calling %r', irc.name,
|
|
||||||
utils.getHostmask(irc, source), lastfunc)
|
|
||||||
raise NotAuthenticatedError("You are not authenticated!")
|
|
||||||
|
|
||||||
def _exec(irc, source, args):
|
|
||||||
"""<code>
|
|
||||||
|
|
||||||
Admin-only. Executes <code> in the current PyLink instance.
|
|
||||||
\x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02"""
|
|
||||||
checkauthenticated(irc, source)
|
|
||||||
args = ' '.join(args)
|
|
||||||
if not args.strip():
|
|
||||||
utils.msg(irc, source, 'No code entered!')
|
|
||||||
return
|
|
||||||
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
|
||||||
exec(args, globals(), locals())
|
|
||||||
utils.add_cmd(_exec, 'exec')
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def spawnclient(irc, source, args):
|
def spawnclient(irc, source, args):
|
||||||
"""<nick> <ident> <host>
|
"""<nick> <ident> <host>
|
||||||
|
|
||||||
Admin-only. Spawns the specified PseudoClient on the PyLink server.
|
Admin-only. Spawns the specified PseudoClient on the PyLink server.
|
||||||
Note: this doesn't check the validity of any fields you give it!"""
|
Note: this doesn't check the validity of any fields you give it!"""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick, ident, host = args[:3]
|
nick, ident, host = args[:3]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 3: nick, user, host.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 3: nick, user, host.")
|
||||||
return
|
return
|
||||||
irc.proto.spawnClient(irc, nick, ident, host)
|
irc.proto.spawnClient(irc, nick, ident, host)
|
||||||
|
|
||||||
@ -50,17 +25,17 @@ def quit(irc, source, args):
|
|||||||
"""<target> [<reason>]
|
"""<target> [<reason>]
|
||||||
|
|
||||||
Admin-only. Quits the PyLink client with nick <target>, if one exists."""
|
Admin-only. Quits the PyLink client with nick <target>, if one exists."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1-2: nick, reason (optional).")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).")
|
||||||
return
|
return
|
||||||
if irc.pseudoclient.uid == utils.nickToUid(irc, nick):
|
if irc.pseudoclient.uid == utils.nickToUid(irc, nick):
|
||||||
utils.msg(irc, source, "Error: cannot quit the main PyLink PseudoClient!")
|
utils.msg(irc, source, "Error: Cannot quit the main PyLink PseudoClient!")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
quitmsg = ' '.join(args[1:]) or 'Client quit'
|
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
||||||
irc.proto.quitClient(irc, u, quitmsg)
|
irc.proto.quitClient(irc, u, quitmsg)
|
||||||
irc.callHooks([u, 'PYLINK_ADMIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
irc.callHooks([u, 'PYLINK_ADMIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
||||||
|
|
||||||
@ -68,14 +43,14 @@ def joinclient(irc, source, args):
|
|||||||
"""<target> <channel1>,[<channel2>], etc.
|
"""<target> <channel1>,[<channel2>], etc.
|
||||||
|
|
||||||
Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels."""
|
Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
clist = args[1].split(',')
|
clist = args[1].split(',')
|
||||||
if not clist:
|
if not clist:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 2: nick, comma separated list of channels.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
@ -93,12 +68,12 @@ def nick(irc, source, args):
|
|||||||
"""<target> <newnick>
|
"""<target> <newnick>
|
||||||
|
|
||||||
Admin-only. Changes the nick of <target>, a PyLink client, to <newnick>."""
|
Admin-only. Changes the nick of <target>, a PyLink client, to <newnick>."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
newnick = args[1]
|
newnick = args[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 2: nick, newnick.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, newnick.")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
if newnick in ('0', u):
|
if newnick in ('0', u):
|
||||||
@ -114,13 +89,13 @@ def part(irc, source, args):
|
|||||||
"""<target> <channel1>,[<channel2>],... [<reason>]
|
"""<target> <channel1>,[<channel2>],... [<reason>]
|
||||||
|
|
||||||
Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels."""
|
Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
clist = args[1].split(',')
|
clist = args[1].split(',')
|
||||||
reason = ' '.join(args[2:])
|
reason = ' '.join(args[2:])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 2: nick, comma separated list of channels.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
@ -135,14 +110,14 @@ def kick(irc, source, args):
|
|||||||
"""<source> <channel> <user> [<reason>]
|
"""<source> <channel> <user> [<reason>]
|
||||||
|
|
||||||
Admin-only. Kicks <user> from <channel> via <source>, where <source> is the nick of a PyLink client."""
|
Admin-only. Kicks <user> from <channel> via <source>, where <source> is the nick of a PyLink client."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
target = args[2]
|
target = args[2]
|
||||||
reason = ' '.join(args[3:])
|
reason = ' '.join(args[3:])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
|
||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick) or nick
|
u = utils.nickToUid(irc, nick) or nick
|
||||||
targetu = utils.nickToUid(irc, target)
|
targetu = utils.nickToUid(irc, target)
|
||||||
@ -155,38 +130,19 @@ def kick(irc, source, args):
|
|||||||
irc.proto.kickClient(irc, u, channel, targetu, reason)
|
irc.proto.kickClient(irc, u, channel, targetu, reason)
|
||||||
irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])
|
irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
|
||||||
def showuser(irc, source, args):
|
|
||||||
"""<user>
|
|
||||||
|
|
||||||
Admin-only. Shows information about <user>."""
|
|
||||||
checkauthenticated(irc, source)
|
|
||||||
try:
|
|
||||||
target = args[0]
|
|
||||||
except IndexError:
|
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1: nick.")
|
|
||||||
return
|
|
||||||
u = utils.nickToUid(irc, target)
|
|
||||||
if u is None:
|
|
||||||
utils.msg(irc, source, 'Error: unknown user %r' % target)
|
|
||||||
return
|
|
||||||
s = ['\x02%s\x02: %s' % (k, v) for k, v in sorted(irc.users[u].__dict__.items())]
|
|
||||||
s = 'Information on user \x02%s\x02: %s' % (target, '; '.join(s))
|
|
||||||
utils.msg(irc, source, s)
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def showchan(irc, source, args):
|
def showchan(irc, source, args):
|
||||||
"""<channel>
|
"""<channel>
|
||||||
|
|
||||||
Admin-only. Shows information about <channel>."""
|
Admin-only. Shows information about <channel>."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
channel = args[0].lower()
|
channel = args[0].lower()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1: channel.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if channel not in irc.channels:
|
if channel not in irc.channels:
|
||||||
utils.msg(irc, source, 'Error: unknown channel %r' % channel)
|
utils.msg(irc, source, 'Error: Unknown channel %r.' % channel)
|
||||||
return
|
return
|
||||||
s = ['\x02%s\x02: %s' % (k, v) for k, v in sorted(irc.channels[channel].__dict__.items())]
|
s = ['\x02%s\x02: %s' % (k, v) for k, v in sorted(irc.channels[channel].__dict__.items())]
|
||||||
s = 'Information on channel \x02%s\x02: %s' % (channel, '; '.join(s))
|
s = 'Information on channel \x02%s\x02: %s' % (channel, '; '.join(s))
|
||||||
@ -197,14 +153,14 @@ def mode(irc, source, args):
|
|||||||
"""<source> <target> <modes>
|
"""<source> <target> <modes>
|
||||||
|
|
||||||
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is either the nick of a PyLink client, or the SID of a PyLink server."""
|
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is either the nick of a PyLink client, or the SID of a PyLink server."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
modesource, target, modes = args[0], args[1], args[2:]
|
modesource, target, modes = args[0], args[1], args[2:]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, 'Error: not enough arguments. Needs 3: source nick, target, modes to set.')
|
utils.msg(irc, source, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
|
||||||
return
|
return
|
||||||
if not modes:
|
if not modes:
|
||||||
utils.msg(irc, source, "Error: no modes given to set!")
|
utils.msg(irc, source, "Error: No modes given to set!")
|
||||||
return
|
return
|
||||||
parsedmodes = utils.parseModes(irc, target, modes)
|
parsedmodes = utils.parseModes(irc, target, modes)
|
||||||
targetuid = utils.nickToUid(irc, target)
|
targetuid = utils.nickToUid(irc, target)
|
||||||
@ -226,25 +182,25 @@ def msg(irc, source, args):
|
|||||||
"""<source> <target> <text>
|
"""<source> <target> <text>
|
||||||
|
|
||||||
Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client."""
|
Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client."""
|
||||||
checkauthenticated(irc, source)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
|
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, 'Error: not enough arguments. Needs 3: source nick, target, text.')
|
utils.msg(irc, source, 'Error: Not enough arguments. Needs 3: source nick, target, text.')
|
||||||
return
|
return
|
||||||
sourceuid = utils.nickToUid(irc, msgsource)
|
sourceuid = utils.nickToUid(irc, msgsource)
|
||||||
if not sourceuid:
|
if not sourceuid:
|
||||||
utils.msg(irc, source, 'Error: unknown user %r' % msgsource)
|
utils.msg(irc, source, 'Error: Unknown user %r.' % msgsource)
|
||||||
return
|
return
|
||||||
if not utils.isChannel(target):
|
if not utils.isChannel(target):
|
||||||
real_target = utils.nickToUid(irc, target)
|
real_target = utils.nickToUid(irc, target)
|
||||||
if real_target is None:
|
if real_target is None:
|
||||||
utils.msg(irc, source, 'Error: unknown user %r' % target)
|
utils.msg(irc, source, 'Error: Unknown user %r.' % target)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
real_target = target
|
real_target = target
|
||||||
if not text:
|
if not text:
|
||||||
utils.msg(irc, source, 'Error: no text given.')
|
utils.msg(irc, source, 'Error: No text given.')
|
||||||
return
|
return
|
||||||
irc.proto.messageClient(irc, sourceuid, real_target, text)
|
irc.proto.messageClient(irc, sourceuid, real_target, text)
|
||||||
irc.callHooks([sourceuid, 'PYLINK_ADMIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
irc.callHooks([sourceuid, 'PYLINK_ADMIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
# commands.py: base PyLink commands
|
# commands.py: base PyLink commands
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
from time import ctime
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
import utils
|
import utils
|
||||||
from conf import conf
|
from conf import conf
|
||||||
from log import log
|
from log import log
|
||||||
|
import world
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def status(irc, source, args):
|
def status(irc, source, args):
|
||||||
@ -27,7 +29,7 @@ def identify(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
username, password = args[0], args[1]
|
username, password = args[0], args[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, 'Error: not enough arguments.')
|
utils.msg(irc, source, 'Error: Not enough arguments.')
|
||||||
return
|
return
|
||||||
# Usernames are case-insensitive, passwords are NOT.
|
# Usernames are case-insensitive, passwords are NOT.
|
||||||
if username.lower() == conf['login']['user'].lower() and password == conf['login']['password']:
|
if username.lower() == conf['login']['user'].lower() and password == conf['login']['password']:
|
||||||
@ -37,7 +39,7 @@ def identify(irc, source, args):
|
|||||||
log.info("(%s) Successful login to %r by %s.",
|
log.info("(%s) Successful login to %r by %s.",
|
||||||
irc.name, username, utils.getHostmask(irc, source))
|
irc.name, username, utils.getHostmask(irc, source))
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Incorrect credentials.')
|
utils.msg(irc, source, 'Error: Incorrect credentials.')
|
||||||
u = irc.users[source]
|
u = irc.users[source]
|
||||||
log.warning("(%s) Failed login to %r from %s.",
|
log.warning("(%s) Failed login to %r from %s.",
|
||||||
irc.name, username, utils.getHostmask(irc, source))
|
irc.name, username, utils.getHostmask(irc, source))
|
||||||
@ -46,8 +48,12 @@ def listcommands(irc, source, args):
|
|||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
|
|
||||||
Returns a list of available commands PyLink has to offer."""
|
Returns a list of available commands PyLink has to offer."""
|
||||||
cmds = list(utils.bot_commands.keys())
|
cmds = list(world.bot_commands.keys())
|
||||||
cmds.sort()
|
cmds.sort()
|
||||||
|
for idx, cmd in enumerate(cmds):
|
||||||
|
nfuncs = len(world.bot_commands[cmd])
|
||||||
|
if nfuncs > 1:
|
||||||
|
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
|
||||||
utils.msg(irc, source, 'Available commands include: %s' % ', '.join(cmds))
|
utils.msg(irc, source, 'Available commands include: %s' % ', '.join(cmds))
|
||||||
utils.msg(irc, source, 'To see help on a specific command, type \x02help <command>\x02.')
|
utils.msg(irc, source, 'To see help on a specific command, type \x02help <command>\x02.')
|
||||||
utils.add_cmd(listcommands, 'list')
|
utils.add_cmd(listcommands, 'list')
|
||||||
@ -62,20 +68,60 @@ def help(irc, source, args):
|
|||||||
except IndexError: # No argument given, just return 'list' output
|
except IndexError: # No argument given, just return 'list' output
|
||||||
listcommands(irc, source, args)
|
listcommands(irc, source, args)
|
||||||
return
|
return
|
||||||
try:
|
if command not in world.bot_commands:
|
||||||
func = utils.bot_commands[command]
|
utils.msg(irc, source, 'Error: Unknown command %r.' % command)
|
||||||
except KeyError:
|
|
||||||
utils.msg(irc, source, 'Error: no such command %r.' % command)
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
funcs = world.bot_commands[command]
|
||||||
|
if len(funcs) > 1:
|
||||||
|
utils.msg(irc, source, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
|
||||||
|
% (len(funcs), command, ', '.join([func.__module__ for func in funcs])))
|
||||||
|
for func in funcs:
|
||||||
doc = func.__doc__
|
doc = func.__doc__
|
||||||
|
mod = func.__module__
|
||||||
if doc:
|
if doc:
|
||||||
lines = doc.split('\n')
|
lines = doc.split('\n')
|
||||||
# Bold the first line, which usually just tells you what
|
# Bold the first line, which usually just tells you what
|
||||||
# arguments the command takes.
|
# arguments the command takes.
|
||||||
lines[0] = '\x02%s %s\x02' % (command, lines[0])
|
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
utils.msg(irc, source, line.strip())
|
utils.msg(irc, source, line.strip())
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: Command %r doesn\'t offer any help.' % command)
|
utils.msg(irc, source, "Error: Command %r (from plugin %r) "
|
||||||
|
"doesn't offer any help." % (command, mod))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def showuser(irc, source, args):
|
||||||
|
"""<user>
|
||||||
|
|
||||||
|
Shows information about <user>."""
|
||||||
|
try:
|
||||||
|
target = args[0]
|
||||||
|
except IndexError:
|
||||||
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: nick.")
|
||||||
|
return
|
||||||
|
u = utils.nickToUid(irc, target) or target
|
||||||
|
# Only show private info if the person is calling 'showuser' on themselves,
|
||||||
|
# or is an oper.
|
||||||
|
verbose = utils.isOper(irc, source) or u == source
|
||||||
|
if u not in irc.users:
|
||||||
|
utils.msg(irc, source, 'Error: Unknown user %r.' % target)
|
||||||
|
return
|
||||||
|
|
||||||
|
f = lambda s: utils.msg(irc, source, s)
|
||||||
|
userobj = irc.users[u]
|
||||||
|
f('Information on user \x02%s\x02 (%s@%s): %s' % (userobj.nick, userobj.ident,
|
||||||
|
userobj.host, userobj.realname))
|
||||||
|
sid = utils.clientToServer(irc, u)
|
||||||
|
serverobj = irc.servers[sid]
|
||||||
|
ts = userobj.ts
|
||||||
|
f('\x02Home server\x02: %s (%s); \x02Signon time:\x02 %s (%s)' % \
|
||||||
|
(serverobj.name, sid, ctime(float(ts)), ts))
|
||||||
|
if verbose:
|
||||||
|
f('\x02Protocol UID\x02: %s; \x02PyLink identification\x02: %s' % \
|
||||||
|
(u, userobj.identified))
|
||||||
|
f('\x02User modes\x02: %s' % utils.joinModes(userobj.modes))
|
||||||
|
f('\x02Real host\x02: %s; \x02IP\x02: %s; \x02Away status\x02: %s' % \
|
||||||
|
(userobj.realhost, userobj.ip, userobj.away or '\x1D(not set)\x1D'))
|
||||||
|
f('\x02Channels\x02: %s' % (' '.join(userobj.channels).strip() or '\x1D(none)\x1D'))
|
||||||
|
21
plugins/exec.py
Executable file
21
plugins/exec.py
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
# exec.py: Provides an 'exec' command to execute raw code
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
import utils
|
||||||
|
from log import log
|
||||||
|
|
||||||
|
def _exec(irc, source, args):
|
||||||
|
"""<code>
|
||||||
|
|
||||||
|
Admin-only. Executes <code> in the current PyLink instance.
|
||||||
|
\x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02"""
|
||||||
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
|
args = ' '.join(args)
|
||||||
|
if not args.strip():
|
||||||
|
utils.msg(irc, source, 'No code entered!')
|
||||||
|
return
|
||||||
|
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
||||||
|
exec(args, globals(), locals())
|
||||||
|
utils.add_cmd(_exec, 'exec')
|
229
plugins/relay.py
229
plugins/relay.py
@ -13,6 +13,7 @@ from expiringdict import ExpiringDict
|
|||||||
import utils
|
import utils
|
||||||
from log import log
|
from log import log
|
||||||
from conf import confname
|
from conf import confname
|
||||||
|
import world
|
||||||
|
|
||||||
dbname = "pylinkrelay"
|
dbname = "pylinkrelay"
|
||||||
if confname != 'pylink':
|
if confname != 'pylink':
|
||||||
@ -28,11 +29,11 @@ def relayWhoisHandlers(irc, target):
|
|||||||
orig = getLocalUser(irc, target)
|
orig = getLocalUser(irc, target)
|
||||||
if orig:
|
if orig:
|
||||||
network, remoteuid = orig
|
network, remoteuid = orig
|
||||||
remotenick = utils.networkobjects[network].users[remoteuid].nick
|
remotenick = world.networkobjects[network].users[remoteuid].nick
|
||||||
return [320, "%s :is a remote user connected via PyLink Relay. Home "
|
return [320, "%s :is a remote user connected via PyLink Relay. Home "
|
||||||
"network: %s; Home nick: %s" % (user.nick, network,
|
"network: %s; Home nick: %s" % (user.nick, network,
|
||||||
remotenick)]
|
remotenick)]
|
||||||
utils.whois_handlers.append(relayWhoisHandlers)
|
world.whois_handlers.append(relayWhoisHandlers)
|
||||||
|
|
||||||
def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
||||||
separator = separator or irc.serverdata.get('separator') or "/"
|
separator = separator or irc.serverdata.get('separator') or "/"
|
||||||
@ -94,7 +95,7 @@ def loadDB():
|
|||||||
db = {}
|
db = {}
|
||||||
|
|
||||||
def exportDB(reschedule=False):
|
def exportDB(reschedule=False):
|
||||||
scheduler = utils.schedulers.get('relaydb')
|
scheduler = world.schedulers.get('relaydb')
|
||||||
if reschedule and scheduler:
|
if reschedule and scheduler:
|
||||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||||
log.debug("Relay: exporting links database to %s", dbname)
|
log.debug("Relay: exporting links database to %s", dbname)
|
||||||
@ -110,7 +111,7 @@ def save(irc, source, args):
|
|||||||
exportDB()
|
exportDB()
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: you are not authenticated!')
|
utils.msg(irc, source, 'Error: You are not authenticated!')
|
||||||
return
|
return
|
||||||
|
|
||||||
def getPrefixModes(irc, remoteirc, channel, user):
|
def getPrefixModes(irc, remoteirc, channel, user):
|
||||||
@ -149,16 +150,48 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
|||||||
host = userobj.host[:64]
|
host = userobj.host[:64]
|
||||||
realname = userobj.realname
|
realname = userobj.realname
|
||||||
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
||||||
|
opertype = ''
|
||||||
|
if ('o', None) in userobj.modes:
|
||||||
|
if hasattr(userobj, 'opertype'):
|
||||||
|
# InspIRCd's special OPERTYPE command; this is mandatory
|
||||||
|
# and setting of umode +/-o will fail unless this
|
||||||
|
# is used instead. This also sets an oper type for
|
||||||
|
# the user, which is used in WHOIS, etc.
|
||||||
|
|
||||||
|
# If an opertype exists for the user, add " (remote)"
|
||||||
|
# for the relayed clone, so that it shows in whois.
|
||||||
|
# Janus does this too. :)
|
||||||
|
# OPERTYPE uses underscores instead of spaces, FYI.
|
||||||
|
log.debug('(%s) relay.getRemoteUser: setting OPERTYPE of client for %r to %s',
|
||||||
|
irc.name, user, userobj.opertype)
|
||||||
|
opertype = userobj.opertype + '_(remote)'
|
||||||
|
else:
|
||||||
|
opertype = 'IRC_Operator_(remote)'
|
||||||
|
# Set hideoper on remote opers, to prevent inflating
|
||||||
|
# /lusers and various /stats
|
||||||
|
hideoper_mode = remoteirc.umodes.get('hideoper')
|
||||||
|
if hideoper_mode:
|
||||||
|
modes.append((hideoper_mode, None))
|
||||||
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
||||||
host=host, realname=realname,
|
host=host, realname=realname,
|
||||||
modes=modes, ts=userobj.ts).uid
|
modes=modes, ts=userobj.ts,
|
||||||
|
opertype=opertype).uid
|
||||||
remoteirc.users[u].remote = (irc.name, user)
|
remoteirc.users[u].remote = (irc.name, user)
|
||||||
|
remoteirc.users[u].opertype = opertype
|
||||||
away = userobj.away
|
away = userobj.away
|
||||||
if away:
|
if away:
|
||||||
remoteirc.proto.awayClient(remoteirc, u, away)
|
remoteirc.proto.awayClient(remoteirc, u, away)
|
||||||
relayusers[(irc.name, user)][remoteirc.name] = u
|
relayusers[(irc.name, user)][remoteirc.name] = u
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
def handle_operup(irc, numeric, command, args):
|
||||||
|
newtype = args['text'] + '_(remote)'
|
||||||
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
|
log.debug('(%s) relay.handle_opertype: setting OPERTYPE of %s/%s to %s', irc.name, user, netname, newtype)
|
||||||
|
remoteirc = world.networkobjects[netname]
|
||||||
|
remoteirc.users[user].opertype = newtype
|
||||||
|
utils.add_hook(handle_operup, 'PYLINK_CLIENT_OPERED')
|
||||||
|
|
||||||
def getLocalUser(irc, user, targetirc=None):
|
def getLocalUser(irc, user, targetirc=None):
|
||||||
"""<irc object> <pseudoclient uid> [<target irc object>]
|
"""<irc object> <pseudoclient uid> [<target irc object>]
|
||||||
|
|
||||||
@ -179,7 +212,7 @@ def getLocalUser(irc, user, targetirc=None):
|
|||||||
# If targetirc is given, we'll return simply the UID of the user on the
|
# If targetirc is given, we'll return simply the UID of the user on the
|
||||||
# target network, if it exists. Otherwise, we'll return a tuple
|
# target network, if it exists. Otherwise, we'll return a tuple
|
||||||
# with the home network name and the original user's UID.
|
# with the home network name and the original user's UID.
|
||||||
sourceobj = utils.networkobjects.get(remoteuser[0])
|
sourceobj = world.networkobjects.get(remoteuser[0])
|
||||||
if targetirc and sourceobj:
|
if targetirc and sourceobj:
|
||||||
if remoteuser[0] == targetirc.name:
|
if remoteuser[0] == targetirc.name:
|
||||||
# The user we found's home network happens to be the one being
|
# The user we found's home network happens to be the one being
|
||||||
@ -230,7 +263,7 @@ def initializeChannel(irc, channel):
|
|||||||
remotenet, remotechan = link
|
remotenet, remotechan = link
|
||||||
if remotenet == irc.name:
|
if remotenet == irc.name:
|
||||||
continue
|
continue
|
||||||
remoteirc = utils.networkobjects.get(remotenet)
|
remoteirc = world.networkobjects.get(remotenet)
|
||||||
if remoteirc is None:
|
if remoteirc is None:
|
||||||
continue
|
continue
|
||||||
rc = remoteirc.channels[remotechan]
|
rc = remoteirc.channels[remotechan]
|
||||||
@ -255,12 +288,12 @@ def handle_join(irc, numeric, command, args):
|
|||||||
return
|
return
|
||||||
ts = args['ts']
|
ts = args['ts']
|
||||||
users = set(args['users'])
|
users = set(args['users'])
|
||||||
relayJoins(irc, channel, users, ts)
|
relayJoins(irc, channel, users, ts, burst=False)
|
||||||
utils.add_hook(handle_join, 'JOIN')
|
utils.add_hook(handle_join, 'JOIN')
|
||||||
|
|
||||||
def handle_quit(irc, numeric, command, args):
|
def handle_quit(irc, numeric, command, args):
|
||||||
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
remoteirc.proto.quitClient(remoteirc, user, args['text'])
|
remoteirc.proto.quitClient(remoteirc, user, args['text'])
|
||||||
del relayusers[(irc.name, numeric)]
|
del relayusers[(irc.name, numeric)]
|
||||||
utils.add_hook(handle_quit, 'QUIT')
|
utils.add_hook(handle_quit, 'QUIT')
|
||||||
@ -274,7 +307,7 @@ utils.add_hook(handle_squit, 'SQUIT')
|
|||||||
|
|
||||||
def handle_nick(irc, numeric, command, args):
|
def handle_nick(irc, numeric, command, args):
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
|
newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
|
||||||
if remoteirc.users[user].nick != newnick:
|
if remoteirc.users[user].nick != newnick:
|
||||||
remoteirc.proto.nickClient(remoteirc, user, newnick)
|
remoteirc.proto.nickClient(remoteirc, user, newnick)
|
||||||
@ -288,7 +321,7 @@ def handle_part(irc, numeric, command, args):
|
|||||||
return
|
return
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
if remotechan is None:
|
if remotechan is None:
|
||||||
continue
|
continue
|
||||||
@ -324,7 +357,7 @@ def handle_privmsg(irc, numeric, command, args):
|
|||||||
return
|
return
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
real_target = findRemoteChan(irc, remoteirc, target)
|
real_target = findRemoteChan(irc, remoteirc, target)
|
||||||
if not real_target:
|
if not real_target:
|
||||||
continue
|
continue
|
||||||
@ -343,11 +376,11 @@ def handle_privmsg(irc, numeric, command, args):
|
|||||||
# on the remote network, and we won't have anything to send our
|
# on the remote network, and we won't have anything to send our
|
||||||
# messages from.
|
# messages from.
|
||||||
if homenet not in remoteusers.keys():
|
if homenet not in remoteusers.keys():
|
||||||
utils.msg(irc, numeric, 'Error: you must be in a common channel '
|
utils.msg(irc, numeric, 'Error: You must be in a common channel '
|
||||||
'with %r in order to send messages.' % \
|
'with %r in order to send messages.' % \
|
||||||
irc.users[target].nick, notice=True)
|
irc.users[target].nick, notice=True)
|
||||||
return
|
return
|
||||||
remoteirc = utils.networkobjects[homenet]
|
remoteirc = world.networkobjects[homenet]
|
||||||
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
|
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
|
||||||
if notice:
|
if notice:
|
||||||
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
|
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
|
||||||
@ -367,7 +400,7 @@ def handle_kick(irc, source, command, args):
|
|||||||
if relay is None or target == irc.pseudoclient.uid:
|
if relay is None or target == irc.pseudoclient.uid:
|
||||||
return
|
return
|
||||||
origuser = getLocalUser(irc, target)
|
origuser = getLocalUser(irc, target)
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in world.networkobjects.items():
|
||||||
if irc.name == name or not remoteirc.connected.is_set():
|
if irc.name == name or not remoteirc.connected.is_set():
|
||||||
continue
|
continue
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
@ -400,14 +433,14 @@ def handle_kick(irc, source, command, args):
|
|||||||
# Join the kicked client back with its respective modes.
|
# Join the kicked client back with its respective modes.
|
||||||
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
|
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
|
||||||
if kicker in irc.users:
|
if kicker in irc.users:
|
||||||
log.info('(%s) Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
|
log.info('(%s) Relay claim: Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
|
||||||
irc.name, args['text'], irc.users[source].nick,
|
irc.name, args['text'], irc.users[source].nick,
|
||||||
remoteirc.users[real_target].nick, remoteirc.name, channel)
|
remoteirc.users[real_target].nick, remoteirc.name, channel)
|
||||||
utils.msg(irc, kicker, "This channel is claimed; your kick to "
|
utils.msg(irc, kicker, "This channel is claimed; your kick to "
|
||||||
"%s has been blocked because you are not "
|
"%s has been blocked because you are not "
|
||||||
"(half)opped." % channel, notice=True)
|
"(half)opped." % channel, notice=True)
|
||||||
else:
|
else:
|
||||||
log.info('(%s) Blocked KICK (reason %r) from server %s to relay client %s/%s on %s.',
|
log.info('(%s) Relay claim: Blocked KICK (reason %r) from server %s to relay client %s/%s on %s.',
|
||||||
irc.name, args['text'], irc.servers[source].name,
|
irc.name, args['text'], irc.servers[source].name,
|
||||||
remoteirc.users[real_target].nick, remoteirc.name, channel)
|
remoteirc.users[real_target].nick, remoteirc.name, channel)
|
||||||
return
|
return
|
||||||
@ -458,7 +491,7 @@ def handle_chgclient(irc, source, command, args):
|
|||||||
text = args['newgecos']
|
text = args['newgecos']
|
||||||
if field:
|
if field:
|
||||||
for netname, user in relayusers[(irc.name, target)].items():
|
for netname, user in relayusers[(irc.name, target)].items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
try:
|
try:
|
||||||
remoteirc.proto.updateClient(remoteirc, user, field, text)
|
remoteirc.proto.updateClient(remoteirc, user, field, text)
|
||||||
except NotImplementedError: # IRCd doesn't support changing the field we want
|
except NotImplementedError: # IRCd doesn't support changing the field we want
|
||||||
@ -579,22 +612,30 @@ def getSupportedUmodes(irc, remoteirc, modes):
|
|||||||
else:
|
else:
|
||||||
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
|
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
|
||||||
"the remote network (%s)'s IRCd (%s) doesn't support it.",
|
"the remote network (%s)'s IRCd (%s) doesn't support it.",
|
||||||
irc.name, modechar, arg, remoteirc.name, irc.proto.__name__)
|
irc.name, modechar, arg, remoteirc.name,
|
||||||
|
remoteirc.proto.__name__)
|
||||||
return supported_modes
|
return supported_modes
|
||||||
|
|
||||||
def handle_mode(irc, numeric, command, args):
|
def handle_mode(irc, numeric, command, args):
|
||||||
target = args['target']
|
target = args['target']
|
||||||
modes = args['modes']
|
modes = args['modes']
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in world.networkobjects.items():
|
||||||
if irc.name == name or not remoteirc.connected.is_set():
|
if irc.name == name or not remoteirc.connected.is_set():
|
||||||
continue
|
continue
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
relayModes(irc, remoteirc, numeric, target, modes)
|
relayModes(irc, remoteirc, numeric, target, modes)
|
||||||
else:
|
else:
|
||||||
|
# Set hideoper on remote opers, to prevent inflating
|
||||||
|
# /lusers and various /stats
|
||||||
|
hideoper_mode = remoteirc.umodes.get('hideoper')
|
||||||
modes = getSupportedUmodes(irc, remoteirc, modes)
|
modes = getSupportedUmodes(irc, remoteirc, modes)
|
||||||
|
if hideoper_mode:
|
||||||
|
if ('+o', None) in modes:
|
||||||
|
modes.append(('+%s' % hideoper_mode, None))
|
||||||
|
elif ('-o', None) in modes:
|
||||||
|
modes.append(('-%s' % hideoper_mode, None))
|
||||||
remoteuser = getRemoteUser(irc, remoteirc, target, spawnIfMissing=False)
|
remoteuser = getRemoteUser(irc, remoteirc, target, spawnIfMissing=False)
|
||||||
if remoteuser is None:
|
if remoteuser and modes:
|
||||||
continue
|
|
||||||
remoteirc.proto.modeClient(remoteirc, remoteuser, remoteuser, modes)
|
remoteirc.proto.modeClient(remoteirc, remoteuser, remoteuser, modes)
|
||||||
|
|
||||||
utils.add_hook(handle_mode, 'MODE')
|
utils.add_hook(handle_mode, 'MODE')
|
||||||
@ -602,7 +643,7 @@ utils.add_hook(handle_mode, 'MODE')
|
|||||||
def handle_topic(irc, numeric, command, args):
|
def handle_topic(irc, numeric, command, args):
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
topic = args['topic']
|
topic = args['topic']
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in world.networkobjects.items():
|
||||||
if irc.name == name or not remoteirc.connected.is_set():
|
if irc.name == name or not remoteirc.connected.is_set():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -628,7 +669,7 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
# We don't allow killing over the relay, so we must respawn the affected
|
# We don't allow killing over the relay, so we must respawn the affected
|
||||||
# client and rejoin it to its channels.
|
# client and rejoin it to its channels.
|
||||||
del relayusers[realuser][irc.name]
|
del relayusers[realuser][irc.name]
|
||||||
remoteirc = utils.networkobjects[realuser[0]]
|
remoteirc = world.networkobjects[realuser[0]]
|
||||||
for remotechan in remoteirc.channels.copy():
|
for remotechan in remoteirc.channels.copy():
|
||||||
localchan = findRemoteChan(remoteirc, irc, remotechan)
|
localchan = findRemoteChan(remoteirc, irc, remotechan)
|
||||||
if localchan:
|
if localchan:
|
||||||
@ -637,7 +678,7 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
client = getRemoteUser(remoteirc, irc, realuser[1])
|
client = getRemoteUser(remoteirc, irc, realuser[1])
|
||||||
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
|
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
|
||||||
if userdata and numeric in irc.users:
|
if userdata and numeric in irc.users:
|
||||||
log.info('(%s) Blocked KILL (reason %r) from %s to relay client %s/%s.',
|
log.info('(%s) Relay claim: Blocked KILL (reason %r) from %s to relay client %s/%s.',
|
||||||
irc.name, args['text'], irc.users[numeric].nick,
|
irc.name, args['text'], irc.users[numeric].nick,
|
||||||
remoteirc.users[realuser[1]].nick, realuser[0])
|
remoteirc.users[realuser[1]].nick, realuser[0])
|
||||||
utils.msg(irc, numeric, "Your kill to %s has been blocked "
|
utils.msg(irc, numeric, "Your kill to %s has been blocked "
|
||||||
@ -645,7 +686,7 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
" users over the relay at this time." % \
|
" users over the relay at this time." % \
|
||||||
userdata.nick, notice=True)
|
userdata.nick, notice=True)
|
||||||
else:
|
else:
|
||||||
log.info('(%s) Blocked KILL (reason %r) from server %s to relay client %s/%s.',
|
log.info('(%s) Relay claim: Blocked KILL (reason %r) from server %s to relay client %s/%s.',
|
||||||
irc.name, args['text'], irc.servers[numeric].name,
|
irc.name, args['text'], irc.servers[numeric].name,
|
||||||
remoteirc.users[realuser[1]].nick, realuser[0])
|
remoteirc.users[realuser[1]].nick, realuser[0])
|
||||||
# Target user was local.
|
# Target user was local.
|
||||||
@ -670,7 +711,7 @@ def isRelayClient(irc, user):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def relayJoins(irc, channel, users, ts, burst=True):
|
def relayJoins(irc, channel, users, ts, burst=True):
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in world.networkobjects.items():
|
||||||
queued_users = []
|
queued_users = []
|
||||||
if name == irc.name or not remoteirc.connected.is_set():
|
if name == irc.name or not remoteirc.connected.is_set():
|
||||||
# Don't relay things to their source network...
|
# Don't relay things to their source network...
|
||||||
@ -710,7 +751,7 @@ def relayJoins(irc, channel, users, ts, burst=True):
|
|||||||
remoteirc.proto.joinClient(remoteirc, queued_users[0][1], remotechan)
|
remoteirc.proto.joinClient(remoteirc, queued_users[0][1], remotechan)
|
||||||
|
|
||||||
def relayPart(irc, channel, user):
|
def relayPart(irc, channel, user):
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in world.networkobjects.items():
|
||||||
if name == irc.name or not remoteirc.connected.is_set():
|
if name == irc.name or not remoteirc.connected.is_set():
|
||||||
# Don't relay things to their source network...
|
# Don't relay things to their source network...
|
||||||
continue
|
continue
|
||||||
@ -730,7 +771,7 @@ def removeChannel(irc, channel):
|
|||||||
if irc is None:
|
if irc is None:
|
||||||
return
|
return
|
||||||
if channel not in map(str.lower, irc.serverdata['channels']):
|
if channel not in map(str.lower, irc.serverdata['channels']):
|
||||||
irc.proto.partClient(irc, irc.pseudoclient.uid, channel)
|
irc.proto.partClient(irc, irc.pseudoclient.uid, channel, 'Channel delinked.')
|
||||||
relay = findRelay((irc.name, channel))
|
relay = findRelay((irc.name, channel))
|
||||||
if relay:
|
if relay:
|
||||||
for user in irc.channels[channel].users.copy():
|
for user in irc.channels[channel].users.copy():
|
||||||
@ -756,16 +797,16 @@ def create(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1: channel.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
|
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
if source not in irc.channels[channel].users:
|
if source not in irc.channels[channel].users:
|
||||||
utils.msg(irc, source, 'Error: you must be in %r to complete this operation.' % channel)
|
utils.msg(irc, source, 'Error: You must be in %r to complete this operation.' % channel)
|
||||||
return
|
return
|
||||||
if not utils.isOper(irc, source):
|
if not utils.isOper(irc, source):
|
||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
||||||
initializeChannel(irc, channel)
|
initializeChannel(irc, channel)
|
||||||
@ -779,24 +820,24 @@ def destroy(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1: channel.")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: channel.")
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
|
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
if not utils.isOper(irc, source):
|
if not utils.isOper(irc, source):
|
||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
|
|
||||||
entry = (irc.name, channel)
|
entry = (irc.name, channel)
|
||||||
if entry in db:
|
if entry in db:
|
||||||
for link in db[entry]['links']:
|
for link in db[entry]['links']:
|
||||||
removeChannel(utils.networkobjects.get(link[0]), link[1])
|
removeChannel(world.networkobjects.get(link[0]), link[1])
|
||||||
removeChannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
del db[entry]
|
del db[entry]
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)
|
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
|
||||||
return
|
return
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
@ -809,7 +850,7 @@ def link(irc, source, args):
|
|||||||
channel = utils.toLower(irc, args[1])
|
channel = utils.toLower(irc, args[1])
|
||||||
remotenet = args[0].lower()
|
remotenet = args[0].lower()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
localchan = utils.toLower(irc, args[2])
|
localchan = utils.toLower(irc, args[2])
|
||||||
@ -817,33 +858,33 @@ def link(irc, source, args):
|
|||||||
localchan = channel
|
localchan = channel
|
||||||
for c in (channel, localchan):
|
for c in (channel, localchan):
|
||||||
if not utils.isChannel(c):
|
if not utils.isChannel(c):
|
||||||
utils.msg(irc, source, 'Error: invalid channel %r.' % c)
|
utils.msg(irc, source, 'Error: Invalid channel %r.' % c)
|
||||||
return
|
return
|
||||||
if source not in irc.channels[localchan].users:
|
if source not in irc.channels[localchan].users:
|
||||||
utils.msg(irc, source, 'Error: you must be in %r to complete this operation.' % localchan)
|
utils.msg(irc, source, 'Error: You must be in %r to complete this operation.' % localchan)
|
||||||
return
|
return
|
||||||
if not utils.isOper(irc, source):
|
if not utils.isOper(irc, source):
|
||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
if remotenet not in utils.networkobjects:
|
if remotenet not in world.networkobjects:
|
||||||
utils.msg(irc, source, 'Error: no network named %r exists.' % remotenet)
|
utils.msg(irc, source, 'Error: No network named %r exists.' % remotenet)
|
||||||
return
|
return
|
||||||
localentry = findRelay((irc.name, localchan))
|
localentry = findRelay((irc.name, localchan))
|
||||||
if localentry:
|
if localentry:
|
||||||
utils.msg(irc, source, 'Error: channel %r is already part of a relay.' % localchan)
|
utils.msg(irc, source, 'Error: Channel %r is already part of a relay.' % localchan)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
entry = db[(remotenet, channel)]
|
entry = db[(remotenet, channel)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)
|
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if irc.name in entry['blocked_nets']:
|
if irc.name in entry['blocked_nets']:
|
||||||
utils.msg(irc, source, 'Error: access denied (network is banned from linking to this channel).')
|
utils.msg(irc, source, 'Error: Access denied (network is banned from linking to this channel).')
|
||||||
return
|
return
|
||||||
for link in entry['links']:
|
for link in entry['links']:
|
||||||
if link[0] == irc.name:
|
if link[0] == irc.name:
|
||||||
utils.msg(irc, source, "Error: remote channel '%s%s' is already"
|
utils.msg(irc, source, "Error: Remote channel '%s%s' is already"
|
||||||
" linked here as %r." % (remotenet,
|
" linked here as %r." % (remotenet,
|
||||||
channel, link[1]))
|
channel, link[1]))
|
||||||
return
|
return
|
||||||
@ -860,17 +901,17 @@ def delink(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
channel = utils.toLower(irc, args[0])
|
channel = utils.toLower(irc, args[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
utils.msg(irc, source, "Error: not enough arguments. Needs 1-2: channel, remote netname (optional).")
|
utils.msg(irc, source, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
remotenet = args[1].lower()
|
remotenet = args[1].lower()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
remotenet = None
|
remotenet = None
|
||||||
if not utils.isOper(irc, source):
|
if not utils.isOper(irc, source):
|
||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
|
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
entry = findRelay((irc.name, channel))
|
entry = findRelay((irc.name, channel))
|
||||||
if entry:
|
if entry:
|
||||||
@ -884,18 +925,18 @@ def delink(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
for link in db[entry]['links'].copy():
|
for link in db[entry]['links'].copy():
|
||||||
if link[0] == remotenet:
|
if link[0] == remotenet:
|
||||||
removeChannel(utils.networkobjects.get(remotenet), link[1])
|
removeChannel(world.networkobjects.get(remotenet), link[1])
|
||||||
db[entry]['links'].remove(link)
|
db[entry]['links'].remove(link)
|
||||||
else:
|
else:
|
||||||
removeChannel(irc, channel)
|
removeChannel(irc, channel)
|
||||||
db[entry]['links'].remove((irc.name, channel))
|
db[entry]['links'].remove((irc.name, channel))
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: no such relay %r.' % channel)
|
utils.msg(irc, source, 'Error: No such relay %r.' % channel)
|
||||||
|
|
||||||
def initializeAll(irc):
|
def initializeAll(irc):
|
||||||
log.debug('(%s) initializeAll: waiting for utils.started', irc.name)
|
log.debug('(%s) initializeAll: waiting for world.started', irc.name)
|
||||||
utils.started.wait()
|
world.started.wait()
|
||||||
for chanpair, entrydata in db.items():
|
for chanpair, entrydata in db.items():
|
||||||
network, channel = chanpair
|
network, channel = chanpair
|
||||||
initializeChannel(irc, channel)
|
initializeChannel(irc, channel)
|
||||||
@ -905,7 +946,7 @@ def initializeAll(irc):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
loadDB()
|
loadDB()
|
||||||
utils.schedulers['relaydb'] = scheduler = sched.scheduler()
|
world.schedulers['relaydb'] = scheduler = sched.scheduler()
|
||||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||||
# Thread this because exportDB() queues itself as part of its
|
# Thread this because exportDB() queues itself as part of its
|
||||||
# execution, in order to get a repeating loop.
|
# execution, in order to get a repeating loop.
|
||||||
@ -937,7 +978,7 @@ def handle_save(irc, numeric, command, args):
|
|||||||
# It's one of our relay clients; try to fix our nick to the next
|
# It's one of our relay clients; try to fix our nick to the next
|
||||||
# available normalized nick.
|
# available normalized nick.
|
||||||
remotenet, remoteuser = realuser
|
remotenet, remoteuser = realuser
|
||||||
remoteirc = utils.networkobjects[remotenet]
|
remoteirc = world.networkobjects[remotenet]
|
||||||
nick = remoteirc.users[remoteuser].nick
|
nick = remoteirc.users[remoteuser].nick
|
||||||
# Limit how many times we can attempt to fix our nick, to prevent
|
# Limit how many times we can attempt to fix our nick, to prevent
|
||||||
# floods and such.
|
# floods and such.
|
||||||
@ -963,7 +1004,7 @@ def linked(irc, source, args):
|
|||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
|
|
||||||
Returns a list of channels shared across the relay."""
|
Returns a list of channels shared across the relay."""
|
||||||
networks = list(utils.networkobjects.keys())
|
networks = list(world.networkobjects.keys())
|
||||||
networks.remove(irc.name)
|
networks.remove(irc.name)
|
||||||
s = 'Connected networks: \x02%s\x02 %s' % (irc.name, ' '.join(networks))
|
s = 'Connected networks: \x02%s\x02 %s' % (irc.name, ' '.join(networks))
|
||||||
utils.msg(irc, source, s)
|
utils.msg(irc, source, s)
|
||||||
@ -978,7 +1019,7 @@ def linked(irc, source, args):
|
|||||||
|
|
||||||
def handle_away(irc, numeric, command, args):
|
def handle_away(irc, numeric, command, args):
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = world.networkobjects[netname]
|
||||||
remoteirc.proto.awayClient(remoteirc, user, args['text'])
|
remoteirc.proto.awayClient(remoteirc, user, args['text'])
|
||||||
utils.add_hook(handle_away, 'AWAY')
|
utils.add_hook(handle_away, 'AWAY')
|
||||||
|
|
||||||
@ -989,15 +1030,37 @@ def handle_spawnmain(irc, numeric, command, args):
|
|||||||
initializeAll(irc)
|
initializeAll(irc)
|
||||||
utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN')
|
utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN')
|
||||||
|
|
||||||
|
def handle_invite(irc, source, command, args):
|
||||||
|
target = args['target']
|
||||||
|
channel = args['channel']
|
||||||
|
if isRelayClient(irc, target):
|
||||||
|
remotenet, remoteuser = getLocalUser(irc, target)
|
||||||
|
remoteirc = world.networkobjects[remotenet]
|
||||||
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
|
remotesource = getRemoteUser(irc, remoteirc, source, spawnIfMissing=False)
|
||||||
|
if remotesource is None:
|
||||||
|
utils.msg(irc, source, 'Error: You must be in a common channel '
|
||||||
|
'with %s to invite them to channels.' % \
|
||||||
|
irc.users[target].nick,
|
||||||
|
notice=True)
|
||||||
|
elif remotechan is None:
|
||||||
|
utils.msg(irc, source, 'Error: You cannot invite someone to a '
|
||||||
|
'channel not on their network!',
|
||||||
|
notice=True)
|
||||||
|
else:
|
||||||
|
remoteirc.proto.inviteClient(remoteirc, remotesource, remoteuser,
|
||||||
|
remotechan)
|
||||||
|
utils.add_hook(handle_invite, 'INVITE')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def linkacl(irc, source, args):
|
def linkacl(irc, source, args):
|
||||||
"""ALLOW|DENY|LIST <channel> <remotenet>
|
"""ALLOW|DENY|LIST <channel> <remotenet>
|
||||||
|
|
||||||
Allows blocking / unblocking certain networks from linking to a relay, based on a blacklist.
|
Allows blocking / unblocking certain networks from linking to a relay, based on a blacklist.
|
||||||
LINKACL LIST returns a list of blocked networks for a channel, while the ALLOW and DENY subcommands allow manipulating this blacklist."""
|
LINKACL LIST returns a list of blocked networks for a channel, while the ALLOW and DENY subcommands allow manipulating this blacklist."""
|
||||||
missingargs = "Error: not enough arguments. Needs 2-3: subcommand (ALLOW/DENY/LIST), channel, remote network (for ALLOW/DENY)."
|
missingargs = "Error: Not enough arguments. Needs 2-3: subcommand (ALLOW/DENY/LIST), channel, remote network (for ALLOW/DENY)."
|
||||||
if not utils.isOper(irc, source):
|
if not utils.isOper(irc, source):
|
||||||
utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.')
|
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
cmd = args[0].lower()
|
cmd = args[0].lower()
|
||||||
@ -1006,11 +1069,11 @@ def linkacl(irc, source, args):
|
|||||||
utils.msg(irc, source, missingargs)
|
utils.msg(irc, source, missingargs)
|
||||||
return
|
return
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
|
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
|
||||||
return
|
return
|
||||||
relay = findRelay((irc.name, channel))
|
relay = findRelay((irc.name, channel))
|
||||||
if not relay:
|
if not relay:
|
||||||
utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)
|
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
|
||||||
return
|
return
|
||||||
if cmd == 'list':
|
if cmd == 'list':
|
||||||
s = 'Blocked networks for \x02%s\x02: \x02%s\x02' % (channel, ', '.join(db[relay]['blocked_nets']) or '(empty)')
|
s = 'Blocked networks for \x02%s\x02: \x02%s\x02' % (channel, ', '.join(db[relay]['blocked_nets']) or '(empty)')
|
||||||
@ -1029,8 +1092,44 @@ def linkacl(irc, source, args):
|
|||||||
try:
|
try:
|
||||||
db[relay]['blocked_nets'].remove(remotenet)
|
db[relay]['blocked_nets'].remove(remotenet)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
utils.msg(irc, source, 'Error: network %r is not on the blacklist for %r.' % (remotenet, channel))
|
utils.msg(irc, source, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Done.')
|
utils.msg(irc, source, 'Done.')
|
||||||
else:
|
else:
|
||||||
utils.msg(irc, source, 'Error: unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
|
utils.msg(irc, source, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def showuser(irc, source, args):
|
||||||
|
"""<user>
|
||||||
|
|
||||||
|
Shows relay data about user <user>. This is intended to be used alongside the 'commands' plugin, which provides a 'showuser' command with more general information."""
|
||||||
|
try:
|
||||||
|
target = args[0]
|
||||||
|
except IndexError:
|
||||||
|
# No errors here; showuser from the commands plugin already does this
|
||||||
|
# for us.
|
||||||
|
return
|
||||||
|
u = utils.nickToUid(irc, target)
|
||||||
|
if u:
|
||||||
|
try:
|
||||||
|
userpair = getLocalUser(irc, u) or (irc.name, u)
|
||||||
|
remoteusers = relayusers[userpair].items()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
nicks = []
|
||||||
|
if remoteusers:
|
||||||
|
nicks.append('%s (home network): \x02%s\x02' % (userpair[0],
|
||||||
|
world.networkobjects[userpair[0]].users[userpair[1]].nick))
|
||||||
|
for r in remoteusers:
|
||||||
|
remotenet, remoteuser = r
|
||||||
|
remoteirc = world.networkobjects[remotenet]
|
||||||
|
nicks.append('%s: \x02%s\x02' % (remotenet, remoteirc.users[remoteuser].nick))
|
||||||
|
utils.msg(irc, source, "\x02Relay nicks\x02: %s" % ', '.join(nicks))
|
||||||
|
relaychannels = []
|
||||||
|
for ch in irc.users[u].channels:
|
||||||
|
relay = findRelay((irc.name, ch))
|
||||||
|
if relay:
|
||||||
|
relaychannels.append(''.join(relay))
|
||||||
|
if relaychannels and (utils.isOper(irc, source) or u == source):
|
||||||
|
utils.msg(irc, source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels))
|
||||||
|
@ -39,7 +39,7 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set()
|
|||||||
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip)
|
realhost=realhost, ip=ip)
|
||||||
utils.applyModes(irc, uid, modes)
|
utils.applyModes(irc, uid, modes)
|
||||||
irc.servers[server].users.append(uid)
|
irc.servers[server].users.add(uid)
|
||||||
_send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
_send(irc, 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,
|
||||||
@ -138,7 +138,7 @@ def removeClient(irc, numeric):
|
|||||||
log.debug('Removing client %s from irc.users', numeric)
|
log.debug('Removing client %s from irc.users', numeric)
|
||||||
del irc.users[numeric]
|
del irc.users[numeric]
|
||||||
log.debug('Removing client %s from irc.servers[%s]', numeric, sid)
|
log.debug('Removing client %s from irc.servers[%s]', numeric, sid)
|
||||||
irc.servers[sid].users.remove(numeric)
|
irc.servers[sid].users.discard(numeric)
|
||||||
|
|
||||||
def quitClient(irc, numeric, reason):
|
def quitClient(irc, numeric, reason):
|
||||||
"""<irc object> <client numeric>
|
"""<irc object> <client numeric>
|
||||||
@ -195,6 +195,7 @@ def _operUp(irc, target, opertype=None):
|
|||||||
otype = 'IRC_Operator'
|
otype = 'IRC_Operator'
|
||||||
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
|
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
|
||||||
irc.name, target)
|
irc.name, target)
|
||||||
|
userobj.opertype = otype
|
||||||
_send(irc, target, 'OPERTYPE %s' % otype)
|
_send(irc, target, 'OPERTYPE %s' % otype)
|
||||||
|
|
||||||
def _sendModes(irc, numeric, target, modes, ts=None):
|
def _sendModes(irc, numeric, target, modes, ts=None):
|
||||||
@ -449,7 +450,7 @@ def handle_uid(irc, numeric, command, args):
|
|||||||
parsedmodes = utils.parseModes(irc, uid, [args[8], args[9]])
|
parsedmodes = utils.parseModes(irc, 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(irc, uid, parsedmodes)
|
utils.applyModes(irc, uid, parsedmodes)
|
||||||
irc.servers[numeric].users.append(uid)
|
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}
|
||||||
|
|
||||||
def handle_quit(irc, numeric, command, args):
|
def handle_quit(irc, numeric, command, args):
|
||||||
@ -706,7 +707,10 @@ def handle_opertype(irc, numeric, command, args):
|
|||||||
omode = [('+o', None)]
|
omode = [('+o', None)]
|
||||||
irc.users[numeric].opertype = opertype = args[0]
|
irc.users[numeric].opertype = opertype = args[0]
|
||||||
utils.applyModes(irc, numeric, omode)
|
utils.applyModes(irc, numeric, omode)
|
||||||
return {'target': numeric, 'modes': omode, 'text': opertype}
|
# OPERTYPE is essentially umode +o and metadata in one command;
|
||||||
|
# we'll call that too.
|
||||||
|
irc.callHooks([numeric, 'PYLINK_CLIENT_OPERED', {'text': opertype}])
|
||||||
|
return {'target': numeric, 'modes': omode}
|
||||||
|
|
||||||
def handle_fident(irc, numeric, command, args):
|
def handle_fident(irc, numeric, command, args):
|
||||||
# :70MAAAAAB FHOST test
|
# :70MAAAAAB FHOST test
|
||||||
|
@ -42,7 +42,7 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set()
|
|||||||
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip)
|
realhost=realhost, ip=ip)
|
||||||
utils.applyModes(irc, uid, modes)
|
utils.applyModes(irc, uid, modes)
|
||||||
irc.servers[server].users.append(uid)
|
irc.servers[server].users.add(uid)
|
||||||
_send(irc, server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
_send(irc, 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,
|
||||||
@ -469,7 +469,10 @@ def handle_euid(irc, numeric, command, args):
|
|||||||
parsedmodes = utils.parseModes(irc, uid, [modes])
|
parsedmodes = utils.parseModes(irc, uid, [modes])
|
||||||
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
log.debug('Applying modes %s for %s', parsedmodes, uid)
|
||||||
utils.applyModes(irc, uid, parsedmodes)
|
utils.applyModes(irc, uid, parsedmodes)
|
||||||
irc.servers[numeric].users.append(uid)
|
irc.servers[numeric].users.add(uid)
|
||||||
|
if ('o', None) in parsedmodes:
|
||||||
|
otype = 'Server_Administrator' if ('a', None) in parsedmodes else 'IRC_Operator'
|
||||||
|
irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
|
||||||
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}
|
||||||
|
|
||||||
def handle_uid(irc, numeric, command, args):
|
def handle_uid(irc, numeric, command, args):
|
||||||
@ -502,6 +505,17 @@ def handle_tmode(irc, numeric, command, args):
|
|||||||
ts = int(args[0])
|
ts = int(args[0])
|
||||||
return {'target': channel, 'modes': changedmodes, 'ts': ts}
|
return {'target': channel, 'modes': changedmodes, 'ts': ts}
|
||||||
|
|
||||||
|
def handle_mode(irc, numeric, command, args):
|
||||||
|
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||||
|
target = args[0]
|
||||||
|
modestrings = args[1:]
|
||||||
|
changedmodes = utils.parseModes(irc, numeric, modestrings)
|
||||||
|
utils.applyModes(irc, target, changedmodes)
|
||||||
|
if ('+o', None) in changedmodes:
|
||||||
|
otype = 'Server_Administrator' if ('a', None) in irc.users[target].modes else 'IRC_Operator'
|
||||||
|
irc.callHooks([target, 'PYLINK_CLIENT_OPERED', {'text': otype}])
|
||||||
|
return {'target': target, 'modes': changedmodes}
|
||||||
|
|
||||||
def handle_events(irc, data):
|
def handle_events(irc, data):
|
||||||
# TS6 messages:
|
# TS6 messages:
|
||||||
# :42X COMMAND arg1 arg2 :final long arg
|
# :42X COMMAND arg1 arg2 :final long arg
|
||||||
|
21
runtests.py
Executable file
21
runtests.py
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
fails = []
|
||||||
|
suites = []
|
||||||
|
|
||||||
|
# Yay, import hacks!
|
||||||
|
sys.path.append(os.path.join(os.getcwd(), 'tests'))
|
||||||
|
for testfile in glob.glob('tests/test_*.py'):
|
||||||
|
# Strip the tests/ and .py extension: tests/test_whatever.py => test_whatever
|
||||||
|
module = testfile.replace('.py', '').replace('tests/', '')
|
||||||
|
module = __import__(module)
|
||||||
|
suites.append(unittest.defaultTestLoader.loadTestsFromModule(module))
|
||||||
|
|
||||||
|
testsuite = unittest.TestSuite(suites)
|
||||||
|
runner.run(testsuite)
|
35
tests/test_coreplugin.py
Normal file
35
tests/test_coreplugin.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import inspircd
|
||||||
|
import unittest
|
||||||
|
import world
|
||||||
|
import coreplugin
|
||||||
|
|
||||||
|
import tests_common
|
||||||
|
|
||||||
|
world.testing = inspircd
|
||||||
|
|
||||||
|
class CorePluginTestCase(tests_common.PluginTestCase):
|
||||||
|
@unittest.skip("Test doesn't work yet.")
|
||||||
|
def testKillRespawn(self):
|
||||||
|
self.irc.run(':9PY KILL {u} :test'.format(u=self.u))
|
||||||
|
hooks = self.irc.takeHooks()
|
||||||
|
|
||||||
|
# Make sure we're respawning our PseudoClient when its killed
|
||||||
|
print(hooks)
|
||||||
|
spmain = [h for h in hooks if h[1] == 'PYLINK_SPAWNMAIN']
|
||||||
|
self.assertTrue(spmain, 'PYLINK_SPAWNMAIN hook was never sent!')
|
||||||
|
|
||||||
|
msgs = self.irc.takeMsgs()
|
||||||
|
commands = self.irc.takeCommands(msgs)
|
||||||
|
self.assertIn('UID', commands)
|
||||||
|
self.assertIn('FJOIN', commands)
|
||||||
|
|
||||||
|
# Also make sure that we're updating the irc.pseudoclient field
|
||||||
|
self.assertNotEqual(self.irc.pseudoclient.uid, spmain[0]['olduser'])
|
||||||
|
|
||||||
|
def testKickrejoin(self):
|
||||||
|
self.proto.kickClient(self.irc, self.u, '#pylink', self.u, 'test')
|
||||||
|
msgs = self.irc.takeMsgs()
|
||||||
|
commands = self.irc.takeCommands(msgs)
|
||||||
|
self.assertIn('FJOIN', commands)
|
@ -1,15 +1,13 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
from log import log
|
|
||||||
|
|
||||||
import classes
|
import classes
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# Yes, we're going to even test the testing classes. Testception? I think so.
|
|
||||||
class TestFakeIRC(unittest.TestCase):
|
class TestFakeIRC(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.irc = classes.FakeIRC('unittest', classes.FakeProto(), classes.testconf)
|
self.irc = classes.FakeIRC('unittest', classes.FakeProto())
|
||||||
|
|
||||||
def testFakeIRC(self):
|
def testFakeIRC(self):
|
||||||
self.irc.run('this should do nothing')
|
self.irc.run('this should do nothing')
|
||||||
|
@ -2,31 +2,30 @@ import sys
|
|||||||
import os
|
import os
|
||||||
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
|
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
|
||||||
import unittest
|
import unittest
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import inspircd
|
import inspircd
|
||||||
import classes
|
import classes
|
||||||
import utils
|
import world
|
||||||
import coreplugin
|
|
||||||
|
|
||||||
class TestProtoInspIRCd(unittest.TestCase):
|
import tests_common
|
||||||
def setUp(self):
|
|
||||||
self.irc = classes.FakeIRC('unittest', inspircd, classes.testconf)
|
world.testing = inspircd
|
||||||
self.proto = self.irc.proto
|
|
||||||
self.sdata = self.irc.serverdata
|
class InspIRCdTestCase(tests_common.CommonProtoTestCase):
|
||||||
# This is to initialize ourself as an internal PseudoServer, so we can spawn clients
|
def testCheckRecvpass(self):
|
||||||
self.proto.connect(self.irc)
|
# Correct recvpass here.
|
||||||
self.u = self.irc.pseudoclient.uid
|
self.irc.run('SERVER somehow.someday abcd 0 0AL :Somehow Server - McMurdo Station, Antarctica')
|
||||||
self.maxDiff = None
|
# Incorrect recvpass here; should raise ProtocolError.
|
||||||
utils.command_hooks = defaultdict(list)
|
self.assertRaises(classes.ProtocolError, self.irc.run, 'SERVER somehow.someday BADPASS 0 0AL :Somehow Server - McMurdo Station, Antarctica')
|
||||||
|
|
||||||
def testConnect(self):
|
def testConnect(self):
|
||||||
|
self.proto.connect(self.irc)
|
||||||
initial_messages = self.irc.takeMsgs()
|
initial_messages = self.irc.takeMsgs()
|
||||||
commands = self.irc.takeCommands(initial_messages)
|
commands = self.irc.takeCommands(initial_messages)
|
||||||
|
|
||||||
# SERVER pylink.unittest abcd 0 9PY :PyLink Service
|
# SERVER pylink.unittest abcd 0 9PY :PyLink Service
|
||||||
serverline = 'SERVER %s %s 0 %s :PyLink Service' % (
|
serverline = 'SERVER %s %s 0 %s :%s' % (
|
||||||
self.sdata['hostname'], self.sdata['sendpass'], self.sdata['sid'])
|
self.sdata['hostname'], self.sdata['sendpass'], self.sdata['sid'],
|
||||||
|
self.irc.botdata['serverdesc'])
|
||||||
self.assertIn(serverline, initial_messages)
|
self.assertIn(serverline, initial_messages)
|
||||||
self.assertIn('BURST', commands)
|
self.assertIn('BURST', commands)
|
||||||
self.assertIn('ENDBURST', commands)
|
self.assertIn('ENDBURST', commands)
|
||||||
@ -34,88 +33,12 @@ class TestProtoInspIRCd(unittest.TestCase):
|
|||||||
self.assertIn('UID', commands)
|
self.assertIn('UID', commands)
|
||||||
self.assertIn('FJOIN', commands)
|
self.assertIn('FJOIN', commands)
|
||||||
|
|
||||||
def testCheckRecvpass(self):
|
|
||||||
# Correct recvpass here.
|
|
||||||
self.irc.run('SERVER somehow.someday abcd 0 0AL :Somehow Server - McMurdo Station, Antarctica')
|
|
||||||
# Incorrect recvpass here; should raise ProtocolError.
|
|
||||||
self.assertRaises(classes.ProtocolError, self.irc.run, 'SERVER somehow.someday BADPASS 0 0AL :Somehow Server - McMurdo Station, Antarctica')
|
|
||||||
|
|
||||||
def testSpawnClient(self):
|
|
||||||
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
|
|
||||||
# Check the server index and the user index
|
|
||||||
self.assertIn(u, self.irc.servers[self.irc.sid].users)
|
|
||||||
self.assertIn(u, self.irc.users)
|
|
||||||
# Raise ValueError when trying to spawn a client on a server that's not ours
|
|
||||||
self.assertRaises(ValueError, self.proto.spawnClient, self.irc, 'abcd', 'user', 'dummy.user.net', server='44A')
|
|
||||||
# Unfilled args should get placeholder fields and not error.
|
|
||||||
self.proto.spawnClient(self.irc, 'testuser4')
|
|
||||||
|
|
||||||
def testJoinClient(self):
|
|
||||||
u = self.u
|
|
||||||
self.proto.joinClient(self.irc, u, '#Channel')
|
|
||||||
self.assertIn(u, self.irc.channels['#channel'].users)
|
|
||||||
# Non-existant user.
|
|
||||||
self.assertRaises(LookupError, self.proto.joinClient, self.irc, '9PYZZZZZZ', '#test')
|
|
||||||
|
|
||||||
def testPartClient(self):
|
|
||||||
u = self.u
|
|
||||||
self.proto.joinClient(self.irc, u, '#channel')
|
|
||||||
self.proto.partClient(self.irc, u, '#channel')
|
|
||||||
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
|
||||||
|
|
||||||
def testQuitClient(self):
|
|
||||||
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
|
|
||||||
self.proto.joinClient(self.irc, u, '#channel')
|
|
||||||
self.assertRaises(LookupError, self.proto.quitClient, self.irc, '9PYZZZZZZ', 'quit reason')
|
|
||||||
self.proto.quitClient(self.irc, u, 'quit reason')
|
|
||||||
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
|
||||||
self.assertNotIn(u, self.irc.users)
|
|
||||||
self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
|
|
||||||
|
|
||||||
def testKickClient(self):
|
|
||||||
target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid
|
|
||||||
self.proto.joinClient(self.irc, target, '#pylink')
|
|
||||||
self.assertIn(self.u, self.irc.channels['#pylink'].users)
|
|
||||||
self.assertIn(target, self.irc.channels['#pylink'].users)
|
|
||||||
self.proto.kickClient(self.irc, self.u, '#pylink', target, 'Pow!')
|
|
||||||
self.assertNotIn(target, self.irc.channels['#pylink'].users)
|
|
||||||
|
|
||||||
def testNickClient(self):
|
|
||||||
self.proto.nickClient(self.irc, self.u, 'NotPyLink')
|
|
||||||
self.assertEqual('NotPyLink', self.irc.users[self.u].nick)
|
|
||||||
|
|
||||||
def testModeClient(self):
|
|
||||||
testuser = self.proto.spawnClient(self.irc, 'testcakes')
|
|
||||||
self.irc.takeMsgs()
|
|
||||||
self.proto.modeClient(self.irc, self.u, testuser.uid, [('+i', None), ('+w', None)])
|
|
||||||
self.assertEqual({('i', None), ('w', None)}, testuser.modes)
|
|
||||||
|
|
||||||
self.proto.modeClient(self.irc, self.u, '#pylink', [('+s', None), ('+l', '30')])
|
|
||||||
self.assertEqual({('s', None), ('l', '30')}, self.irc.channels['#pylink'].modes)
|
|
||||||
|
|
||||||
cmds = self.irc.takeCommands(self.irc.takeMsgs())
|
|
||||||
self.assertEqual(cmds, ['MODE', 'FMODE'])
|
|
||||||
|
|
||||||
def testSpawnServer(self):
|
def testSpawnServer(self):
|
||||||
# Incorrect SID length
|
super(InspIRCdTestCase, self).testSpawnServer()
|
||||||
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'subserver.pylink', '34Q0')
|
|
||||||
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
|
|
||||||
# Duplicate server name
|
|
||||||
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'Subserver.PyLink', '34Z')
|
|
||||||
# Duplicate SID
|
|
||||||
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'another.Subserver.PyLink', '34Q')
|
|
||||||
self.assertIn('34Q', self.irc.servers)
|
|
||||||
# Are we bursting properly?
|
# Are we bursting properly?
|
||||||
self.assertIn(':34Q ENDBURST', self.irc.takeMsgs())
|
self.assertIn(':34Q ENDBURST', self.irc.takeMsgs())
|
||||||
|
|
||||||
def testSpawnClientOnServer(self):
|
def testHandleSQuit(self):
|
||||||
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
|
|
||||||
u = self.proto.spawnClient(self.irc, 'person1', 'person', 'users.overdrive.pw', server='34Q')
|
|
||||||
# We're spawning clients on the right server, hopefully...
|
|
||||||
self.assertIn(u.uid, self.irc.servers['34Q'].users)
|
|
||||||
self.assertNotIn(u.uid, self.irc.servers[self.irc.sid].users)
|
|
||||||
|
|
||||||
def testSquit(self):
|
|
||||||
# Spawn a messy network map, just because!
|
# Spawn a messy network map, just because!
|
||||||
self.proto.spawnServer(self.irc, 'level1.pylink', '34P')
|
self.proto.spawnServer(self.irc, 'level1.pylink', '34P')
|
||||||
self.proto.spawnServer(self.irc, 'level2.pylink', '34Q', uplink='34P')
|
self.proto.spawnServer(self.irc, 'level2.pylink', '34Q', uplink='34P')
|
||||||
@ -136,18 +59,6 @@ class TestProtoInspIRCd(unittest.TestCase):
|
|||||||
self.assertNotIn('34Q', self.irc.servers)
|
self.assertNotIn('34Q', self.irc.servers)
|
||||||
self.assertNotIn('34Z', self.irc.servers)
|
self.assertNotIn('34Z', self.irc.servers)
|
||||||
|
|
||||||
def testRSquit(self):
|
|
||||||
u = self.proto.spawnClient(self.irc, 'person1', 'person', 'users.overdrive.pw')
|
|
||||||
u.identified = 'admin'
|
|
||||||
self.proto.spawnServer(self.irc, 'level1.pylink', '34P')
|
|
||||||
self.irc.run(':%s RSQUIT level1.pylink :some reason' % self.u)
|
|
||||||
# No SQUIT yet, since the 'PyLink' client isn't identified
|
|
||||||
self.assertNotIn('SQUIT', self.irc.takeCommands(self.irc.takeMsgs()))
|
|
||||||
# The one we just spawned however, is.
|
|
||||||
self.irc.run(':%s RSQUIT level1.pylink :some reason' % u.uid)
|
|
||||||
self.assertIn('SQUIT', self.irc.takeCommands(self.irc.takeMsgs()))
|
|
||||||
self.assertNotIn('34P', self.irc.servers)
|
|
||||||
|
|
||||||
def testHandleServer(self):
|
def testHandleServer(self):
|
||||||
self.irc.run('SERVER whatever.net abcd 0 10X :something')
|
self.irc.run('SERVER whatever.net abcd 0 10X :something')
|
||||||
self.assertIn('10X', self.irc.servers)
|
self.assertIn('10X', self.irc.servers)
|
||||||
@ -157,124 +68,122 @@ class TestProtoInspIRCd(unittest.TestCase):
|
|||||||
self.assertEqual('test.server', self.irc.servers['0AL'].name)
|
self.assertEqual('test.server', self.irc.servers['0AL'].name)
|
||||||
|
|
||||||
def testHandleUID(self):
|
def testHandleUID(self):
|
||||||
self.irc.run('SERVER whatever.net abcd 0 10X :something')
|
|
||||||
self.irc.run(':10X UID 10XAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname')
|
self.irc.run(':10X UID 10XAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname')
|
||||||
self.assertIn('10XAAAAAB', self.irc.servers['10X'].users)
|
self.assertIn('10XAAAAAB', self.irc.servers['10X'].users)
|
||||||
self.assertIn('10XAAAAAB', self.irc.users)
|
|
||||||
u = self.irc.users['10XAAAAAB']
|
u = self.irc.users['10XAAAAAB']
|
||||||
self.assertEqual('GL', u.nick)
|
self.assertEqual('GL', u.nick)
|
||||||
|
|
||||||
|
expected = {'uid': '10XAAAAAB', 'ts': '1429934638', 'nick': 'GL',
|
||||||
|
'realhost': '0::1', 'ident': 'gl', 'ip': '0::1',
|
||||||
|
'host': 'hidden-7j810p.9mdf.lrek.0000.0000.IP'}
|
||||||
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
|
self.assertEqual(hookdata, expected)
|
||||||
|
|
||||||
def testHandleKill(self):
|
def testHandleKill(self):
|
||||||
self.irc.takeMsgs() # Ignore the initial connect messages
|
self.irc.takeMsgs() # Ignore the initial connect messages
|
||||||
utils.add_hook(self.irc.dummyhook, 'KILL')
|
self.u = self.irc.pseudoclient.uid
|
||||||
olduid = self.irc.pseudoclient.uid
|
self.irc.run(':{u} KILL {u} :killed'.format(u=self.u))
|
||||||
self.irc.run(':{u} KILL {u} :killed'.format(u=olduid))
|
|
||||||
msgs = self.irc.takeMsgs()
|
msgs = self.irc.takeMsgs()
|
||||||
commands = self.irc.takeCommands(msgs)
|
commands = self.irc.takeCommands(msgs)
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['target'], self.u)
|
||||||
self.assertEqual({'target': olduid, 'text': 'killed'}, hookdata)
|
self.assertEqual(hookdata['text'], 'killed')
|
||||||
# Make sure we're respawning our PseudoClient when its killed
|
self.assertNotIn(self.u, self.irc.users)
|
||||||
self.assertIn('UID', commands)
|
|
||||||
self.assertIn('FJOIN', commands)
|
|
||||||
# Also make sure that we're updating the irc.pseudoclient field
|
|
||||||
self.assertNotEqual(self.irc.pseudoclient.uid, olduid)
|
|
||||||
|
|
||||||
def testHandleKick(self):
|
def testHandleKick(self):
|
||||||
self.irc.takeMsgs() # Ignore the initial connect messages
|
self.irc.takeMsgs() # Ignore the initial connect messages
|
||||||
utils.add_hook(self.irc.dummyhook, 'KICK')
|
|
||||||
self.irc.run(':{u} KICK #pylink {u} :kicked'.format(u=self.irc.pseudoclient.uid))
|
self.irc.run(':{u} KICK #pylink {u} :kicked'.format(u=self.irc.pseudoclient.uid))
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['target'], self.u)
|
||||||
self.assertEqual({'target': self.u, 'text': 'kicked', 'channel': '#pylink'}, hookdata)
|
self.assertEqual(hookdata['text'], 'kicked')
|
||||||
|
self.assertEqual(hookdata['channel'], '#pylink')
|
||||||
|
|
||||||
# Ditto above
|
def testHandleFJoinUsers(self):
|
||||||
msgs = self.irc.takeMsgs()
|
|
||||||
commands = self.irc.takeCommands(msgs)
|
|
||||||
self.assertIn('FJOIN', commands)
|
|
||||||
|
|
||||||
def testHandleFjoinUsers(self):
|
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790411 + :,10XAAAAAA ,10XAAAAAB')
|
self.irc.run(':10X FJOIN #Chat 1423790411 + :,10XAAAAAA ,10XAAAAAB')
|
||||||
self.assertEqual({'10XAAAAAA', '10XAAAAAB'}, self.irc.channels['#chat'].users)
|
self.assertEqual({'10XAAAAAA', '10XAAAAAB'}, self.irc.channels['#chat'].users)
|
||||||
# self.assertIn('10XAAAAAB', self.irc.channels['#chat'].users)
|
self.assertIn('#chat', self.irc.users['10XAAAAAA'].channels)
|
||||||
# Sequential FJOINs must NOT remove existing users
|
# Sequential FJOINs must NOT remove existing users
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790412 + :,10XAAAAAC')
|
self.irc.run(':10X FJOIN #Chat 1423790412 + :,10XAAAAAC')
|
||||||
# Join list can be empty too, in the case of permanent channels with 0 users.
|
# Join list can be empty too, in the case of permanent channels with 0 users.
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790413 +nt :')
|
self.irc.run(':10X FJOIN #Chat 1423790413 +nt :')
|
||||||
|
|
||||||
def testHandleFjoinModes(self):
|
def testHandleFJoinModes(self):
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790411 +nt :,10XAAAAAA ,10XAAAAAB')
|
self.irc.run(':10X FJOIN #Chat 1423790411 +nt :,10XAAAAAA ,10XAAAAAB')
|
||||||
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
||||||
# Sequential FJOINs must NOT remove existing modes
|
# Sequential FJOINs must NOT remove existing modes
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790412 + :,10XAAAAAC')
|
self.irc.run(':10X FJOIN #Chat 1423790412 + :,10XAAAAAC')
|
||||||
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
||||||
|
|
||||||
def testHandleFjoinModesWithArgs(self):
|
def testHandleFJoinModesWithArgs(self):
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790414 +nlks 10 t0psekrit :,10XAAAAAA ,10XAAAAAB')
|
self.irc.run(':10X FJOIN #Chat 1423790414 +nlks 10 t0psekrit :,10XAAAAAA ,10XAAAAAB')
|
||||||
self.assertEqual({('n', None), ('s', None), ('l', '10'), ('k', 't0psekrit')},
|
self.assertEqual({('n', None), ('s', None), ('l', '10'), ('k', 't0psekrit')},
|
||||||
self.irc.channels['#chat'].modes)
|
self.irc.channels['#chat'].modes)
|
||||||
|
|
||||||
def testHandleFjoinPrefixes(self):
|
def testHandleFJoinPrefixes(self):
|
||||||
self.irc.run(':10X FJOIN #Chat 1423790418 +nt :ov,10XAAAAAA v,10XAAAAAB ,10XAAAAAC')
|
self.irc.run(':10X FJOIN #Chat 1423790418 +nt :ov,10XAAAAAA v,10XAAAAAB ,10XAAAAAC')
|
||||||
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#chat'].modes)
|
||||||
self.assertEqual({'10XAAAAAA', '10XAAAAAB', '10XAAAAAC'}, self.irc.channels['#chat'].users)
|
self.assertEqual({'10XAAAAAA', '10XAAAAAB', '10XAAAAAC'}, self.irc.channels['#chat'].users)
|
||||||
self.assertIn('10XAAAAAA', self.irc.channels['#chat'].prefixmodes['ops'])
|
self.assertIn('10XAAAAAA', self.irc.channels['#chat'].prefixmodes['ops'])
|
||||||
self.assertEqual({'10XAAAAAA', '10XAAAAAB'}, self.irc.channels['#chat'].prefixmodes['voices'])
|
self.assertEqual({'10XAAAAAA', '10XAAAAAB'}, self.irc.channels['#chat'].prefixmodes['voices'])
|
||||||
|
|
||||||
def testHandleFjoinHook(self):
|
def testHandleFJoinHook(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'JOIN')
|
|
||||||
self.irc.run(':10X FJOIN #PyLink 1423790418 +ls 10 :ov,10XAAAAAA v,10XAAAAAB ,10XAAAAAC')
|
self.irc.run(':10X FJOIN #PyLink 1423790418 +ls 10 :ov,10XAAAAAA v,10XAAAAAB ,10XAAAAAC')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
expected = {'modes': [('+l', '10'), ('+s', None)],
|
expected = {'modes': [('+l', '10'), ('+s', None)],
|
||||||
'channel': '#pylink',
|
'channel': '#pylink',
|
||||||
'users': ['10XAAAAAA', '10XAAAAAB', '10XAAAAAC'],
|
'users': ['10XAAAAAA', '10XAAAAAB', '10XAAAAAC'],
|
||||||
'ts': 1423790418}
|
'ts': 1423790418}
|
||||||
self.assertEqual(expected, hookdata)
|
self.assertEqual(expected, hookdata)
|
||||||
|
|
||||||
def testHandleFmode(self):
|
def testHandleFMode(self):
|
||||||
self.irc.run(':10X FJOIN #pylink 1423790411 +n :o,10XAAAAAA ,10XAAAAAB')
|
|
||||||
utils.add_hook(self.irc.dummyhook, 'MODE')
|
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +ikl herebedragons 100')
|
self.irc.run(':70M FMODE #pylink 1423790412 +ikl herebedragons 100')
|
||||||
self.assertEqual({('i', None), ('k', 'herebedragons'), ('l', '100'), ('n', None)}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('i', None), ('k', 'herebedragons'), ('l', '100')}, self.irc.channels['#pylink'].modes)
|
||||||
self.irc.run(':70M FMODE #pylink 1423790413 -ilk+m herebedragons')
|
self.irc.run(':70M FMODE #pylink 1423790413 -ilk+m herebedragons')
|
||||||
self.assertEqual({('m', None), ('n', None)}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('m', None)}, self.irc.channels['#pylink'].modes)
|
||||||
|
|
||||||
hookdata = self.irc.takeHooks()
|
hookdata = self.irc.takeHooks()
|
||||||
expected = [{'target': '#pylink', 'modes': [('+i', None), ('+k', 'herebedragons'), ('+l', '100')], 'ts': 1423790412},
|
expected = [['70M', 'FMODE', {'target': '#pylink', 'modes':
|
||||||
{'target': '#pylink', 'modes': [('-i', None), ('-l', None), ('-k', 'herebedragons'), ('+m', None)], 'ts': 1423790413}]
|
[('+i', None), ('+k', 'herebedragons'),
|
||||||
|
('+l', '100')], 'ts': 1423790412}
|
||||||
|
],
|
||||||
|
['70M', 'FMODE', {'target': '#pylink', 'modes':
|
||||||
|
[('-i', None), ('-l', None),
|
||||||
|
('-k', 'herebedragons'), ('+m', None)],
|
||||||
|
'ts': 1423790413}]
|
||||||
|
]
|
||||||
self.assertEqual(expected, hookdata)
|
self.assertEqual(expected, hookdata)
|
||||||
|
|
||||||
def testHandleFmodeWithPrefixes(self):
|
def testHandleFModeWithPrefixes(self):
|
||||||
self.irc.run(':70M FJOIN #pylink 1423790411 +n :o,10XAAAAAA ,10XAAAAAB')
|
self.irc.run(':70M FJOIN #pylink 123 +n :o,10XAAAAAA ,10XAAAAAB')
|
||||||
utils.add_hook(self.irc.dummyhook, 'MODE')
|
|
||||||
# Prefix modes are stored separately, so they should never show up in .modes
|
# Prefix modes are stored separately, so they should never show up in .modes
|
||||||
self.assertNotIn(('o', '10XAAAAAA'), self.irc.channels['#pylink'].modes)
|
self.assertNotIn(('o', '10XAAAAAA'), self.irc.channels['#pylink'].modes)
|
||||||
self.assertEqual({'10XAAAAAA'}, self.irc.channels['#pylink'].prefixmodes['ops'])
|
self.assertEqual({'10XAAAAAA'}, self.irc.channels['#pylink'].prefixmodes['ops'])
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +lot 50 %s' % self.u)
|
self.irc.run(':70M FMODE #pylink 123 +lot 50 %s' % self.u)
|
||||||
self.assertIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
self.assertIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
||||||
modes = {('l', '50'), ('n', None), ('t', None)}
|
modes = {('l', '50'), ('n', None), ('t', None)}
|
||||||
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
||||||
self.irc.run(':70M FMODE #pylink 1423790413 -o %s' % self.u)
|
self.irc.run(':70M FMODE #pylink 123 -o %s' % self.u)
|
||||||
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
||||||
self.assertNotIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
self.assertNotIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
||||||
# Test hooks
|
# Test hooks
|
||||||
hookdata = self.irc.takeHooks()
|
hookdata = self.irc.takeHooks()
|
||||||
expected = [{'target': '#pylink', 'modes': [('+l', '50'), ('+o', '9PYAAAAAA'), ('+t', None)], 'ts': 1423790412},
|
expected = [['70M', 'FJOIN', {'channel': '#pylink', 'ts': 123, 'modes': [('+n', None)],
|
||||||
{'target': '#pylink', 'modes': [('-o', '9PYAAAAAA')], 'ts': 1423790413}]
|
'users': ['10XAAAAAA', '10XAAAAAB']}],
|
||||||
|
['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '50'), ('+o', '9PYAAAAAA'), ('+t', None)], 'ts': 123}],
|
||||||
|
['70M', 'FMODE', {'target': '#pylink', 'modes': [('-o', '9PYAAAAAA')], 'ts': 123}]]
|
||||||
self.assertEqual(expected, hookdata)
|
self.assertEqual(expected, hookdata)
|
||||||
|
|
||||||
def testFmodeRemovesOldParams(self):
|
def testHandleFModeRemovesOldParams(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'MODE')
|
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +l 50')
|
self.irc.run(':70M FMODE #pylink 1423790412 +l 50')
|
||||||
self.assertEqual({('l', '50')}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('l', '50')}, self.irc.channels['#pylink'].modes)
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +l 30')
|
self.irc.run(':70M FMODE #pylink 1423790412 +l 30')
|
||||||
self.assertEqual({('l', '30')}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('l', '30')}, self.irc.channels['#pylink'].modes)
|
||||||
hookdata = self.irc.takeHooks()
|
hookdata = self.irc.takeHooks()
|
||||||
expected = [{'target': '#pylink', 'modes': [('+l', '50')], 'ts': 1423790412},
|
expected = [['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '50')], 'ts': 1423790412}],
|
||||||
{'target': '#pylink', 'modes': [('+l', '30')], 'ts': 1423790412}]
|
['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '30')], 'ts': 1423790412}]]
|
||||||
self.assertEqual(expected, hookdata)
|
self.assertEqual(expected, hookdata)
|
||||||
|
|
||||||
def testFjoinResetsTS(self):
|
def testHandleFJoinResetsTS(self):
|
||||||
curr_ts = self.irc.channels['#pylink'].ts
|
curr_ts = self.irc.channels['#pylink'].ts
|
||||||
self.irc.run(':70M FJOIN #pylink 5 + :')
|
self.irc.run(':70M FJOIN #pylink 5 + :')
|
||||||
self.assertEqual(self.irc.channels['#pylink'].ts, 5)
|
self.assertEqual(self.irc.channels['#pylink'].ts, 5)
|
||||||
@ -285,87 +194,60 @@ class TestProtoInspIRCd(unittest.TestCase):
|
|||||||
|
|
||||||
def testHandleTopic(self):
|
def testHandleTopic(self):
|
||||||
self.irc.connect()
|
self.irc.connect()
|
||||||
utils.add_hook(self.irc.dummyhook, 'TOPIC')
|
|
||||||
self.irc.run(':9PYAAAAAA TOPIC #PyLink :test')
|
self.irc.run(':9PYAAAAAA TOPIC #PyLink :test')
|
||||||
self.assertEqual(self.irc.channels['#pylink'].topic, 'test')
|
self.assertEqual(self.irc.channels['#pylink'].topic, 'test')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
# Setter is a nick here, not an UID - this is to be consistent
|
|
||||||
# with FTOPIC above, which sends the nick/prefix of the topic setter.
|
|
||||||
self.assertTrue(utils.isNick(hookdata.get('setter')))
|
|
||||||
self.assertEqual(type(hookdata['ts']), int)
|
self.assertEqual(type(hookdata['ts']), int)
|
||||||
self.assertEqual(hookdata['topic'], 'test')
|
self.assertEqual(hookdata['topic'], 'test')
|
||||||
self.assertEqual(hookdata['channel'], '#pylink')
|
self.assertEqual(hookdata['channel'], '#pylink')
|
||||||
|
|
||||||
def testMsgHooks(self):
|
def testHandleMessages(self):
|
||||||
for m in ('NOTICE', 'PRIVMSG'):
|
for m in ('NOTICE', 'PRIVMSG'):
|
||||||
utils.add_hook(self.irc.dummyhook, m)
|
|
||||||
self.irc.run(':70MAAAAAA %s #dev :afasfsa' % m)
|
self.irc.run(':70MAAAAAA %s #dev :afasfsa' % m)
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['target'], '#dev')
|
||||||
self.assertEqual({'target': '#dev', 'text': 'afasfsa'}, hookdata)
|
self.assertEqual(hookdata['text'], 'afasfsa')
|
||||||
|
|
||||||
def testHandlePart(self):
|
def testHandlePart(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'PART')
|
hookdata = self.irc.takeHooks()
|
||||||
self.irc.run(':9PYAAAAAA PART #pylink')
|
self.irc.run(':9PYAAAAAA PART #pylink')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['channels'], ['#pylink'])
|
||||||
self.assertEqual({'channel': '#pylink', 'text': ''}, hookdata)
|
self.assertEqual(hookdata['text'], '')
|
||||||
|
|
||||||
def testUIDHook(self):
|
|
||||||
utils.add_hook(self.irc.dummyhook, 'UID')
|
|
||||||
# Create the server so we won't KeyError on processing UID
|
|
||||||
self.irc.run('SERVER whatever. abcd 0 10X :Whatever Server - Hellas Planitia, Mars')
|
|
||||||
self.irc.run(':10X UID 10XAAAAAB 1429934638 GL 0::1 '
|
|
||||||
'hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 '
|
|
||||||
'+Wioswx +ACGKNOQXacfgklnoqvx :realname')
|
|
||||||
expected = {'uid': '10XAAAAAB', 'ts': '1429934638', 'nick': 'GL',
|
|
||||||
'realhost': '0::1', 'ident': 'gl', 'ip': '0::1',
|
|
||||||
'host': 'hidden-7j810p.9mdf.lrek.0000.0000.IP'}
|
|
||||||
hookdata = self.irc.takeHooks()[0]
|
|
||||||
self.assertEqual(hookdata, expected)
|
|
||||||
|
|
||||||
def testHandleQuit(self):
|
def testHandleQuit(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'QUIT')
|
self.irc.takeHooks()
|
||||||
self.irc.run('SERVER whatever. abcd 0 10X :Whatever Server - Hellas Planitia, Mars')
|
|
||||||
self.irc.run(':10X UID 10XAAAAAB 1429934638 GL 0::1 '
|
|
||||||
'hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 '
|
|
||||||
'+Wioswx +ACGKNOQXacfgklnoqvx :realname')
|
|
||||||
self.irc.run(':10XAAAAAB QUIT :Quit: quit message goes here')
|
self.irc.run(':10XAAAAAB QUIT :Quit: quit message goes here')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['text'], 'Quit: quit message goes here')
|
||||||
self.assertEqual(hookdata, {'text': 'Quit: quit message goes here'})
|
|
||||||
self.assertNotIn('10XAAAAAB', self.irc.users)
|
self.assertNotIn('10XAAAAAB', self.irc.users)
|
||||||
self.assertNotIn('10XAAAAAB', self.irc.servers['10X'].users)
|
self.assertNotIn('10XAAAAAB', self.irc.servers['10X'].users)
|
||||||
|
|
||||||
def testHandleServer(self):
|
def testHandleServer(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'SERVER')
|
|
||||||
self.irc.run(':00A SERVER test.server * 1 00C :testing raw message syntax')
|
self.irc.run(':00A SERVER test.server * 1 00C :testing raw message syntax')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[-1][-1]
|
||||||
del hookdata['ts']
|
self.assertEqual(hookdata['name'], 'test.server')
|
||||||
self.assertEqual(hookdata, {'name': 'test.server', 'sid': '00C',
|
self.assertEqual(hookdata['sid'], '00C')
|
||||||
'text': 'testing raw message syntax'})
|
self.assertEqual(hookdata['text'], 'testing raw message syntax')
|
||||||
self.assertIn('00C', self.irc.servers)
|
self.assertIn('00C', self.irc.servers)
|
||||||
|
|
||||||
def testHandleNick(self):
|
def testHandleNick(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'NICK')
|
|
||||||
self.irc.run(':9PYAAAAAA NICK PyLink-devel 1434744242')
|
self.irc.run(':9PYAAAAAA NICK PyLink-devel 1434744242')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
expected = {'newnick': 'PyLink-devel', 'oldnick': 'PyLink', 'ts': 1434744242}
|
expected = {'newnick': 'PyLink-devel', 'oldnick': 'PyLink', 'ts': 1434744242}
|
||||||
self.assertEqual(hookdata, expected)
|
self.assertEqual(hookdata, expected)
|
||||||
self.assertEqual('PyLink-devel', self.irc.users['9PYAAAAAA'].nick)
|
self.assertEqual('PyLink-devel', self.irc.users['9PYAAAAAA'].nick)
|
||||||
|
|
||||||
def testHandleSave(self):
|
def testHandleSave(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'SAVE')
|
|
||||||
self.irc.run(':9PYAAAAAA NICK Derp_ 1433728673')
|
self.irc.run(':9PYAAAAAA NICK Derp_ 1433728673')
|
||||||
self.irc.run(':70M SAVE 9PYAAAAAA 1433728673')
|
self.irc.run(':70M SAVE 9PYAAAAAA 1433728673')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[-1][-1]
|
||||||
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'ts': 1433728673})
|
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'ts': 1433728673, 'oldnick': 'Derp_'})
|
||||||
self.assertEqual('9PYAAAAAA', self.irc.users['9PYAAAAAA'].nick)
|
self.assertEqual('9PYAAAAAA', self.irc.users['9PYAAAAAA'].nick)
|
||||||
|
|
||||||
def testInviteHook(self):
|
def testHandleInvite(self):
|
||||||
utils.add_hook(self.irc.dummyhook, 'INVITE')
|
|
||||||
self.irc.run(':10XAAAAAA INVITE 9PYAAAAAA #blah 0')
|
self.irc.run(':10XAAAAAA INVITE 9PYAAAAAA #blah 0')
|
||||||
hookdata = self.irc.takeHooks()[0]
|
hookdata = self.irc.takeHooks()[-1][-1]
|
||||||
del hookdata['ts']
|
del hookdata['ts']
|
||||||
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'channel': '#blah'})
|
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'channel': '#blah'})
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ cwd = os.getcwd()
|
|||||||
sys.path += [cwd, os.path.join(cwd, 'plugins')]
|
sys.path += [cwd, os.path.join(cwd, 'plugins')]
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import utils
|
|
||||||
import classes
|
import classes
|
||||||
import relay
|
import relay
|
||||||
|
|
||||||
@ -13,28 +12,34 @@ def dummyf():
|
|||||||
|
|
||||||
class TestRelay(unittest.TestCase):
|
class TestRelay(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.irc = classes.FakeIRC('unittest', classes.FakeProto(), classes.testconf)
|
self.irc = classes.FakeIRC('unittest', classes.FakeProto())
|
||||||
self.irc.maxnicklen = 20
|
self.irc.maxnicklen = 20
|
||||||
self.irc.proto.__name__ = "test"
|
self.f = lambda nick: relay.normalizeNick(self.irc, 'unittest', nick)
|
||||||
self.f = relay.normalizeNick
|
# Fake our protocol name to something that supports slashes in nicks.
|
||||||
|
# relay uses a whitelist for this to prevent accidentally introducing
|
||||||
|
# bad nicks:
|
||||||
|
self.irc.proto.__name__ = "inspircd"
|
||||||
|
|
||||||
def testNormalizeNick(self):
|
def testNormalizeNick(self):
|
||||||
# Second argument simply states the suffix.
|
# Second argument simply states the suffix.
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld/unittest')
|
self.assertEqual(self.f('helloworld'), 'helloworld/unittest')
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'ObnoxiouslyLongNick'), 'Obnoxiously/unittest')
|
self.assertEqual(self.f('ObnoxiouslyLongNick'), 'Obnoxiously/unittest')
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', '10XAAAAAA'), '_10XAAAAAA/unittest')
|
self.assertEqual(self.f('10XAAAAAA'), '_10XAAAAAA/unittest')
|
||||||
|
|
||||||
def testNormalizeNickConflict(self):
|
def testNormalizeNickConflict(self):
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld/unittest')
|
self.assertEqual(self.f('helloworld'), 'helloworld/unittest')
|
||||||
self.irc.users['10XAAAAAA'] = classes.IrcUser('helloworld/unittest', 1234, '10XAAAAAA')
|
self.irc.users['10XAAAAAA'] = classes.IrcUser('helloworld/unittest', 1234, '10XAAAAAA')
|
||||||
# Increase amount of /'s by one
|
# Increase amount of /'s by one
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld//unittest')
|
self.assertEqual(self.f('helloworld'), 'helloworld//unittest')
|
||||||
self.irc.users['10XAAAAAB'] = classes.IrcUser('helloworld//unittest', 1234, '10XAAAAAB')
|
self.irc.users['10XAAAAAB'] = classes.IrcUser('helloworld//unittest', 1234, '10XAAAAAB')
|
||||||
# Cut off the nick, not the suffix if the result is too long.
|
# Cut off the nick, not the suffix if the result is too long.
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworl///unittest')
|
self.assertEqual(self.f('helloworld'), 'helloworl///unittest')
|
||||||
|
|
||||||
def testNormalizeNickRemovesSlashes(self):
|
def testNormalizeNickRemovesSlashes(self):
|
||||||
self.irc.proto.__name__ = "charybdis"
|
self.irc.proto.__name__ = "charybdis"
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'helloworld'), 'helloworld|unittest')
|
try:
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'abcde/eJanus'), 'abcde|eJanu|unittest')
|
self.assertEqual(self.f('helloworld'), 'helloworld|unittest')
|
||||||
self.assertEqual(self.f(self.irc, 'unittest', 'ObnoxiouslyLongNick'), 'Obnoxiously|unittest')
|
self.assertEqual(self.f('abcde/eJanus'), 'abcde|eJanu|unittest')
|
||||||
|
self.assertEqual(self.f('ObnoxiouslyLongNick'), 'Obnoxiously|unittest')
|
||||||
|
finally:
|
||||||
|
self.irc.proto.__name__ = "inspircd"
|
||||||
|
@ -5,11 +5,16 @@ import unittest
|
|||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
|
import classes
|
||||||
|
import world
|
||||||
|
|
||||||
def dummyf():
|
def dummyf():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.irc = classes.FakeIRC('fakeirc', classes.FakeProto())
|
||||||
|
|
||||||
def testTS6UIDGenerator(self):
|
def testTS6UIDGenerator(self):
|
||||||
uidgen = utils.TS6UIDGenerator('9PY')
|
uidgen = utils.TS6UIDGenerator('9PY')
|
||||||
self.assertEqual(uidgen.next_uid(), '9PYAAAAAA')
|
self.assertEqual(uidgen.next_uid(), '9PYAAAAAA')
|
||||||
@ -21,16 +26,16 @@ class TestUtils(unittest.TestCase):
|
|||||||
utils.add_cmd(dummyf)
|
utils.add_cmd(dummyf)
|
||||||
utils.add_cmd(dummyf, 'TEST')
|
utils.add_cmd(dummyf, 'TEST')
|
||||||
# All command names should be automatically lowercased.
|
# All command names should be automatically lowercased.
|
||||||
self.assertIn('dummyf', utils.bot_commands)
|
self.assertIn('dummyf', world.bot_commands)
|
||||||
self.assertIn('test', utils.bot_commands)
|
self.assertIn('test', world.bot_commands)
|
||||||
self.assertNotIn('TEST', utils.bot_commands)
|
self.assertNotIn('TEST', world.bot_commands)
|
||||||
|
|
||||||
def test_add_hook(self):
|
def test_add_hook(self):
|
||||||
utils.add_hook(dummyf, 'join')
|
utils.add_hook(dummyf, 'join')
|
||||||
self.assertIn('JOIN', utils.command_hooks)
|
self.assertIn('JOIN', world.command_hooks)
|
||||||
# Command names stored in uppercase.
|
# Command names stored in uppercase.
|
||||||
self.assertNotIn('join', utils.command_hooks)
|
self.assertNotIn('join', world.command_hooks)
|
||||||
self.assertIn(dummyf, utils.command_hooks['JOIN'])
|
self.assertIn(dummyf, world.command_hooks['JOIN'])
|
||||||
|
|
||||||
def testIsNick(self):
|
def testIsNick(self):
|
||||||
self.assertFalse(utils.isNick('abcdefgh', nicklen=3))
|
self.assertFalse(utils.isNick('abcdefgh', nicklen=3))
|
||||||
@ -96,5 +101,19 @@ class TestUtils(unittest.TestCase):
|
|||||||
('+b', '*!*@*.badisp.net')])
|
('+b', '*!*@*.badisp.net')])
|
||||||
self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net')
|
self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net')
|
||||||
|
|
||||||
|
@unittest.skip('Wait, we need to work out the kinks first! (reversing changes of modes with arguments)')
|
||||||
|
def testReverseModes(self):
|
||||||
|
f = lambda x: utils.reverseModes(self.irc, '#test', x)
|
||||||
|
# Strings.
|
||||||
|
self.assertEqual(f("+nt-lk"), "-nt+lk")
|
||||||
|
self.assertEqual(f("nt-k"), "-nt+k")
|
||||||
|
# Lists.
|
||||||
|
self.assertEqual(f([('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')]),
|
||||||
|
[('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')])
|
||||||
|
# Sets.
|
||||||
|
self.assertEqual(f({('s', None), ('+o', 'whoever')}), {('-s', None), ('-o', 'whoever')})
|
||||||
|
# Combining modes with an initial + and those without
|
||||||
|
self.assertEqual(f({('s', None), ('+n', None)}), {('-s', None), ('-n', None)})
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
96
tests/tests_common.py
Normal file
96
tests/tests_common.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import world
|
||||||
|
import classes
|
||||||
|
|
||||||
|
world.started.set()
|
||||||
|
|
||||||
|
class PluginTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.irc = classes.FakeIRC('unittest', world.testing)
|
||||||
|
self.proto = self.irc.proto
|
||||||
|
self.irc.connect()
|
||||||
|
self.sdata = self.irc.serverdata
|
||||||
|
self.u = self.irc.pseudoclient.uid
|
||||||
|
self.maxDiff = None
|
||||||
|
# Dummy servers/users used in tests below.
|
||||||
|
self.proto.spawnServer(self.irc, 'whatever.', sid='10X')
|
||||||
|
for x in range(3):
|
||||||
|
self.proto.spawnClient(self.irc, 'user%s' % x, server='10X')
|
||||||
|
|
||||||
|
class CommonProtoTestCase(PluginTestCase):
|
||||||
|
def testJoinClient(self):
|
||||||
|
u = self.u
|
||||||
|
self.proto.joinClient(self.irc, u, '#Channel')
|
||||||
|
self.assertIn(u, self.irc.channels['#channel'].users)
|
||||||
|
# Non-existant user.
|
||||||
|
self.assertRaises(LookupError, self.proto.joinClient, self.irc, '9PYZZZZZZ', '#test')
|
||||||
|
|
||||||
|
def testKickClient(self):
|
||||||
|
target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid
|
||||||
|
self.proto.joinClient(self.irc, target, '#pylink')
|
||||||
|
self.assertIn(self.u, self.irc.channels['#pylink'].users)
|
||||||
|
self.assertIn(target, self.irc.channels['#pylink'].users)
|
||||||
|
self.proto.kickClient(self.irc, self.u, '#pylink', target, 'Pow!')
|
||||||
|
self.assertNotIn(target, self.irc.channels['#pylink'].users)
|
||||||
|
|
||||||
|
def testModeClient(self):
|
||||||
|
testuser = self.proto.spawnClient(self.irc, 'testcakes')
|
||||||
|
self.irc.takeMsgs()
|
||||||
|
self.proto.modeClient(self.irc, self.u, testuser.uid, [('+i', None), ('+w', None)])
|
||||||
|
self.assertEqual({('i', None), ('w', None)}, testuser.modes)
|
||||||
|
|
||||||
|
self.proto.modeClient(self.irc, self.u, '#pylink', [('+s', None), ('+l', '30')])
|
||||||
|
self.assertEqual({('s', None), ('l', '30')}, self.irc.channels['#pylink'].modes)
|
||||||
|
|
||||||
|
cmds = self.irc.takeCommands(self.irc.takeMsgs())
|
||||||
|
self.assertEqual(cmds, ['MODE', 'FMODE'])
|
||||||
|
|
||||||
|
def testNickClient(self):
|
||||||
|
self.proto.nickClient(self.irc, self.u, 'NotPyLink')
|
||||||
|
self.assertEqual('NotPyLink', self.irc.users[self.u].nick)
|
||||||
|
|
||||||
|
def testPartClient(self):
|
||||||
|
u = self.u
|
||||||
|
self.proto.joinClient(self.irc, u, '#channel')
|
||||||
|
self.proto.partClient(self.irc, u, '#channel')
|
||||||
|
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
||||||
|
|
||||||
|
def testQuitClient(self):
|
||||||
|
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
|
||||||
|
self.proto.joinClient(self.irc, u, '#channel')
|
||||||
|
self.assertRaises(LookupError, self.proto.quitClient, self.irc, '9PYZZZZZZ', 'quit reason')
|
||||||
|
self.proto.quitClient(self.irc, u, 'quit reason')
|
||||||
|
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
||||||
|
self.assertNotIn(u, self.irc.users)
|
||||||
|
self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
|
||||||
|
|
||||||
|
def testSpawnClient(self):
|
||||||
|
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
|
||||||
|
# Check the server index and the user index
|
||||||
|
self.assertIn(u, self.irc.servers[self.irc.sid].users)
|
||||||
|
self.assertIn(u, self.irc.users)
|
||||||
|
# Raise ValueError when trying to spawn a client on a server that's not ours
|
||||||
|
self.assertRaises(ValueError, self.proto.spawnClient, self.irc, 'abcd', 'user', 'dummy.user.net', server='44A')
|
||||||
|
# Unfilled args should get placeholder fields and not error.
|
||||||
|
self.proto.spawnClient(self.irc, 'testuser4')
|
||||||
|
|
||||||
|
def testSpawnClientOnServer(self):
|
||||||
|
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
|
||||||
|
u = self.proto.spawnClient(self.irc, 'person1', 'person', 'users.overdrive.pw', server='34Q')
|
||||||
|
# We're spawning clients on the right server, hopefully...
|
||||||
|
self.assertIn(u.uid, self.irc.servers['34Q'].users)
|
||||||
|
self.assertNotIn(u.uid, self.irc.servers[self.irc.sid].users)
|
||||||
|
|
||||||
|
def testSpawnServer(self):
|
||||||
|
# Incorrect SID length
|
||||||
|
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'subserver.pylink', '34Q0')
|
||||||
|
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
|
||||||
|
# Duplicate server name
|
||||||
|
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'Subserver.PyLink', '34Z')
|
||||||
|
# Duplicate SID
|
||||||
|
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'another.Subserver.PyLink', '34Q')
|
||||||
|
self.assertIn('34Q', self.irc.servers)
|
79
utils.py
79
utils.py
@ -1,19 +1,13 @@
|
|||||||
import string
|
import string
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
import inspect
|
||||||
import threading
|
|
||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
|
import world
|
||||||
|
|
||||||
global bot_commands, command_hooks
|
# This is separate from classes.py to prevent import loops.
|
||||||
# This should be a mapping of command names to functions
|
class NotAuthenticatedError(Exception):
|
||||||
bot_commands = {}
|
pass
|
||||||
command_hooks = defaultdict(list)
|
|
||||||
networkobjects = {}
|
|
||||||
schedulers = {}
|
|
||||||
plugins = []
|
|
||||||
whois_handlers = []
|
|
||||||
started = threading.Event()
|
|
||||||
|
|
||||||
class TS6UIDGenerator():
|
class TS6UIDGenerator():
|
||||||
"""TS6 UID Generator module, adapted from InspIRCd source
|
"""TS6 UID Generator module, adapted from InspIRCd source
|
||||||
@ -114,12 +108,12 @@ def add_cmd(func, name=None):
|
|||||||
if name is None:
|
if name is None:
|
||||||
name = func.__name__
|
name = func.__name__
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
bot_commands[name] = func
|
world.bot_commands[name].append(func)
|
||||||
|
|
||||||
def add_hook(func, command):
|
def add_hook(func, command):
|
||||||
"""Add a hook <func> for command <command>."""
|
"""Add a hook <func> for command <command>."""
|
||||||
command = command.upper()
|
command = command.upper()
|
||||||
command_hooks[command].append(func)
|
world.command_hooks[command].append(func)
|
||||||
|
|
||||||
def toLower(irc, text):
|
def toLower(irc, text):
|
||||||
"""<irc object> <text>
|
"""<irc object> <text>
|
||||||
@ -168,7 +162,7 @@ def isServerName(s):
|
|||||||
return _isASCII(s) and '.' in s and not s.startswith('.')
|
return _isASCII(s) and '.' in s and not s.startswith('.')
|
||||||
|
|
||||||
def parseModes(irc, target, args):
|
def parseModes(irc, target, args):
|
||||||
"""Parses a mode string 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')]
|
||||||
"""
|
"""
|
||||||
# http://www.irc.org/tech_docs/005.html
|
# http://www.irc.org/tech_docs/005.html
|
||||||
@ -340,6 +334,34 @@ def joinModes(modes):
|
|||||||
modelist += ' %s' % ' '.join(args)
|
modelist += ' %s' % ' '.join(args)
|
||||||
return modelist
|
return modelist
|
||||||
|
|
||||||
|
def reverseModes(irc, target, modes):
|
||||||
|
"""<mode string/mode list>
|
||||||
|
|
||||||
|
Reverses/Inverts the mode string or mode list given.
|
||||||
|
|
||||||
|
"+nt-lk" => "-nt+lk"
|
||||||
|
"nt-k" => "-nt+k"
|
||||||
|
[('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')] =>
|
||||||
|
[('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')]
|
||||||
|
[('s', None), ('+n', None)] => [('-s', None), ('-n', None)]
|
||||||
|
"""
|
||||||
|
origtype = type(modes)
|
||||||
|
# Operate on joined modestrings only; it's easier.
|
||||||
|
if origtype != str:
|
||||||
|
modes = joinModes(modes)
|
||||||
|
# Swap the +'s and -'s by replacing one with a dummy character, and then changing it back.
|
||||||
|
assert '\x00' not in modes, 'NUL cannot be in the mode list (it is a reserved character)!'
|
||||||
|
if not modes.startswith(('+', '-')):
|
||||||
|
modes = '+' + modes
|
||||||
|
newmodes = modes.replace('+', '\x00')
|
||||||
|
newmodes = newmodes.replace('-', '+')
|
||||||
|
newmodes = newmodes.replace('\x00', '-')
|
||||||
|
if origtype != str:
|
||||||
|
# If the original query isn't a string, send back the parseModes() output.
|
||||||
|
return parseModes(irc, target, newmodes.split(" "))
|
||||||
|
else:
|
||||||
|
return newmodes
|
||||||
|
|
||||||
def isInternalClient(irc, numeric):
|
def isInternalClient(irc, numeric):
|
||||||
"""<irc object> <client numeric>
|
"""<irc object> <client numeric>
|
||||||
|
|
||||||
@ -357,15 +379,38 @@ def isInternalServer(irc, sid):
|
|||||||
"""
|
"""
|
||||||
return (sid in irc.servers and irc.servers[sid].internal)
|
return (sid in irc.servers and irc.servers[sid].internal)
|
||||||
|
|
||||||
def isOper(irc, uid):
|
def isOper(irc, uid, allowAuthed=True, allowOper=True):
|
||||||
"""<irc object> <UID>
|
"""<irc object> <UID>
|
||||||
|
|
||||||
Returns whether <UID> has operator status on PyLink. This can be achieved
|
Returns whether <UID> has operator status on PyLink. This can be achieved
|
||||||
by either identifying to PyLink as admin, or having user mode +o set.
|
by either identifying to PyLink as admin (if allowAuthed is True),
|
||||||
|
or having user mode +o set (if allowOper is True). At least one of
|
||||||
|
allowAuthed or allowOper must be True for this to give any meaningful
|
||||||
|
results.
|
||||||
"""
|
"""
|
||||||
return (uid in irc.users and (("o", None) in irc.users[uid].modes or irc.users[uid].identified))
|
if uid in irc.users:
|
||||||
|
if allowOper and ("o", None) in irc.users[uid].modes:
|
||||||
|
return True
|
||||||
|
elif allowAuthed and irc.users[uid].identified:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def checkAuthenticated(irc, uid, allowAuthed=True, allowOper=True):
|
||||||
|
"""<irc object> <UID>
|
||||||
|
|
||||||
|
Checks whether user <UID> has operator status on PyLink, raising
|
||||||
|
NotAuthenticatedError and logging the access denial if not."""
|
||||||
|
lastfunc = inspect.stack()[1][3]
|
||||||
|
if not isOper(irc, uid, allowAuthed=allowAuthed, allowOper=allowOper):
|
||||||
|
log.warning('(%s) Access denied for %s calling %r', irc.name,
|
||||||
|
getHostmask(irc, uid), lastfunc)
|
||||||
|
raise NotAuthenticatedError("You are not authenticated!")
|
||||||
|
return True
|
||||||
|
|
||||||
def getHostmask(irc, user):
|
def getHostmask(irc, user):
|
||||||
|
"""<irc object> <UID>
|
||||||
|
|
||||||
|
Gets the hostmask of user <UID>, if present."""
|
||||||
userobj = irc.users.get(user)
|
userobj = irc.users.get(user)
|
||||||
if userobj is None:
|
if userobj is None:
|
||||||
return '<user object not found>'
|
return '<user object not found>'
|
||||||
|
18
world.py
Normal file
18
world.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# world.py: global state variables go here
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# Global variable to indicate whether we're being ran directly, or imported
|
||||||
|
# for a testcase.
|
||||||
|
testing = True
|
||||||
|
|
||||||
|
global bot_commands, command_hooks
|
||||||
|
# This should be a mapping of command names to functions
|
||||||
|
bot_commands = defaultdict(list)
|
||||||
|
command_hooks = defaultdict(list)
|
||||||
|
networkobjects = {}
|
||||||
|
schedulers = {}
|
||||||
|
plugins = []
|
||||||
|
whois_handlers = []
|
||||||
|
started = threading.Event()
|
Loading…
Reference in New Issue
Block a user