3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-01-13 13:42:37 +01:00

Merge branch 'master' into wip/unrealircd

This commit is contained in:
James Lu 2015-10-08 20:14:30 -07:00
commit 7c7f07b3a9
24 changed files with 926 additions and 308 deletions

View File

@ -8,6 +8,8 @@ PyLink is an extensible, plugin-based IRC Services framework written in Python.
That said, please report any bugs you find to the [issue tracker](https://github.com/GLolol/PyLink/issues). Pull requests are open if you'd like to contribute: note that **master** is bugfix only; new stuff goes to the **devel** branch. That said, please report any bugs you find to the [issue tracker](https://github.com/GLolol/PyLink/issues). Pull requests are open if you'd like to contribute: note that **master** is bugfix only; new stuff goes to the **devel** branch.
You can also find support via our IRC channel: `#PyLink at irc.overdrive.pw` ([webchat](http://webchat.overdrive.pw/?channels=PyLink)). Ask your question and be patient.
### Dependencies ### Dependencies
Dependencies currently include: Dependencies currently include:

View File

@ -9,8 +9,8 @@ import hashlib
from copy import deepcopy from copy import deepcopy
from log import log from log import log
from conf import conf
import world import world
import utils
### Exceptions ### Exceptions
@ -21,11 +21,20 @@ class ProtocolError(Exception):
class Irc(): class Irc():
def initVars(self): def initVars(self):
self.sid = self.serverdata["sid"]
self.botdata = self.conf['bot']
self.pingfreq = self.serverdata.get('pingfreq') or 30
self.pingtimeout = self.pingfreq * 2
self.connected.clear() self.connected.clear()
self.aborted.clear() self.aborted.clear()
self.pseudoclient = None self.pseudoclient = None
self.lastping = time.time() self.lastping = time.time()
# Internal variable to set the place the last command was called (in PM
# or in a channel), used by fantasy command support.
self.called_by = None
# Server, channel, and user indexes to be populated by our protocol module # Server, channel, and user indexes to be populated by our protocol module
self.servers = {self.sid: IrcServer(None, self.serverdata['hostname'], self.servers = {self.sid: IrcServer(None, self.serverdata['hostname'],
internal=True, desc=self.serverdata.get('serverdesc') internal=True, desc=self.serverdata.get('serverdesc')
@ -58,7 +67,7 @@ class Irc():
self.uplink = None self.uplink = None
self.start_ts = int(time.time()) self.start_ts = int(time.time())
def __init__(self, netname, proto): def __init__(self, netname, proto, conf):
# Initialize some variables # Initialize some variables
self.name = netname.lower() self.name = netname.lower()
self.conf = conf self.conf = conf
@ -84,13 +93,14 @@ class Irc():
self.pingTimer = None self.pingTimer = None
def connect(self): def connect(self):
ip = self.serverdata["ip"]
port = self.serverdata["port"]
while True: while True:
self.initVars() self.initVars()
ip = self.serverdata["ip"]
port = self.serverdata["port"]
checks_ok = True checks_ok = True
try: try:
self.socket = socket.socket() stype = socket.AF_INET6 if self.serverdata.get("ipv6") else socket.AF_INET
self.socket = socket.socket(stype)
self.socket.setblocking(0) self.socket.setblocking(0)
# Initial connection timeout is a lot smaller than the timeout after # Initial connection timeout is a lot smaller than the timeout after
# we've connected; this is intentional. # we've connected; this is intentional.
@ -160,20 +170,41 @@ class Irc():
self._disconnect() self._disconnect()
autoconnect = self.serverdata.get('autoconnect') autoconnect = self.serverdata.get('autoconnect')
log.debug('(%s) Autoconnect delay set to %s seconds.', self.name, autoconnect) log.debug('(%s) Autoconnect delay set to %s seconds.', self.name, autoconnect)
if autoconnect is not None and autoconnect >= 0: if autoconnect is not None and autoconnect >= 1:
log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect) log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect)
time.sleep(autoconnect) time.sleep(autoconnect)
else: else:
log.info('(%s) Stopping connect loop (autoconnect value %r is < 1).', self.name, autoconnect)
return return
def callCommand(self, source, text):
cmd_args = text.strip().split(' ')
cmd = cmd_args[0].lower()
cmd_args = cmd_args[1:]
if cmd not in world.commands:
self.msg(self.called_by or source, 'Error: Unknown command %r.' % cmd)
return
log.info('(%s) Calling command %r for %s', self.name, cmd, utils.getHostmask(self, source))
for func in world.commands[cmd]:
try:
func(self, source, cmd_args)
except utils.NotAuthenticatedError:
self.msg(self.called_by or source, 'Error: You are not authorized to perform this operation.')
except Exception as e:
log.exception('Unhandled exception caught in command %r', cmd)
self.msg(self.called_by or source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
def msg(self, target, text, notice=False, source=None): def msg(self, target, text, notice=False, source=None):
"""Handy function to send messages/notices to clients. Source """Handy function to send messages/notices to clients. Source
is optional, and defaults to the main PyLink client if not specified.""" is optional, and defaults to the main PyLink client if not specified."""
source = source or self.pseudoclient.uid source = source or self.pseudoclient.uid
if notice: if notice:
self.proto.noticeClient(source, target, text) self.proto.noticeClient(source, target, text)
cmd = 'PYLINK_SELF_NOTICE'
else: else:
self.proto.messageClient(source, target, text) self.proto.messageClient(source, target, text)
cmd = 'PYLINK_SELF_PRIVMSG'
self.callHooks([source, cmd, {'target': target, 'text': text}])
def _disconnect(self): def _disconnect(self):
log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time()) log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time())
@ -242,9 +273,10 @@ class Irc():
log.debug('(%s) Parsed args %r received from %s handler (calling hook %s)', log.debug('(%s) Parsed args %r received from %s handler (calling hook %s)',
self.name, parsed_args, command, hook_cmd) self.name, parsed_args, command, hook_cmd)
# Iterate over hooked functions, catching errors accordingly # Iterate over hooked functions, catching errors accordingly
for hook_func in world.command_hooks[hook_cmd]: for hook_func in world.hooks[hook_cmd]:
try: try:
log.debug('(%s) Calling function %s', self.name, hook_func) log.debug('(%s) Calling hook function %s from plugin "%s"', self.name,
hook_func, hook_func.__module__)
hook_func(self, numeric, command, parsed_args) hook_func(self, numeric, command, parsed_args)
except Exception: except Exception:
# We don't want plugins to crash our servers... # We don't want plugins to crash our servers...
@ -286,6 +318,9 @@ class Irc():
# contents of Irc().pseudoclient change. # contents of Irc().pseudoclient change.
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}]) self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
def __repr__(self):
return "<classes.Irc object for %r>" % self.name
class IrcUser(): class IrcUser():
def __init__(self, nick, ts, uid, ident='null', host='null', def __init__(self, nick, ts, uid, ident='null', host='null',
realname='PyLink dummy client', realhost='null', realname='PyLink dummy client', realhost='null',
@ -408,6 +443,44 @@ class Protocol():
self.casemapping = 'rfc1459' self.casemapping = 'rfc1459'
self.hook_map = {} self.hook_map = {}
def parseArgs(self, args):
"""Parses a string of RFC1459-style arguments split into a list, where ":" may
be used for multi-word arguments that last until the end of a line.
"""
real_args = []
for idx, arg in enumerate(args):
real_args.append(arg)
# If the argument starts with ':' and ISN'T the first argument.
# The first argument is used for denoting the source UID/SID.
if arg.startswith(':') and idx != 0:
# : is used for multi-word arguments that last until the end
# of the message. We can use list splicing here to turn them all
# into one argument.
# Set the last arg to a joined version of the remaining args
arg = args[idx:]
arg = ' '.join(arg)[1:]
# Cut the original argument list right before the multi-word arg,
# and then append the multi-word arg.
real_args = args[:idx]
real_args.append(arg)
break
return real_args
def removeClient(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
del self.irc.channels[c]
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
sid = numeric[:3]
log.debug('Removing client %s from self.irc.users', numeric)
del self.irc.users[numeric]
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
class FakeProto(Protocol): class FakeProto(Protocol):
"""Dummy protocol module for testing purposes.""" """Dummy protocol module for testing purposes."""
def handle_events(self, data): def handle_events(self, data):

34
conf.py
View File

@ -31,9 +31,36 @@ testconf = {'bot':
'sidrange': '8##' 'sidrange': '8##'
}) })
} }
def validateConf(conf):
"""Validates a parsed configuration dict."""
assert type(conf) == dict, "Invalid configuration given: should be type dict, not %s." % type(conf).__name__
for section in ('bot', 'servers', 'login'):
assert conf.get(section), "Missing %r section in config." % section
for netname, serverblock in conf['servers'].items():
for section in ('ip', 'port', 'recvpass', 'sendpass', 'hostname',
'sid', 'sidrange', 'protocol', 'maxnicklen'):
assert serverblock.get(section), "Missing %r in server block for %r." % (section, netname)
assert type(serverblock.get('channels')) == list, "'channels' option in " \
"server block for %s must be a list, not %s." % (netname, type(serverblock['channels']).__name__)
assert type(conf['login'].get('password')) == type(conf['login'].get('user')) == str and \
conf['login']['password'] != "changeme", "You have not set the login details correctly!"
return conf
def loadConf(fname):
"""Loads a PyLink configuration file from the filename given."""
with open(fname, 'r') as f:
try:
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)
return conf
if world.testing: if world.testing:
conf = testconf conf = testconf
confname = 'testconf' confname = 'testconf'
fname = None
else: 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
@ -46,9 +73,4 @@ else:
# we load. # we load.
confname = 'pylink' confname = 'pylink'
fname = 'config.yml' fname = 'config.yml'
with open(fname, 'r') as f: conf = validateConf(loadConf(fname))
try:
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)

View File

@ -15,6 +15,9 @@ bot:
# Console log verbosity: see https://docs.python.org/3/library/logging.html#logging-levels # Console log verbosity: see https://docs.python.org/3/library/logging.html#logging-levels
loglevel: DEBUG loglevel: DEBUG
# Fantasy command prefix, if the fantasy plugin is loaded.
prefix: "."
login: login:
# PyLink administrative login - Change this, or the service will not start! # PyLink administrative login - Change this, or the service will not start!
user: admin user: admin
@ -36,6 +39,10 @@ relay:
# will see yours. # will see yours.
show_ips: false show_ips: false
# Whether subservers should be spawned for each relay network (requires reload to change).
# Defaults to true.
spawn_servers: true
servers: servers:
yournet: yournet:
# Server IP, port, and passwords # Server IP, port, and passwords
@ -67,7 +74,7 @@ servers:
# (omitting the .py extension). # (omitting the .py extension).
protocol: "inspircd" protocol: "inspircd"
# Sets autoconnect delay - comment this out or set the value negative to disable autoconnect entirely. # Sets autoconnect delay - comment this out or set the value below 1 to disable autoconnect entirely.
autoconnect: 5 autoconnect: 5
# Sets ping frequency (i.e. how long we should wait between sending pings to our uplink). # Sets ping frequency (i.e. how long we should wait between sending pings to our uplink).
@ -94,7 +101,11 @@ servers:
# ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc" # ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc"
ts6net: ts6net:
ip: 127.0.0.1 ip: ::1
# Determines whether IPv6 should be used for this connection.
ipv6: yes
port: 7000 port: 7000
recvpass: "abcd" recvpass: "abcd"
sendpass: "abcd" sendpass: "abcd"
@ -129,7 +140,19 @@ servers:
# Plugins to load (omit the .py extension) # Plugins to load (omit the .py extension)
plugins: plugins:
# Commands plugin: provides core commands such as logging in, shutting down PyLink, and
# various command help.
- commands - commands
# Networks plugin: allows you to manage (dis)connections to networks while PyLink is running.
- networks - networks
# Bots plugin: allows you to manipulate pseudo-clients (bots) on networks.
# - bots # - bots
# Relay plugin: Janus-style server-side relay plugin.
# - relay # - relay
# Fantasy plugin: allows you to trigger commands in channels by PyLink's nick or a
# configurable prefix character.
# - fantasy

View File

@ -20,23 +20,10 @@ utils.add_hook(handle_kick, 'KICK')
def handle_commands(irc, source, command, args): def handle_commands(irc, source, command, args):
"""Handle commands sent to the PyLink client (PRIVMSG).""" """Handle commands sent to the PyLink client (PRIVMSG)."""
if args['target'] == irc.pseudoclient.uid: if args['target'] == irc.pseudoclient.uid and not utils.isInternalClient(irc, source):
text = args['text'].strip() irc.called_by = source
cmd_args = text.split(' ') irc.callCommand(source, args['text'])
cmd = cmd_args[0].lower()
cmd_args = cmd_args[1:]
if cmd not in world.bot_commands:
irc.msg(source, 'Error: Unknown command %r.' % cmd)
return
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)
except utils.NotAuthenticatedError:
irc.msg(source, 'Error: You are not authorized to perform this operation.')
except Exception as e:
log.exception('Unhandled exception caught in command %r', cmd)
irc.msg(source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
utils.add_hook(handle_commands, 'PRIVMSG') utils.add_hook(handle_commands, 'PRIVMSG')
def handle_whois(irc, source, command, args): def handle_whois(irc, source, command, args):

View File

@ -18,6 +18,6 @@ PyLink is an a modular, plugin-based IRC PseudoService framework. It uses swappa
#### Future topics (not yet available) #### Future topics (not yet available)
- [Using PyLink's utils module](using-utils.md)
- [PyLink hooks reference](hooks-reference.md) - [PyLink hooks reference](hooks-reference.md)
- [Writing tests for PyLink modules](writing-tests.md) - [Writing tests for PyLink modules](writing-tests.md)
- [Using PyLink's utils module](using-utils.md)

View File

@ -74,9 +74,11 @@ For&nbsp;InspIRCd,&nbsp;the&nbsp;only&nbsp;ENCAP&nbsp;command&nbsp;we&nbsp;handl
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.&nbsp;This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to<br> <dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.<br>
the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions&nbsp;elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also<br> &nbsp;<br>
handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl> This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to&nbsp;the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions<br>
elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also&nbsp;handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the<br>
initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl>
@ -94,6 +96,8 @@ handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;l
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br> <dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br>
&nbsp;<br> &nbsp;<br>
This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br> This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br>
@ -101,9 +105,11 @@ opertype&nbsp;of&nbsp;the&nbsp;client,&nbsp;and&nbsp;assumes&nbsp;setting&nbsp;u
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.&nbsp;This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether<br> <dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.<br>
the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply<br> &nbsp;<br>
to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl> This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether&nbsp;the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()<br>
internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply&nbsp;to&nbsp;our&nbsp;PINGs&nbsp;eventually<br>
times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl>
@ -111,7 +117,7 @@ to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;an&nbsp;internal&nbsp;spawned&nbsp;client&nbsp;&lt;client&gt;&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;a&nbsp;PyLink&nbsp;client&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
@ -119,13 +125,13 @@ to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br> <dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;mode&nbsp;changes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl> a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br> <dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;mode&nbsp;changes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl> a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, text)</dt></dl> <dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, target, text)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br> <dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br>
automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl> automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl>
@ -140,16 +146,19 @@ Example&nbsp;uses:<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br> <dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None, manipulatable=False)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br>
&nbsp;<br> &nbsp;<br>
Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br> Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br>
up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl> up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;desc&nbsp;(server&nbsp;description)<br>
defaults&nbsp;to&nbsp;the&nbsp;one&nbsp;in&nbsp;the&nbsp;config.&nbsp;uplink&nbsp;defaults&nbsp;to&nbsp;the&nbsp;main&nbsp;PyLink<br>
server,&nbsp;and&nbsp;sid&nbsp;(the&nbsp;server&nbsp;ID)&nbsp;is&nbsp;automatically&nbsp;generated&nbsp;if&nbsp;not<br>
given.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;burst&nbsp;topic&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usally&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;topic&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usually&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
@ -161,9 +170,6 @@ Methods inherited from <a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6B
<dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.&nbsp;For&nbsp;channel&nbsp;mode&nbsp;changes,<br>
TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
@ -172,7 +178,7 @@ TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;use
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUITs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUIT&nbsp;commands.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl>
@ -191,9 +197,6 @@ TB&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FTOPIC&nbsp;(InspIRCd)&nbsp;are&nbsp;used&
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#Class-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br> <dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#Class-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br>
of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl> of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl>
@ -201,10 +204,15 @@ of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></d
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Methods inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<hr> <hr>
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br> Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><strong>__dict__</strong></dt> <dl><dt><strong>__dict__</strong></dt>
@ -245,9 +253,11 @@ For&nbsp;InspIRCd,&nbsp;the&nbsp;only&nbsp;ENCAP&nbsp;command&nbsp;we&nbsp;handl
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.&nbsp;This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to<br> <dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.<br>
the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions&nbsp;elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also<br> &nbsp;<br>
handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl> This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to&nbsp;the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions<br>
elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also&nbsp;handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the<br>
initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl>
@ -265,6 +275,8 @@ handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;l
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br> <dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br>
&nbsp;<br> &nbsp;<br>
This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br> This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br>
@ -272,9 +284,11 @@ opertype&nbsp;of&nbsp;the&nbsp;client,&nbsp;and&nbsp;assumes&nbsp;setting&nbsp;u
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.&nbsp;This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether<br> <dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.<br>
the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply<br> &nbsp;<br>
to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl> This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether&nbsp;the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()<br>
internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply&nbsp;to&nbsp;our&nbsp;PINGs&nbsp;eventually<br>
times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl>
@ -282,7 +296,7 @@ to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;an&nbsp;internal&nbsp;spawned&nbsp;client&nbsp;&lt;client&gt;&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;a&nbsp;PyLink&nbsp;client&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
@ -290,13 +304,13 @@ to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br> <dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;mode&nbsp;changes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl> a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br> <dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;mode&nbsp;changes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl> a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, text)</dt></dl> <dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, target, text)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br> <dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br>
automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl> automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl>
@ -311,16 +325,19 @@ Example&nbsp;uses:<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br> <dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None, manipulatable=False)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br>
&nbsp;<br> &nbsp;<br>
Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br> Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br>
up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl> up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;desc&nbsp;(server&nbsp;description)<br>
defaults&nbsp;to&nbsp;the&nbsp;one&nbsp;in&nbsp;the&nbsp;config.&nbsp;uplink&nbsp;defaults&nbsp;to&nbsp;the&nbsp;main&nbsp;PyLink<br>
server,&nbsp;and&nbsp;sid&nbsp;(the&nbsp;server&nbsp;ID)&nbsp;is&nbsp;automatically&nbsp;generated&nbsp;if&nbsp;not<br>
given.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;burst&nbsp;topic&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usally&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;topic&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usually&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
@ -332,9 +349,6 @@ Methods inherited from <a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6B
<dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.&nbsp;For&nbsp;channel&nbsp;mode&nbsp;changes,<br>
TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
@ -343,7 +357,7 @@ TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;use
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUITs.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUIT&nbsp;commands.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl>
@ -362,9 +376,6 @@ TB&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FTOPIC&nbsp;(InspIRCd)&nbsp;are&nbsp;used&
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#InspIRCdProtocol-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br> <dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#InspIRCdProtocol-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br>
of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl> of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl>
@ -372,10 +383,15 @@ of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></d
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl> <dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Methods inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<hr> <hr>
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br> Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><strong>__dict__</strong></dt> <dl><dt><strong>__dict__</strong></dt>
@ -391,7 +407,6 @@ Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr> <font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td> <tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><strong>conf</strong> = {'bot': {'loglevel': 'CRITICAL', 'nick': 'PyLink', 'realname': 'PyLink Service Client', 'serverdesc': 'PyLink unit tests', 'user': 'pylink'}, 'servers': defaultdict(&lt;function &lt;lambda&gt; at 0x7f0dbb516c80&gt;, {})}<br> <td width="100%"><strong>curdir</strong> = 'protocols'<br>
<strong>curdir</strong> = 'protocols'<br>
<strong>log</strong> = &lt;logging.RootLogger object&gt;</td></tr></table> <strong>log</strong> = &lt;logging.RootLogger object&gt;</td></tr></table>
</body></html> </body></html>

View File

@ -1,27 +1,93 @@
This page is still incomplete. For now, see [inspircd.html](inspircd.html) for an auto-generated specification of the InspIRCd protocol module. Any camelCase `ABCServer/Client` functions are outgoing commands, and include the following: # PyLink Protocol Module Specification
- `awayClient` In PyLink, each protocol module is a single file consisting of a protocol class, and a global `Class` attribute that is set equal to it (e.g. `Class = InspIRCdProtocol`). These classes should be based off of either [`classes.Protocol`](https://github.com/GLolol/PyLink/blob/0.4.0-dev/classes.py#L404), a boilerplate class that only defines a few variables, or [`ts6_common.TS6BaseProtocol`](https://github.com/GLolol/PyLink/blob/0.4.0-dev/protocols/ts6_common.py#L10), which includes elements of the TS6 protocol that are shared by both the InspIRCd and TS6 protocols. IRC objects initialize protocol modules by creating an instance of its class, and passing it the IRC object itself.
- `inviteClient`
- `joinClient` See also: [inspircd.html](inspircd.html) for an auto-generated specification of the InspIRCd protocol module.
- `kickClient`
- `kickServer` ## Tasks
- `killClient`
- `killServer` Protocol modules have some *very* ***important*** jobs. If any of these aren't done correctly, you will be left with a very **broken, desynced** services server:
- `knockClient`
- `messageClient` 1) Handle incoming commands from the uplink IRCd.
- `modeClient`
- `modeServer` 2) Return [hook data](hooks-reference.md) for relevant commands, so that plugins can receive data from IRC.
- `nickClient`
- `noticeClient` 3) Make sure channel/user states are kept correctly. Joins, quits, parts, kicks, mode changes, nick changes, etc. **must** be handled accurately.
- `numericServer`
- `partClient` 4) Respond to both pings *and* pongs - the `irc.lastping` attribute **must** be set to the current time whenever a `PONG` is received from the uplink, so PyLink's internals don't [lag out the uplink thinking it isn't responding to our `PING`s](https://github.com/GLolol/PyLink/blob/0.4.0-dev/classes.py#L202-L204).
- `pingServer`
- `quitClient` 5) Implement a series of camelCase `commandServer/Client` functions - plugins use these for sending outgoing commands. See the `Outbound commands` section below for a list of which ones are needed.
- `removeClient`
- `sjoinServer` ## Core functions
- `spawnClient`
- `spawnServer` The following functions *must* be implemented by any protocol module within its main class, since they are used by the IRC internals.
- `squitServer`
- `topicClient` - **`connect`**`(self)` - Initializes a connection to a server.
- `topicServer`
- `updateClient` - **`handle_events`**`(self, line)` - Handles inbound data (lines of text) from the uplink IRC server. Normally, this will pass commands to other command handlers within the protocol module, dropping commands that are unrecognized (wildcard handling), but it's really up to you how to structure your modules. You will want to be able to parse command arguments properly into a list: many protocols send RFC1459-style commands that can be parsed using the [`self.parseArgs(line)` function within the `Protocol` class](https://github.com/GLolol/PyLink/blob/c77d170765d20b0ac55b945fba4a6257fb15cf43/classes.py#L411).
- All of the outbound commands mentioned in the next section (minus raw numerics) should have their incoming version handled and [hook data](hooks-reference.md) returned.
### Outgoing command functions
- **`spawnClient`**`(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None)` - Spawns a client on the given IRC connection. Note that no nick collision / valid nickname checks are done here, and it is up to plugins to make sure they don't introduce anything invalid.
- `modes` is a set of `(mode char, mode arg)` tuples in the form of [`utils.parseModes()` output](using-utils.md#parseModes).
- `ident` and `host` default to "null", while `realhost` defaults to the same things as `host` if not defined.
- `realname` defaults to the real name specified in the PyLink config, if not given. `ts` defaults to the current time and `opertype` (the oper type name, if applicable) defaults to the simple text of `IRC Operator`.
- **`joinClient`**`(self, client, channel)` - Joins the client UID given to a channel.
- **`awayClient`**`(self, source, text)` - Sends an AWAY message from a PyLink client. `text` can be an empty string to unset AWAY status.
- **`inviteClient`**`(self, source, target, channel)` - Sends an INVITE from a PyLink client.
- **`kickClient`**`(self, source, channel, target, reason=None)` - Sends a kick from a PyLink client.
- **`kickServer`**`(self, source, channel, target, reason=None)` - Sends a kick from a PyLink server.
- **`killClient`**`(self, source, target, reason)` - Sends a kill from a PyLink client.
- **`killServer`**`(self, source, target, reason)` - Sends a kill from a PyLink server.
- **`knockClient`**`(self, source, target, text)` - Sends a KNOCK from a PyLink client.
- **`messageClient`**`(self, source, target, text)` - Sends a PRIVMSG from a PyLink client.
- **`modeClient`**`(self, source, target, modes, ts=None)` - Sends modes from a PyLink client. `modes` takes a set of `([+/-]mode char, mode arg)` tuples.
- **`modeServer`**`(self, source, target, modes, ts=None)` - Sends modes from a PyLink server.
- **`nickClient`**`(self, source, newnick)` - Changes the nick of a PyLink client.
- **`noticeClient`**`(self, source, target, text)` - Sends a NOTICE from a PyLink client.
- **`numericServer`**`(self, source, numeric, target, text)` - Sends a raw numeric `numeric` with `text` from the `source` server to `target`.
- **`partClient`**`(self, client, channel, reason=None)` - Sends a part from a PyLink client.
- **`pingServer`**`(self, source=None, target=None)` - Sends a PING to a target server. Periodic PINGs are sent to our uplink automatically by the [`Irc()`
internals](https://github.com/GLolol/PyLink/blob/0.4.0-dev/classes.py#L267-L272); plugins shouldn't have to use this.
- **`quitClient`**`(self, source, reason)` - Quits a PyLink client.
- **`sjoinServer`**`(self, server, channel, users, ts=None)` - Sends an SJOIN for a group of users to a channel. The sender should always be a Server ID (SID). TS is
optional, and defaults to the one we've stored in the channel state if not given. `users` is a list of `(prefix mode, UID)` pairs. Example uses:
- `sjoinServer('100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])`
- `sjoinServer(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])`
- **`spawnServer`**`(self, name, sid=None, uplink=None, desc=None)` - Spawns a server off another PyLink server. `desc` (server description) defaults to the one in the config. `uplink` defaults to the main PyLink server, and `sid` (the server ID) is automatically generated if not given.
- **`squitServer`**`(self, source, target, text='No reason given')` - SQUITs a PyLink server.
- **`topicClient`**`(self, source, target, text)` - Sends a topic change from a PyLink client.
- **`topicServer`**`(self, source, target, text)` - Sends a topic change from a PyLink server. This is usually used on burst.
- **`updateClient`**`(self, source, field, text)` - Updates the ident, host, or realname of a PyLink client. `field` should be either "IDENT", "HOST", "GECOS", or
"REALNAME". If changing the field given on the IRCd isn't supported, `NotImplementedError` should be raised.
## Variables to note
A protocol module should also set the following variables in their protocol class:
- `self.casemapping`: set this to `rfc1459` (default) or `ascii` to determine which case mapping should be used.
- `self.hook_map`: map a list of non-standard command names sent by the IRCd to those more commonly used: examples in the [TS6](https://github.com/GLolol/PyLink/blob/0.4.0-dev/protocols/ts6.py#L19) and [InspIRCd](https://github.com/GLolol/PyLink/blob/0.4.0-dev/protocols/inspircd.py#L24) modules.

View File

@ -0,0 +1 @@
This page is under construction: see [utils.html ](utils.html) for autogenerated documentation for the utils module.

233
docs/technical/utils.html Normal file
View File

@ -0,0 +1,233 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: module utils</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body bgcolor="#f0f0f8">
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong>utils</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/gl/pylink/utils.py">/home/gl/pylink/utils.py</a></font></td></tr></table>
<p></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="imp.html">imp</a><br>
<a href="inspect.html">inspect</a><br>
</td><td width="25%" valign=top><a href="re.html">re</a><br>
<a href="string.html">string</a><br>
</td><td width="25%" valign=top><a href="world.html">world</a><br>
</td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="builtins.html#Exception">builtins.Exception</a>(<a href="builtins.html#BaseException">builtins.BaseException</a>)
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="utils.html#NotAuthenticatedError">NotAuthenticatedError</a>
</font></dt></dl>
</dd>
<dt><font face="helvetica, arial"><a href="builtins.html#object">builtins.object</a>
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="utils.html#TS6SIDGenerator">TS6SIDGenerator</a>
</font></dt><dt><font face="helvetica, arial"><a href="utils.html#TS6UIDGenerator">TS6UIDGenerator</a>
</font></dt></dl>
</dd>
</dl>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="NotAuthenticatedError">class <strong>NotAuthenticatedError</strong></a>(<a href="builtins.html#Exception">builtins.Exception</a>)</font></td></tr>
<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
<td colspan=2><tt>#&nbsp;This&nbsp;is&nbsp;separate&nbsp;from&nbsp;classes.py&nbsp;to&nbsp;prevent&nbsp;import&nbsp;loops.<br>&nbsp;</tt></td></tr>
<tr><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="utils.html#NotAuthenticatedError">NotAuthenticatedError</a></dd>
<dd><a href="builtins.html#Exception">builtins.Exception</a></dd>
<dd><a href="builtins.html#BaseException">builtins.BaseException</a></dd>
<dd><a href="builtins.html#object">builtins.object</a></dd>
</dl>
<hr>
Data descriptors defined here:<br>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<hr>
Methods inherited from <a href="builtins.html#Exception">builtins.Exception</a>:<br>
<dl><dt><a name="NotAuthenticatedError-__init__"><strong>__init__</strong></a>(self, /, *args, **kwargs)</dt><dd><tt>Initialize&nbsp;self.&nbsp;&nbsp;See&nbsp;help(type(self))&nbsp;for&nbsp;accurate&nbsp;signature.</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-__new__"><strong>__new__</strong></a>(*args, **kwargs)<font color="#909090"><font face="helvetica, arial"> from <a href="builtins.html#type">builtins.type</a></font></font></dt><dd><tt>Create&nbsp;and&nbsp;return&nbsp;a&nbsp;new&nbsp;<a href="builtins.html#object">object</a>.&nbsp;&nbsp;See&nbsp;help(type)&nbsp;for&nbsp;accurate&nbsp;signature.</tt></dd></dl>
<hr>
Methods inherited from <a href="builtins.html#BaseException">builtins.BaseException</a>:<br>
<dl><dt><a name="NotAuthenticatedError-__delattr__"><strong>__delattr__</strong></a>(self, name, /)</dt><dd><tt>Implement&nbsp;delattr(self,&nbsp;name).</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-__getattribute__"><strong>__getattribute__</strong></a>(self, name, /)</dt><dd><tt>Return&nbsp;getattr(self,&nbsp;name).</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-__reduce__"><strong>__reduce__</strong></a>(...)</dt></dl>
<dl><dt><a name="NotAuthenticatedError-__repr__"><strong>__repr__</strong></a>(self, /)</dt><dd><tt>Return&nbsp;repr(self).</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-__setattr__"><strong>__setattr__</strong></a>(self, name, value, /)</dt><dd><tt>Implement&nbsp;setattr(self,&nbsp;name,&nbsp;value).</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-__setstate__"><strong>__setstate__</strong></a>(...)</dt></dl>
<dl><dt><a name="NotAuthenticatedError-__str__"><strong>__str__</strong></a>(self, /)</dt><dd><tt>Return&nbsp;str(self).</tt></dd></dl>
<dl><dt><a name="NotAuthenticatedError-with_traceback"><strong>with_traceback</strong></a>(...)</dt><dd><tt><a href="builtins.html#Exception">Exception</a>.<a href="#NotAuthenticatedError-with_traceback">with_traceback</a>(tb)&nbsp;--<br>
set&nbsp;self.<strong>__traceback__</strong>&nbsp;to&nbsp;tb&nbsp;and&nbsp;return&nbsp;self.</tt></dd></dl>
<hr>
Data descriptors inherited from <a href="builtins.html#BaseException">builtins.BaseException</a>:<br>
<dl><dt><strong>__cause__</strong></dt>
<dd><tt>exception&nbsp;cause</tt></dd>
</dl>
<dl><dt><strong>__context__</strong></dt>
<dd><tt>exception&nbsp;context</tt></dd>
</dl>
<dl><dt><strong>__dict__</strong></dt>
</dl>
<dl><dt><strong>__suppress_context__</strong></dt>
</dl>
<dl><dt><strong>__traceback__</strong></dt>
</dl>
<dl><dt><strong>args</strong></dt>
</dl>
</td></tr></table> <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="TS6SIDGenerator">class <strong>TS6SIDGenerator</strong></a>(<a href="builtins.html#object">builtins.object</a>)</font></td></tr>
<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
<td colspan=2><tt>TS6&nbsp;SID&nbsp;Generator.&nbsp;&lt;query&gt;&nbsp;is&nbsp;a&nbsp;3&nbsp;character&nbsp;string&nbsp;with&nbsp;any&nbsp;combination&nbsp;of<br>
uppercase&nbsp;letters,&nbsp;digits,&nbsp;and&nbsp;#'s.&nbsp;it&nbsp;must&nbsp;contain&nbsp;at&nbsp;least&nbsp;one&nbsp;#,<br>
which&nbsp;are&nbsp;used&nbsp;by&nbsp;the&nbsp;generator&nbsp;as&nbsp;a&nbsp;wildcard.&nbsp;On&nbsp;every&nbsp;<a href="#TS6SIDGenerator-next_sid">next_sid</a>()&nbsp;call,<br>
the&nbsp;first&nbsp;available&nbsp;wildcard&nbsp;character&nbsp;(from&nbsp;the&nbsp;right)&nbsp;will&nbsp;be<br>
incremented&nbsp;to&nbsp;generate&nbsp;the&nbsp;next&nbsp;SID.<br>
&nbsp;<br>
When&nbsp;there&nbsp;are&nbsp;no&nbsp;more&nbsp;available&nbsp;SIDs&nbsp;left&nbsp;(SIDs&nbsp;are&nbsp;not&nbsp;reused,&nbsp;only<br>
incremented),&nbsp;RuntimeError&nbsp;is&nbsp;raised.<br>
&nbsp;<br>
Example&nbsp;queries:<br>
&nbsp;&nbsp;&nbsp;&nbsp;"1#A"&nbsp;would&nbsp;give:&nbsp;10A,&nbsp;11A,&nbsp;12A&nbsp;...&nbsp;19A,&nbsp;1AA,&nbsp;1BA&nbsp;...&nbsp;1ZA&nbsp;(36&nbsp;total&nbsp;results)<br>
&nbsp;&nbsp;&nbsp;&nbsp;"#BQ"&nbsp;would&nbsp;give:&nbsp;0BQ,&nbsp;1BQ,&nbsp;2BQ&nbsp;...&nbsp;9BQ&nbsp;(10&nbsp;total&nbsp;results)<br>
&nbsp;&nbsp;&nbsp;&nbsp;"6##"&nbsp;would&nbsp;give:&nbsp;600,&nbsp;601,&nbsp;602,&nbsp;...&nbsp;60Y,&nbsp;60Z,&nbsp;610,&nbsp;611,&nbsp;...&nbsp;6ZZ&nbsp;(1296&nbsp;total&nbsp;results)<br>&nbsp;</tt></td></tr>
<tr><td>&nbsp;</td>
<td width="100%">Methods defined here:<br>
<dl><dt><a name="TS6SIDGenerator-__init__"><strong>__init__</strong></a>(self, irc)</dt></dl>
<dl><dt><a name="TS6SIDGenerator-increment"><strong>increment</strong></a>(self, pos=2)</dt></dl>
<dl><dt><a name="TS6SIDGenerator-next_sid"><strong>next_sid</strong></a>(self)</dt></dl>
<hr>
Data descriptors defined here:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table> <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="TS6UIDGenerator">class <strong>TS6UIDGenerator</strong></a>(<a href="builtins.html#object">builtins.object</a>)</font></td></tr>
<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
<td colspan=2><tt>TS6&nbsp;UID&nbsp;Generator&nbsp;module,&nbsp;adapted&nbsp;from&nbsp;InspIRCd&nbsp;source<br>
https://github.com/inspircd/inspircd/blob/f449c6b296ab/src/server.cpp#L85-L156<br>&nbsp;</tt></td></tr>
<tr><td>&nbsp;</td>
<td width="100%">Methods defined here:<br>
<dl><dt><a name="TS6UIDGenerator-__init__"><strong>__init__</strong></a>(self, sid)</dt></dl>
<dl><dt><a name="TS6UIDGenerator-increment"><strong>increment</strong></a>(self, pos=5)</dt></dl>
<dl><dt><a name="TS6UIDGenerator-next_uid"><strong>next_uid</strong></a>(self)</dt></dl>
<hr>
Data descriptors defined here:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#eeaa77">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
<tr><td bgcolor="#eeaa77"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt><a name="-add_cmd"><strong>add_cmd</strong></a>(func, name=None)</dt></dl>
<dl><dt><a name="-add_hook"><strong>add_hook</strong></a>(func, command)</dt><dd><tt>Add&nbsp;a&nbsp;hook&nbsp;&lt;func&gt;&nbsp;for&nbsp;command&nbsp;&lt;command&gt;.</tt></dd></dl>
<dl><dt><a name="-applyModes"><strong>applyModes</strong></a>(irc, target, changedmodes)</dt><dd><tt>Takes&nbsp;a&nbsp;list&nbsp;of&nbsp;parsed&nbsp;IRC&nbsp;modes,&nbsp;and&nbsp;applies&nbsp;them&nbsp;on&nbsp;the&nbsp;given&nbsp;target.<br>
&nbsp;<br>
The&nbsp;target&nbsp;can&nbsp;be&nbsp;either&nbsp;a&nbsp;channel&nbsp;or&nbsp;a&nbsp;user;&nbsp;this&nbsp;is&nbsp;handled&nbsp;automatically.</tt></dd></dl>
<dl><dt><a name="-checkAuthenticated"><strong>checkAuthenticated</strong></a>(irc, uid, allowAuthed=True, allowOper=True)</dt><dd><tt>Checks&nbsp;whetherthe&nbsp;given&nbsp;user&nbsp;has&nbsp;operator&nbsp;status&nbsp;on&nbsp;PyLink,&nbsp;raising<br>
<a href="#NotAuthenticatedError">NotAuthenticatedError</a>&nbsp;and&nbsp;logging&nbsp;the&nbsp;access&nbsp;denial&nbsp;if&nbsp;not.</tt></dd></dl>
<dl><dt><a name="-clientToServer"><strong>clientToServer</strong></a>(irc, numeric)</dt><dd><tt>Finds&nbsp;the&nbsp;SID&nbsp;of&nbsp;the&nbsp;server&nbsp;a&nbsp;user&nbsp;is&nbsp;on.</tt></dd></dl>
<dl><dt><a name="-getHostmask"><strong>getHostmask</strong></a>(irc, user)</dt><dd><tt>Gets&nbsp;the&nbsp;hostmask&nbsp;of&nbsp;the&nbsp;given&nbsp;user,&nbsp;if&nbsp;present.</tt></dd></dl>
<dl><dt><a name="-getProtoModule"><strong>getProtoModule</strong></a>(protoname)</dt><dd><tt>Imports&nbsp;and&nbsp;returns&nbsp;the&nbsp;protocol&nbsp;module&nbsp;requested.</tt></dd></dl>
<dl><dt><a name="-isChannel"><strong>isChannel</strong></a>(s)</dt><dd><tt>Checks&nbsp;whether&nbsp;the&nbsp;string&nbsp;given&nbsp;is&nbsp;a&nbsp;valid&nbsp;channel&nbsp;name.</tt></dd></dl>
<dl><dt><a name="-isHostmask"><strong>isHostmask</strong></a>(text)</dt><dd><tt>Returns&nbsp;whether&nbsp;the&nbsp;given&nbsp;text&nbsp;is&nbsp;a&nbsp;valid&nbsp;hostmask.</tt></dd></dl>
<dl><dt><a name="-isInternalClient"><strong>isInternalClient</strong></a>(irc, numeric)</dt><dd><tt>Checks&nbsp;whether&nbsp;the&nbsp;given&nbsp;numeric&nbsp;is&nbsp;a&nbsp;PyLink&nbsp;Client,<br>
returning&nbsp;the&nbsp;SID&nbsp;of&nbsp;the&nbsp;server&nbsp;it's&nbsp;on&nbsp;if&nbsp;so.</tt></dd></dl>
<dl><dt><a name="-isInternalServer"><strong>isInternalServer</strong></a>(irc, sid)</dt><dd><tt>Returns&nbsp;whether&nbsp;the&nbsp;given&nbsp;SID&nbsp;is&nbsp;an&nbsp;internal&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="-isManipulatableClient"><strong>isManipulatableClient</strong></a>(irc, uid)</dt><dd><tt>Returns&nbsp;whether&nbsp;the&nbsp;given&nbsp;user&nbsp;is&nbsp;marked&nbsp;as&nbsp;an&nbsp;internal,&nbsp;manipulatable<br>
client.&nbsp;Usually,&nbsp;automatically&nbsp;spawned&nbsp;services&nbsp;clients&nbsp;should&nbsp;have&nbsp;this<br>
set&nbsp;True&nbsp;to&nbsp;prevent&nbsp;interactions&nbsp;with&nbsp;opers&nbsp;(like&nbsp;mode&nbsp;changes)&nbsp;from<br>
causing&nbsp;desyncs.</tt></dd></dl>
<dl><dt><a name="-isNick"><strong>isNick</strong></a>(s, nicklen=None)</dt><dd><tt>Checks&nbsp;whether&nbsp;the&nbsp;string&nbsp;given&nbsp;is&nbsp;a&nbsp;valid&nbsp;nick.</tt></dd></dl>
<dl><dt><a name="-isOper"><strong>isOper</strong></a>(irc, uid, allowAuthed=True, allowOper=True)</dt><dd><tt>Returns&nbsp;whether&nbsp;the&nbsp;given&nbsp;user&nbsp;has&nbsp;operator&nbsp;status&nbsp;on&nbsp;PyLink.&nbsp;This&nbsp;can&nbsp;be&nbsp;achieved<br>
by&nbsp;either&nbsp;identifying&nbsp;to&nbsp;PyLink&nbsp;as&nbsp;admin&nbsp;(if&nbsp;allowAuthed&nbsp;is&nbsp;True),<br>
or&nbsp;having&nbsp;user&nbsp;mode&nbsp;+o&nbsp;set&nbsp;(if&nbsp;allowOper&nbsp;is&nbsp;True).&nbsp;At&nbsp;least&nbsp;one&nbsp;of<br>
allowAuthed&nbsp;or&nbsp;allowOper&nbsp;must&nbsp;be&nbsp;True&nbsp;for&nbsp;this&nbsp;to&nbsp;give&nbsp;any&nbsp;meaningful<br>
results.</tt></dd></dl>
<dl><dt><a name="-isServerName"><strong>isServerName</strong></a>(s)</dt><dd><tt>Checks&nbsp;whether&nbsp;the&nbsp;string&nbsp;given&nbsp;is&nbsp;a&nbsp;server&nbsp;name.</tt></dd></dl>
<dl><dt><a name="-joinModes"><strong>joinModes</strong></a>(modes)</dt><dd><tt>Takes&nbsp;a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples&nbsp;in&nbsp;<a href="#-parseModes">parseModes</a>()&nbsp;format,&nbsp;and<br>
joins&nbsp;them&nbsp;into&nbsp;a&nbsp;string.<br>
&nbsp;<br>
See&nbsp;testJoinModes&nbsp;in&nbsp;tests/test_utils.py&nbsp;for&nbsp;some&nbsp;examples.</tt></dd></dl>
<dl><dt><a name="-loadModuleFromFolder"><strong>loadModuleFromFolder</strong></a>(name, folder)</dt><dd><tt>Attempts&nbsp;an&nbsp;import&nbsp;of&nbsp;name&nbsp;from&nbsp;a&nbsp;specific&nbsp;folder,&nbsp;returning&nbsp;the&nbsp;resulting&nbsp;module.</tt></dd></dl>
<dl><dt><a name="-nickToUid"><strong>nickToUid</strong></a>(irc, nick)</dt><dd><tt>Returns&nbsp;the&nbsp;UID&nbsp;of&nbsp;a&nbsp;user&nbsp;named&nbsp;nick,&nbsp;if&nbsp;present.</tt></dd></dl>
<dl><dt><a name="-parseModes"><strong>parseModes</strong></a>(irc, target, args)</dt><dd><tt>Parses&nbsp;a&nbsp;modestring&nbsp;list&nbsp;into&nbsp;a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;argument)&nbsp;tuples.<br>
['+mitl-o',&nbsp;'3',&nbsp;'person']&nbsp;=&gt;&nbsp;[('+m',&nbsp;None),&nbsp;('+i',&nbsp;None),&nbsp;('+t',&nbsp;None),&nbsp;('+l',&nbsp;'3'),&nbsp;('-o',&nbsp;'person')]</tt></dd></dl>
<dl><dt><a name="-reverseModes"><strong>reverseModes</strong></a>(irc, target, modes, oldobj=None)</dt><dd><tt>Reverses/Inverts&nbsp;the&nbsp;mode&nbsp;string&nbsp;or&nbsp;mode&nbsp;list&nbsp;given.<br>
&nbsp;<br>
Optionally,&nbsp;an&nbsp;oldobj&nbsp;argument&nbsp;can&nbsp;be&nbsp;given&nbsp;to&nbsp;look&nbsp;at&nbsp;an&nbsp;earlier&nbsp;state&nbsp;of<br>
a&nbsp;channel/user&nbsp;<a href="builtins.html#object">object</a>,&nbsp;e.g.&nbsp;for&nbsp;checking&nbsp;the&nbsp;op&nbsp;status&nbsp;of&nbsp;a&nbsp;mode&nbsp;setter<br>
before&nbsp;their&nbsp;modes&nbsp;are&nbsp;processed&nbsp;and&nbsp;added&nbsp;to&nbsp;the&nbsp;channel&nbsp;state.<br>
&nbsp;<br>
This&nbsp;function&nbsp;allows&nbsp;both&nbsp;mode&nbsp;strings&nbsp;or&nbsp;mode&nbsp;lists.&nbsp;Example&nbsp;uses:<br>
&nbsp;&nbsp;&nbsp;&nbsp;"+mi-lk&nbsp;test&nbsp;=&gt;&nbsp;"-mi+lk&nbsp;test"<br>
&nbsp;&nbsp;&nbsp;&nbsp;"mi-k&nbsp;test&nbsp;=&gt;&nbsp;"-mi+k&nbsp;test"<br>
&nbsp;&nbsp;&nbsp;&nbsp;[('+m',&nbsp;None),&nbsp;('+r',&nbsp;None),&nbsp;('+l',&nbsp;'3'),&nbsp;('-o',&nbsp;'person')<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;{('-m',&nbsp;None),&nbsp;('-r',&nbsp;None),&nbsp;('-l',&nbsp;None),&nbsp;('+o',&nbsp;'person')})<br>
&nbsp;&nbsp;&nbsp;&nbsp;{('s',&nbsp;None),&nbsp;('+o',&nbsp;'whoever')&nbsp;=&gt;&nbsp;{('-s',&nbsp;None),&nbsp;('-o',&nbsp;'whoever')})</tt></dd></dl>
<dl><dt><a name="-toLower"><strong>toLower</strong></a>(irc, text)</dt><dd><tt>Returns&nbsp;a&nbsp;lowercase&nbsp;representation&nbsp;of&nbsp;text&nbsp;based&nbsp;on&nbsp;the&nbsp;IRC&nbsp;<a href="builtins.html#object">object</a>'s<br>
casemapping&nbsp;(rfc1459&nbsp;or&nbsp;ascii).</tt></dd></dl>
</td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#55aa55">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><strong>hostmaskRe</strong> = re.compile('^\\S+!\\S+@\\S+$')<br>
<strong>log</strong> = &lt;logging.RootLogger object&gt;</td></tr></table>
</body></html>

View File

@ -20,7 +20,7 @@ def spawnclient(irc, source, args):
try: try:
nick, ident, host = args[:3] nick, ident, host = args[:3]
except ValueError: except ValueError:
irc.msg(source, "Error: Not enough arguments. Needs 3: nick, user, host.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 3: nick, user, host.")
return return
irc.proto.spawnClient(nick, ident, host, manipulatable=True) irc.proto.spawnClient(nick, ident, host, manipulatable=True)
@ -33,15 +33,15 @@ def quit(irc, source, args):
try: try:
nick = args[0] nick = args[0]
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).") irc.msg(irc.called_by, "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):
irc.msg(source, "Error: Cannot quit the main PyLink PseudoClient!") irc.msg(irc.called_by, "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'
if not utils.isManipulatableClient(irc, u): if not utils.isManipulatableClient(irc, u):
irc.msg(source, "Error: Cannot force quit a protected PyLink services client.") irc.msg(irc.called_by, "Error: Cannot force quit a protected PyLink services client.")
return return
irc.proto.quitClient(u, quitmsg) irc.proto.quitClient(u, quitmsg)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}]) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
@ -57,15 +57,15 @@ def joinclient(irc, source, args):
if not clist: if not clist:
raise IndexError raise IndexError
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return return
u = utils.nickToUid(irc, nick) u = utils.nickToUid(irc, nick)
if not utils.isManipulatableClient(irc, u): if not utils.isManipulatableClient(irc, u):
irc.msg(source, "Error: Cannot force join a protected PyLink services client.") irc.msg(irc.called_by, "Error: Cannot force join a protected PyLink services client.")
return return
for channel in clist: for channel in clist:
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, "Error: Invalid channel name %r." % channel) irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
return return
irc.proto.joinClient(u, channel) irc.proto.joinClient(u, channel)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u], irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
@ -83,16 +83,16 @@ def nick(irc, source, args):
nick = args[0] nick = args[0]
newnick = args[1] newnick = args[1]
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 2: nick, newnick.") irc.msg(irc.called_by, "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):
newnick = u newnick = u
elif not utils.isNick(newnick): elif not utils.isNick(newnick):
irc.msg(source, 'Error: Invalid nickname %r.' % newnick) irc.msg(irc.called_by, 'Error: Invalid nickname %r.' % newnick)
return return
elif not utils.isManipulatableClient(irc, u): elif not utils.isManipulatableClient(irc, u):
irc.msg(source, "Error: Cannot force nick changes for a protected PyLink services client.") irc.msg(irc.called_by, "Error: Cannot force nick changes for a protected PyLink services client.")
return return
irc.proto.nickClient(u, newnick) irc.proto.nickClient(u, newnick)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}]) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
@ -108,15 +108,15 @@ def part(irc, source, args):
clist = args[1].split(',') clist = args[1].split(',')
reason = ' '.join(args[2:]) reason = ' '.join(args[2:])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return return
u = utils.nickToUid(irc, nick) u = utils.nickToUid(irc, nick)
if not utils.isManipulatableClient(irc, u): if not utils.isManipulatableClient(irc, u):
irc.msg(source, "Error: Cannot force part a protected PyLink services client.") irc.msg(irc.called_by, "Error: Cannot force part a protected PyLink services client.")
return return
for channel in clist: for channel in clist:
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, "Error: Invalid channel name %r." % channel) irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
return return
irc.proto.partClient(u, channel, reason) irc.proto.partClient(u, channel, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}]) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
@ -133,12 +133,12 @@ def kick(irc, source, args):
target = args[2] target = args[2]
reason = ' '.join(args[3:]) reason = ' '.join(args[3:])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).") irc.msg(irc.called_by, "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)
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, "Error: Invalid channel name %r." % channel) irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel)
return return
if utils.isInternalServer(irc, u): if utils.isInternalServer(irc, u):
irc.proto.kickServer(u, channel, targetu, reason) irc.proto.kickServer(u, channel, targetu, reason)
@ -155,20 +155,20 @@ def mode(irc, source, args):
try: try:
modesource, target, modes = args[0], args[1], args[2:] modesource, target, modes = args[0], args[1], args[2:]
except IndexError: except IndexError:
irc.msg(source, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.') irc.msg(irc.called_by, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
return return
target = utils.nickToUid(irc, target) or target target = utils.nickToUid(irc, target) or target
extclient = target in irc.users and not utils.isInternalClient(irc, target) extclient = target in irc.users and not utils.isInternalClient(irc, target)
parsedmodes = utils.parseModes(irc, target, modes) parsedmodes = utils.parseModes(irc, target, modes)
ischannel = target in irc.channels ischannel = target in irc.channels
if not (target in irc.users or ischannel): if not (target in irc.users or ischannel):
irc.msg(source, "Error: Invalid channel or nick %r." % target) irc.msg(irc.called_by, "Error: Invalid channel or nick %r." % target)
return return
elif not parsedmodes: elif not parsedmodes:
irc.msg(source, "Error: No valid modes were given.") irc.msg(irc.called_by, "Error: No valid modes were given.")
return return
elif not (ischannel or utils.isManipulatableClient(irc, target)): elif not (ischannel or utils.isManipulatableClient(irc, target)):
irc.msg(source, "Error: Can only set modes on channels or non-protected PyLink clients.") irc.msg(irc.called_by, "Error: Can only set modes on channels or non-protected PyLink clients.")
return return
if utils.isInternalServer(irc, modesource): if utils.isInternalServer(irc, modesource):
# Setting modes from a server. # Setting modes from a server.
@ -189,21 +189,21 @@ def msg(irc, source, args):
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:
irc.msg(source, 'Error: Not enough arguments. Needs 3: source nick, target, text.') irc.msg(irc.called_by, '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:
irc.msg(source, 'Error: Unknown user %r.' % msgsource) irc.msg(irc.called_by, '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:
irc.msg(source, 'Error: Unknown user %r.' % target) irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
return return
else: else:
real_target = target real_target = target
if not text: if not text:
irc.msg(source, 'Error: No text given.') irc.msg(irc.called_by, 'Error: No text given.')
return return
irc.proto.messageClient(sourceuid, real_target, text) irc.proto.messageClient(sourceuid, real_target, text)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}]) irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])

View File

@ -2,12 +2,15 @@
import sys import sys
import os import os
from time import ctime from time import ctime
import itertools
import gc
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 import conf
from log import log from log import log
import world import world
import classes
@utils.add_cmd @utils.add_cmd
def status(irc, source, args): def status(irc, source, args):
@ -16,24 +19,27 @@ def status(irc, source, args):
Returns your current PyLink login status.""" Returns your current PyLink login status."""
identified = irc.users[source].identified identified = irc.users[source].identified
if identified: if identified:
irc.msg(source, 'You are identified as \x02%s\x02.' % identified) irc.msg(irc.called_by, 'You are identified as \x02%s\x02.' % identified)
else: else:
irc.msg(source, 'You are not identified as anyone.') irc.msg(irc.called_by, 'You are not identified as anyone.')
irc.msg(source, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
@utils.add_cmd @utils.add_cmd
def identify(irc, source, args): def identify(irc, source, args):
"""<username> <password> """<username> <password>
Logs in to PyLink using the configured administrator account.""" Logs in to PyLink using the configured administrator account."""
if utils.isChannel(irc.called_by):
irc.msg(irc.called_by, 'Error: This command must be sent in private. '
'(Would you really type a password inside a public channel?)')
try: try:
username, password = args[0], args[1] username, password = args[0], args[1]
except IndexError: except IndexError:
irc.msg(source, 'Error: Not enough arguments.') irc.msg(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() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']:
realuser = conf['login']['user'] realuser = irc.conf['login']['user']
irc.users[source].identified = realuser irc.users[source].identified = realuser
irc.msg(source, 'Successfully logged in as %s.' % realuser) irc.msg(source, 'Successfully logged in as %s.' % realuser)
log.info("(%s) Successful login to %r by %s.", log.info("(%s) Successful login to %r by %s.",
@ -48,14 +54,14 @@ 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(world.bot_commands.keys()) cmds = list(world.commands.keys())
cmds.sort() cmds.sort()
for idx, cmd in enumerate(cmds): for idx, cmd in enumerate(cmds):
nfuncs = len(world.bot_commands[cmd]) nfuncs = len(world.commands[cmd])
if nfuncs > 1: if nfuncs > 1:
cmds[idx] = '%s(x%s)' % (cmd, nfuncs) cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
irc.msg(source, 'Available commands include: %s' % ', '.join(cmds)) irc.msg(irc.called_by, 'Available commands include: %s' % ', '.join(cmds))
irc.msg(source, 'To see help on a specific command, type \x02help <command>\x02.') irc.msg(irc.called_by, 'To see help on a specific command, type \x02help <command>\x02.')
utils.add_cmd(listcommands, 'list') utils.add_cmd(listcommands, 'list')
@utils.add_cmd @utils.add_cmd
@ -68,13 +74,13 @@ 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
if command not in world.bot_commands: if command not in world.commands:
irc.msg(source, 'Error: Unknown command %r.' % command) irc.msg(source, 'Error: Unknown command %r.' % command)
return return
else: else:
funcs = world.bot_commands[command] funcs = world.commands[command]
if len(funcs) > 1: if len(funcs) > 1:
irc.msg(source, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s' irc.msg(irc.called_by, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
% (len(funcs), command, ', '.join([func.__module__ for func in funcs]))) % (len(funcs), command, ', '.join([func.__module__ for func in funcs])))
for func in funcs: for func in funcs:
doc = func.__doc__ doc = func.__doc__
@ -85,7 +91,7 @@ def help(irc, source, args):
# arguments the command takes. # arguments the command takes.
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod) lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
for line in lines: for line in lines:
irc.msg(source, line.strip()) irc.msg(irc.called_by, line.strip())
else: else:
irc.msg(source, "Error: Command %r (from plugin %r) " irc.msg(source, "Error: Command %r (from plugin %r) "
"doesn't offer any help." % (command, mod)) "doesn't offer any help." % (command, mod))
@ -99,14 +105,14 @@ def showuser(irc, source, args):
try: try:
target = args[0] target = args[0]
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1: nick.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: nick.")
return return
u = utils.nickToUid(irc, target) or target u = utils.nickToUid(irc, target) or target
# Only show private info if the person is calling 'showuser' on themselves, # Only show private info if the person is calling 'showuser' on themselves,
# or is an oper. # or is an oper.
verbose = utils.isOper(irc, source) or u == source verbose = utils.isOper(irc, source) or u == source
if u not in irc.users: if u not in irc.users:
irc.msg(source, 'Error: Unknown user %r.' % target) irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
return return
f = lambda s: irc.msg(source, s) f = lambda s: irc.msg(source, s)
@ -134,10 +140,10 @@ def showchan(irc, source, args):
try: try:
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
return return
if channel not in irc.channels: if channel not in irc.channels:
irc.msg(source, 'Error: Unknown channel %r.' % channel) irc.msg(irc.called_by, 'Error: Unknown channel %r.' % channel)
return return
f = lambda s: irc.msg(source, s) f = lambda s: irc.msg(source, s)
@ -191,5 +197,154 @@ def version(irc, source, args):
"""takes no arguments. """takes no arguments.
Returns the version of the currently running PyLink instance.""" Returns the version of the currently running PyLink instance."""
irc.msg(source, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version) irc.msg(irc.called_by, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version)
irc.msg(source, "The source of this program is available at \x02%s\x02." % world.source) irc.msg(irc.called_by, "The source of this program is available at \x02%s\x02." % world.source)
@utils.add_cmd
def echo(irc, source, args):
"""<text>
Echoes the text given."""
irc.msg(irc.called_by, ' '.join(args))
def load(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if name in world.plugins:
irc.msg(irc.called_by, "Error: %r is already loaded." % name)
return
try:
world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder)
except ImportError as e:
if str(e) == ('No module named %r' % name):
log.exception('Failed to load plugin %r: The plugin could not be found.', name)
else:
log.exception('Failed to load plugin %r: ImportError.', name)
raise
else:
if hasattr(pl, 'main'):
log.debug('Calling main() function of plugin %r', pl)
pl.main(irc)
irc.msg(irc.called_by, "Loaded plugin %r." % name)
utils.add_cmd(load)
def unload(irc, source, args):
"""<plugin name>.
Unloads a currently loaded plugin."""
utils.checkAuthenticated(irc, source, allowOper=False)
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if name == 'commands':
irc.msg(irc.called_by, "Error: Cannot unload the commands plugin!")
return
elif name in world.plugins:
pl = world.plugins[name]
log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl))
# Remove any command functions set by the plugin.
for cmdname, cmdfuncs in world.commands.copy().items():
log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs)
for cmdfunc in cmdfuncs:
log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__)
if cmdfunc.__module__ == name:
log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname)
world.commands[cmdname].remove(cmdfunc)
# If the cmdfunc list is empty, remove it.
if not cmdfuncs:
log.debug("Removing world.commands[%s] (it's empty now)", cmdname)
del world.commands[cmdname]
# Remove any command hooks set by the plugin.
for hookname, hookfuncs in world.hooks.copy().items():
for hookfunc in hookfuncs:
if hookfunc.__module__ == name:
world.hooks[hookname].remove(hookfunc)
# If the hookfuncs list is empty, remove it.
if not hookfuncs:
del world.hooks[hookname]
# Remove whois handlers too.
for f in world.whois_handlers:
if f.__module__ == name:
world.whois_handlers.remove(f)
# Call the die() function in the plugin, if present.
if hasattr(pl, 'die'):
try:
pl.die(irc)
except: # But don't allow it to crash the server.
log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl)
# Delete it from memory (hopefully).
del world.plugins[name]
if name in sys.modules:
del sys.modules[name]
if name in globals():
del globals()[name]
# Garbage collect.
gc.collect()
irc.msg(irc.called_by, "Unloaded plugin %r." % name)
return True # We succeeded, make it clear (this status is used by reload() below)
else:
irc.msg(irc.called_by, "Unknown plugin %r." % name)
utils.add_cmd(unload)
@utils.add_cmd
def reload(irc, source, args):
"""<plugin name>.
Loads a plugin from the plugin folder."""
try:
name = args[0]
except IndexError:
irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.")
return
if unload(irc, source, args):
load(irc, source, args)
@utils.add_cmd
def rehash(irc, source, args):
"""takes no arguments.
Reloads the configuration file for PyLink, (dis)connecting added/removed networks.
Plugins must be manually reloaded."""
utils.checkAuthenticated(irc, source, allowOper=False)
old_conf = conf.conf.copy()
fname = conf.fname
try:
new_conf = conf.validateConf(conf.loadConf(fname))
except Exception as e: # Something went wrong, abort.
log.exception("Error REHASH'ing config: ")
irc.msg(irc.called_by, "Error loading configuration file: %s: %s", type(e).__name__, e)
return
conf.conf = new_conf
for network, ircobj in world.networkobjects.copy().items():
# Server was removed from the config file, disconnect them.
log.debug('(%s) rehash: checking if %r is in new conf still.', irc.name, network)
if network not in new_conf['servers']:
# Disable autoconnect first.
log.debug('(%s) rehash: removing connection to %r (removed from config).', irc.name, network)
ircobj.serverdata['autoconnect'] = -1
ircobj.aborted.set()
del world.networkobjects[network]
else:
ircobj.conf = new_conf
ircobj.serverdata = new_conf['servers'][network]
for network, sdata in new_conf['servers'].items():
# New server was added. Connect them if not already connected.
if network not in world.networkobjects:
proto = utils.getProtoModule(sdata['protocol'])
world.networkobjects[network] = classes.Irc(network, proto, new_conf)
irc.msg(irc.called_by, "Done.")

View File

@ -6,6 +6,9 @@ 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
# Easier access to world through eval/exec.
import world
def _exec(irc, source, args): def _exec(irc, source, args):
"""<code> """<code>
@ -14,7 +17,7 @@ def _exec(irc, source, args):
utils.checkAuthenticated(irc, source, allowOper=False) utils.checkAuthenticated(irc, source, allowOper=False)
args = ' '.join(args) args = ' '.join(args)
if not args.strip(): if not args.strip():
irc.msg(source, 'No code entered!') irc.msg(irc.called_by, 'No code entered!')
return return
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source)) log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
exec(args, globals(), locals()) exec(args, globals(), locals())
@ -28,8 +31,8 @@ def _eval(irc, source, args):
utils.checkAuthenticated(irc, source, allowOper=False) utils.checkAuthenticated(irc, source, allowOper=False)
args = ' '.join(args) args = ' '.join(args)
if not args.strip(): if not args.strip():
irc.msg(source, 'No code entered!') irc.msg(irc.called_by, 'No code entered!')
return return
log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source)) log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source))
irc.msg(source, eval(args)) irc.msg(irc.called_by, eval(args))
utils.add_cmd(_eval, 'eval') utils.add_cmd(_eval, 'eval')

33
plugins/fantasy.py Executable file
View File

@ -0,0 +1,33 @@
# fantasy.py: Adds FANTASY command support, to allow calling commands in channels
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils
from log import log
def handle_fantasy(irc, source, command, args):
"""Fantasy command handler."""
try:
prefix = irc.botdata["prefix"]
except KeyError:
log.warning("(%s) Fantasy prefix was not set in configuration - "
"fantasy commands will not work!", self.name)
return
channel = args['target']
text = args['text']
# Conditions:
# 1) Message target is a channel,
# 2) Message starts with our fantasy prefix,
# 3) The main PyLink client is in the channel.
# 4) The sender is NOT a PyLink client (prevents message loops).
if utils.isChannel(channel) and text.startswith(prefix) and \
irc.pseudoclient.uid in irc.channels[channel].users and not \
utils.isInternalClient(irc, source):
# Cut off the length of the prefix from the text.
text = text[len(prefix):]
# Set the last called in variable to the channel, so replies (from
# supporting plugins) get forwarded to it.
irc.called_by = channel
irc.callCommand(source, text)
utils.add_hook(handle_fantasy, 'PRIVMSG')

View File

@ -22,12 +22,12 @@ def disconnect(irc, source, args):
netname = args[0] netname = args[0]
network = world.networkobjects[netname] network = world.networkobjects[netname]
except IndexError: # No argument given. except IndexError: # No argument given.
irc.msg(source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).') irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
return return
except KeyError: # Unknown network. except KeyError: # Unknown network.
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname) irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
return return
irc.msg(source, "Done.") irc.msg(irc.called_by, "Done.")
# Abort the connection! Simple as that. # Abort the connection! Simple as that.
network.aborted.set() network.aborted.set()
@ -41,18 +41,18 @@ def connect(irc, source, args):
netname = args[0] netname = args[0]
network = world.networkobjects[netname] network = world.networkobjects[netname]
except IndexError: # No argument given. except IndexError: # No argument given.
irc.msg(source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).') irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
return return
except KeyError: # Unknown network. except KeyError: # Unknown network.
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname) irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
return return
if network.connection_thread.is_alive(): if network.connection_thread.is_alive():
irc.msg(source, 'Error: Network "%s" seems to be already connected.' % netname) irc.msg(irc.called_by, 'Error: Network "%s" seems to be already connected.' % netname)
else: # Reconnect the network! else: # Reconnect the network!
network.initVars() network.initVars()
network.connection_thread = threading.Thread(target=network.connect) network.connection_thread = threading.Thread(target=network.connect)
network.connection_thread.start() network.connection_thread.start()
irc.msg(source, "Done.") irc.msg(irc.called_by, "Done.")
@utils.add_cmd @utils.add_cmd
def autoconnect(irc, source, args): def autoconnect(irc, source, args):
@ -66,13 +66,13 @@ def autoconnect(irc, source, args):
seconds = float(args[1]) seconds = float(args[1])
network = world.networkobjects[netname] network = world.networkobjects[netname]
except IndexError: # Arguments not given. except IndexError: # Arguments not given.
irc.msg(source, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).') irc.msg(irc.called_by, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).')
return return
except KeyError: # Unknown network. except KeyError: # Unknown network.
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname) irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname)
return return
except ValueError: except ValueError:
irc.msg(source, 'Error: Invalid argument "%s" for <seconds>.' % seconds) irc.msg(irc.called_by, 'Error: Invalid argument "%s" for <seconds>.' % seconds)
return return
network.serverdata['autoconnect'] = seconds network.serverdata['autoconnect'] = seconds
irc.msg(source, "Done.") irc.msg(irc.called_by, "Done.")

View File

@ -26,6 +26,7 @@ spawnlocks = defaultdict(threading.RLock)
spawnlocks_servers = defaultdict(threading.RLock) spawnlocks_servers = defaultdict(threading.RLock)
savecache = ExpiringDict(max_len=5, max_age_seconds=10) savecache = ExpiringDict(max_len=5, max_age_seconds=10)
killcache = ExpiringDict(max_len=5, max_age_seconds=10) killcache = ExpiringDict(max_len=5, max_age_seconds=10)
relay_started = True
### INTERNAL FUNCTIONS ### INTERNAL FUNCTIONS
@ -38,8 +39,10 @@ def initializeAll(irc):
network, channel = link network, channel = link
initializeChannel(irc, channel) initializeChannel(irc, channel)
def main(): def main(irc=None):
"""Main function, called during plugin loading at start.""" """Main function, called during plugin loading at start."""
global relay_started
relay_started = True
loadDB() loadDB()
world.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,))
@ -48,6 +51,23 @@ def main():
thread = threading.Thread(target=scheduler.run) thread = threading.Thread(target=scheduler.run)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
if irc is not None:
for ircobj in world.networkobjects.values():
initializeAll(ircobj)
def die(sourceirc):
"""Deinitialize PyLink Relay by quitting all relay clients."""
global relay_started
relay_started = False
for irc in world.networkobjects.values():
for user in irc.users.copy():
if isRelayClient(irc, user):
irc.proto.quitClient(user, "Relay plugin unloaded.")
for server, sobj in irc.servers.copy().items():
if hasattr(sobj, 'remote'):
irc.proto.squitServer(irc.sid, server, text="Relay plugin unloaded.")
relayservers.clear()
relayusers.clear()
def normalizeNick(irc, netname, nick, separator=None, uid=''): def normalizeNick(irc, netname, nick, separator=None, uid=''):
"""Creates a normalized nickname for the given nick suitable for """Creates a normalized nickname for the given nick suitable for
@ -115,7 +135,7 @@ def exportDB(reschedule=False):
"""Exports the relay database, optionally creating a loop to do this """Exports the relay database, optionally creating a loop to do this
automatically.""" automatically."""
scheduler = world.schedulers.get('relaydb') scheduler = world.schedulers.get('relaydb')
if reschedule and scheduler: if reschedule and scheduler and relay_started:
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)
with open(dbname, 'wb') as f: with open(dbname, 'wb') as f:
@ -144,6 +164,12 @@ def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
def getRemoteSid(irc, remoteirc): def getRemoteSid(irc, remoteirc):
"""Gets the remote server SID representing remoteirc on irc, spawning """Gets the remote server SID representing remoteirc on irc, spawning
it if it doesn't exist.""" it if it doesn't exist."""
try:
spawnservers = irc.conf['relay']['spawn_servers']
except KeyError:
spawnservers = True
if not spawnservers:
return irc.sid
with spawnlocks_servers[irc.name]: with spawnlocks_servers[irc.name]:
try: try:
sid = relayservers[irc.name][remoteirc.name] sid = relayservers[irc.name][remoteirc.name]
@ -156,7 +182,9 @@ def getRemoteSid(irc, remoteirc):
except ValueError: # Network not initialized yet. except ValueError: # Network not initialized yet.
log.exception('(%s) Failed to spawn server for %r:', log.exception('(%s) Failed to spawn server for %r:',
irc.name, remoteirc.name) irc.name, remoteirc.name)
return irc.aborted.set()
else:
irc.servers[sid].remote = remoteirc.name
relayservers[irc.name][remoteirc.name] = sid relayservers[irc.name][remoteirc.name] = sid
return sid return sid
@ -223,16 +251,11 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
else: else:
realhost = None realhost = None
ip = '0.0.0.0' ip = '0.0.0.0'
try:
u = remoteirc.proto.spawnClient(nick, ident=ident, u = remoteirc.proto.spawnClient(nick, ident=ident,
host=host, realname=realname, host=host, realname=realname,
modes=modes, ts=userobj.ts, modes=modes, ts=userobj.ts,
opertype=opertype, server=rsid, opertype=opertype, server=rsid,
ip=ip, realhost=realhost).uid ip=ip, realhost=realhost).uid
except ValueError:
log.exception('(%s) Failed to spawn relay user %s on %s.', irc.name,
nick, remoteirc.name)
return
remoteirc.users[u].remote = (irc.name, user) remoteirc.users[u].remote = (irc.name, user)
remoteirc.users[u].opertype = opertype remoteirc.users[u].opertype = opertype
away = userobj.away away = userobj.away
@ -660,11 +683,11 @@ def handle_part(irc, numeric, command, args):
del relayusers[(irc.name, numeric)][remoteirc.name] del relayusers[(irc.name, numeric)][remoteirc.name]
utils.add_hook(handle_part, 'PART') utils.add_hook(handle_part, 'PART')
def handle_privmsg(irc, numeric, command, args): def handle_messages(irc, numeric, command, args):
notice = (command == 'NOTICE') notice = (command in ('NOTICE', 'PYLINK_SELF_NOTICE'))
target = args['target'] target = args['target']
text = args['text'] text = args['text']
if target == irc.pseudoclient.uid: if utils.isInternalClient(irc, numeric) and utils.isInternalClient(irc, target):
return return
relay = getRelay((irc.name, target)) relay = getRelay((irc.name, target))
remoteusers = relayusers[(irc.name, numeric)] remoteusers = relayusers[(irc.name, numeric)]
@ -685,11 +708,11 @@ def handle_privmsg(irc, numeric, command, args):
'messages over the relay.' % target, notice=True) 'messages over the relay.' % target, notice=True)
return return
if utils.isChannel(target): if utils.isChannel(target):
for netname, user in relayusers[(irc.name, numeric)].items(): for name, remoteirc in world.networkobjects.items():
remoteirc = world.networkobjects[netname]
real_target = getRemoteChan(irc, remoteirc, target) real_target = getRemoteChan(irc, remoteirc, target)
if not real_target: if irc.name == name or not remoteirc.connected.is_set() or not real_target:
continue continue
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
real_target = prefix + real_target real_target = prefix + real_target
if notice: if notice:
remoteirc.proto.noticeClient(user, real_target, text) remoteirc.proto.noticeClient(user, real_target, text)
@ -715,8 +738,8 @@ def handle_privmsg(irc, numeric, command, args):
remoteirc.proto.noticeClient(user, real_target, text) remoteirc.proto.noticeClient(user, real_target, text)
else: else:
remoteirc.proto.messageClient(user, real_target, text) remoteirc.proto.messageClient(user, real_target, text)
utils.add_hook(handle_privmsg, 'PRIVMSG') for cmd in ('PRIVMSG', 'NOTICE', 'PYLINK_SELF_NOTICE', 'PYLINK_SELF_PRIVMSG'):
utils.add_hook(handle_privmsg, 'NOTICE') utils.add_hook(handle_messages, cmd)
def handle_kick(irc, source, command, args): def handle_kick(irc, source, command, args):
channel = args['channel'] channel = args['channel']
@ -977,7 +1000,11 @@ def handle_disconnect(irc, numeric, command, args):
if irc.name in v: if irc.name in v:
del relayusers[k][irc.name] del relayusers[k][irc.name]
if k[0] == irc.name: if k[0] == irc.name:
try:
handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'})
del relayusers[k] del relayusers[k]
except KeyError:
pass
for name, ircobj in world.networkobjects.items(): for name, ircobj in world.networkobjects.items():
if name != irc.name: if name != irc.name:
rsid = getRemoteSid(ircobj, irc) rsid = getRemoteSid(ircobj, irc)
@ -1033,22 +1060,22 @@ def create(irc, source, args):
try: try:
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
return return
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, 'Error: Invalid channel %r.' % channel) irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
return return
if source not in irc.channels[channel].users: if source not in irc.channels[channel].users:
irc.msg(source, 'Error: You must be in %r to complete this operation.' % channel) irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % channel)
return return
utils.checkAuthenticated(irc, source) utils.checkAuthenticated(irc, source)
localentry = getRelay((irc.name, channel)) localentry = getRelay((irc.name, channel))
if localentry: if localentry:
irc.msg(source, 'Error: Channel %r is already part of a relay.' % channel) irc.msg(irc.called_by, 'Error: Channel %r is already part of a relay.' % channel)
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)
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
@utils.add_cmd @utils.add_cmd
def destroy(irc, source, args): def destroy(irc, source, args):
@ -1058,10 +1085,10 @@ def destroy(irc, source, args):
try: try:
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.")
return return
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, 'Error: Invalid channel %r.' % channel) irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
return return
utils.checkAuthenticated(irc, source) utils.checkAuthenticated(irc, source)
@ -1071,9 +1098,9 @@ def destroy(irc, source, args):
removeChannel(world.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]
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
else: else:
irc.msg(source, 'Error: No such relay %r exists.' % channel) irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
return return
@utils.add_cmd @utils.add_cmd
@ -1086,7 +1113,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:
irc.msg(source, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).") irc.msg(irc.called_by, "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])
@ -1094,37 +1121,37 @@ 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):
irc.msg(source, 'Error: Invalid channel %r.' % c) irc.msg(irc.called_by, 'Error: Invalid channel %r.' % c)
return return
if source not in irc.channels[localchan].users: if source not in irc.channels[localchan].users:
irc.msg(source, 'Error: You must be in %r to complete this operation.' % localchan) irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % localchan)
return return
utils.checkAuthenticated(irc, source) utils.checkAuthenticated(irc, source)
if remotenet not in world.networkobjects: if remotenet not in world.networkobjects:
irc.msg(source, 'Error: No network named %r exists.' % remotenet) irc.msg(irc.called_by, 'Error: No network named %r exists.' % remotenet)
return return
localentry = getRelay((irc.name, localchan)) localentry = getRelay((irc.name, localchan))
if localentry: if localentry:
irc.msg(source, 'Error: Channel %r is already part of a relay.' % localchan) irc.msg(irc.called_by, '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:
irc.msg(source, 'Error: No such relay %r exists.' % channel) irc.msg(irc.called_by, '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']:
irc.msg(source, 'Error: Access denied (network is banned from linking to this channel).') irc.msg(irc.called_by, '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:
irc.msg(source, "Error: Remote channel '%s%s' is already" irc.msg(irc.called_by, "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
entry['links'].add((irc.name, localchan)) entry['links'].add((irc.name, localchan))
initializeChannel(irc, localchan) initializeChannel(irc, localchan)
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
@utils.add_cmd @utils.add_cmd
def delink(irc, source, args): def delink(irc, source, args):
@ -1135,7 +1162,7 @@ def delink(irc, source, args):
try: try:
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
return return
try: try:
remotenet = args[1].lower() remotenet = args[1].lower()
@ -1143,13 +1170,13 @@ def delink(irc, source, args):
remotenet = None remotenet = None
utils.checkAuthenticated(irc, source) utils.checkAuthenticated(irc, source)
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, 'Error: Invalid channel %r.' % channel) irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
return return
entry = getRelay((irc.name, channel)) entry = getRelay((irc.name, channel))
if entry: if entry:
if entry[0] == irc.name: # We own this channel. if entry[0] == irc.name: # We own this channel.
if not remotenet: if not remotenet:
irc.msg(source, "Error: You must select a network to " irc.msg(irc.called_by, "Error: You must select a network to "
"delink, or use the 'destroy' command to remove " "delink, or use the 'destroy' command to remove "
"this relay entirely (it was created on the current " "this relay entirely (it was created on the current "
"network).") "network).")
@ -1162,9 +1189,9 @@ def delink(irc, source, args):
else: else:
removeChannel(irc, channel) removeChannel(irc, channel)
db[entry]['links'].remove((irc.name, channel)) db[entry]['links'].remove((irc.name, channel))
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
else: else:
irc.msg(source, 'Error: No such relay %r.' % channel) irc.msg(irc.called_by, 'Error: No such relay %r.' % channel)
@utils.add_cmd @utils.add_cmd
def linked(irc, source, args): def linked(irc, source, args):
@ -1206,37 +1233,37 @@ def linkacl(irc, source, args):
cmd = args[0].lower() cmd = args[0].lower()
channel = utils.toLower(irc, args[1]) channel = utils.toLower(irc, args[1])
except IndexError: except IndexError:
irc.msg(source, missingargs) irc.msg(irc.called_by, missingargs)
return return
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.msg(source, 'Error: Invalid channel %r.' % channel) irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
return return
relay = getRelay((irc.name, channel)) relay = getRelay((irc.name, channel))
if not relay: if not relay:
irc.msg(source, 'Error: No such relay %r exists.' % channel) irc.msg(irc.called_by, '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)')
irc.msg(source, s) irc.msg(irc.called_by, s)
return return
try: try:
remotenet = args[2] remotenet = args[2]
except IndexError: except IndexError:
irc.msg(source, missingargs) irc.msg(irc.called_by, missingargs)
return return
if cmd == 'deny': if cmd == 'deny':
db[relay]['blocked_nets'].add(remotenet) db[relay]['blocked_nets'].add(remotenet)
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
elif cmd == 'allow': elif cmd == 'allow':
try: try:
db[relay]['blocked_nets'].remove(remotenet) db[relay]['blocked_nets'].remove(remotenet)
except KeyError: except KeyError:
irc.msg(source, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel)) irc.msg(irc.called_by, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
else: else:
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
else: else:
irc.msg(source, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd) irc.msg(irc.called_by, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
@utils.add_cmd @utils.add_cmd
def showuser(irc, source, args): def showuser(irc, source, args):
@ -1281,7 +1308,7 @@ def save(irc, source, args):
Saves the relay database to disk.""" Saves the relay database to disk."""
utils.checkAuthenticated(irc, source) utils.checkAuthenticated(irc, source)
exportDB() exportDB()
irc.msg(source, 'Done.') irc.msg(irc.called_by, 'Done.')
@utils.add_cmd @utils.add_cmd
def claim(irc, source, args): def claim(irc, source, args):
@ -1294,19 +1321,19 @@ def claim(irc, source, args):
try: try:
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
except IndexError: except IndexError:
irc.msg(source, "Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).") irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).")
return return
# We override getRelay() here to limit the search to the current network. # We override getRelay() here to limit the search to the current network.
relay = (irc.name, channel) relay = (irc.name, channel)
if relay not in db: if relay not in db:
irc.msg(source, 'Error: No such relay %r exists.' % channel) irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
return return
claimed = db[relay]["claim"] claimed = db[relay]["claim"]
try: try:
nets = args[1].strip() nets = args[1].strip()
except IndexError: # No networks given. except IndexError: # No networks given.
irc.msg(source, 'Channel \x02%s\x02 is claimed by: %s' % irc.msg(irc.called_by, 'Channel \x02%s\x02 is claimed by: %s' %
(channel, ', '.join(claimed) or '\x1D(none)\x1D')) (channel, ', '.join(claimed) or '\x1D(none)\x1D'))
else: else:
if nets == '-' or not nets: if nets == '-' or not nets:
@ -1314,5 +1341,5 @@ def claim(irc, source, args):
else: else:
claimed = set(nets.split(',')) claimed = set(nets.split(','))
db[relay]["claim"] = claimed db[relay]["claim"] = claimed
irc.msg(source, 'CLAIM for channel \x02%s\x02 set to: %s' % irc.msg(irc.called_by, 'CLAIM for channel \x02%s\x02 set to: %s' %
(channel, ', '.join(claimed) or '\x1D(none)\x1D')) (channel, ', '.join(claimed) or '\x1D(none)\x1D'))

View File

@ -15,7 +15,7 @@ from ts6_common import TS6BaseProtocol
class InspIRCdProtocol(TS6BaseProtocol): class InspIRCdProtocol(TS6BaseProtocol):
def __init__(self, irc): def __init__(self, irc):
super(InspIRCdProtocol, self).__init__(irc) super(InspIRCdProtocol, self).__init__(irc)
# Set our case mapping (rfc1459 maps "\" and "|" together, for example". # Set our case mapping (rfc1459 maps "\" and "|" together, for example).
self.casemapping = 'rfc1459' self.casemapping = 'rfc1459'
# Raw commands sent from servers vary from protocol to protocol. Here, we map # Raw commands sent from servers vary from protocol to protocol. Here, we map
@ -190,24 +190,28 @@ class InspIRCdProtocol(TS6BaseProtocol):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
self._sendModes(numeric, target, modes, ts=ts) self._sendModes(numeric, target, modes, ts=ts)
def _sendKill(self, numeric, target, reason):
self._send(numeric, 'KILL %s :%s' % (target, reason))
# We only need to call removeClient here if the target is one of our
# clients, since any remote servers will send a QUIT from
# their target if the command succeeds.
if utils.isInternalClient(self.irc, target):
self.removeClient(target)
def killServer(self, numeric, target, reason): def killServer(self, numeric, target, reason):
"""Sends a kill from a PyLink server.""" """Sends a kill from a PyLink server."""
if not utils.isInternalServer(self.irc, numeric): if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
self._send(numeric, 'KILL %s :%s' % (target, reason)) self._sendKill(numeric, target, reason)
# We don't need to call removeClient here, since the remote server
# will send a QUIT from the target if the command succeeds.
def killClient(self, numeric, target, reason): def killClient(self, numeric, target, reason):
"""Sends a kill from a PyLink client.""" """Sends a kill from a PyLink client."""
if not utils.isInternalClient(self.irc, numeric): if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
self._send(numeric, 'KILL %s :%s' % (target, reason)) self._sendKill(numeric, target, reason)
# We don't need to call removeClient here, since the remote server
# will send a QUIT from the target if the command succeeds.
def topicServer(self, numeric, target, text): def topicServer(self, numeric, target, text):
"""Sends a topic change from a PyLink server. This is usally used on burst.""" """Sends a topic change from a PyLink server. This is usually used on burst."""
if not utils.isInternalServer(self.irc, numeric): if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
ts = int(time.time()) ts = int(time.time())
@ -251,7 +255,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
if not (target is None or source is None): if not (target is None or source is None):
self._send(source, 'PING %s %s' % (source, target)) self._send(source, 'PING %s %s' % (source, target))
def numericServer(self, source, numeric, text): def numericServer(self, source, numeric, target, text):
raise NotImplementedError("Numeric sending is not yet implemented by this " raise NotImplementedError("Numeric sending is not yet implemented by this "
"protocol module. WHOIS requests are handled " "protocol module. WHOIS requests are handled "
"locally by InspIRCd servers, so there is no " "locally by InspIRCd servers, so there is no "
@ -266,7 +270,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
self._send(source, 'AWAY') self._send(source, 'AWAY')
def spawnServer(self, name, sid=None, uplink=None, desc=None): def spawnServer(self, name, sid=None, uplink=None, desc=None):
"""Spawns a server off a PyLink server.""" """
Spawns a server off a PyLink server. desc (server description)
defaults to the one in the config. uplink defaults to the main PyLink
server, and sid (the server ID) is automatically generated if not
given.
"""
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver # -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver
uplink = uplink or self.irc.sid uplink = uplink or self.irc.sid
name = name.lower() name = name.lower()

View File

@ -183,7 +183,7 @@ class TS6Protocol(TS6BaseProtocol):
assert target in self.irc.users, "Unknown target %r for killServer!" % target assert target in self.irc.users, "Unknown target %r for killServer!" % target
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason)) self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(self.irc, target) self.removeClient(target)
def killClient(self, numeric, target, reason): def killClient(self, numeric, target, reason):
"""Sends a kill from a PyLink client.""" """Sends a kill from a PyLink client."""
@ -191,10 +191,10 @@ class TS6Protocol(TS6BaseProtocol):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
assert target in self.irc.users, "Unknown target %r for killClient!" % target assert target in self.irc.users, "Unknown target %r for killClient!" % target
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason)) self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(self.irc, target) self.removeClient(target)
def topicServer(self, numeric, target, text): def topicServer(self, numeric, target, text):
"""Sends a topic change from a PyLink server. This is usally used on burst.""" """Sends a topic change from a PyLink server. This is usually used on burst."""
if not utils.isInternalServer(self.irc, numeric): if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
# TB # TB
@ -259,7 +259,12 @@ class TS6Protocol(TS6BaseProtocol):
self._send(source, 'AWAY') self._send(source, 'AWAY')
def spawnServer(self, name, sid=None, uplink=None, desc=None): def spawnServer(self, name, sid=None, uplink=None, desc=None):
"""Spawns a server off a PyLink server.""" """
Spawns a server off a PyLink server. desc (server description)
defaults to the one in the config. uplink defaults to the main PyLink
server, and sid (the server ID) is automatically generated if not
given.
"""
# -> :0AL SID test.server 1 0XY :some silly pseudoserver # -> :0AL SID test.server 1 0XY :some silly pseudoserver
uplink = uplink or self.irc.sid uplink = uplink or self.irc.sid
name = name.lower() name = name.lower()

View File

@ -12,29 +12,6 @@ class TS6BaseProtocol(Protocol):
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given.""" """Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
self.irc.send(':%s %s' % (source, msg)) self.irc.send(':%s %s' % (source, msg))
def parseArgs(self, args):
"""Parses a string of RFC1459-style arguments split into a list, where ":" may
be used for multi-word arguments that last until the end of a line.
"""
real_args = []
for idx, arg in enumerate(args):
real_args.append(arg)
# If the argument starts with ':' and ISN'T the first argument.
# The first argument is used for denoting the source UID/SID.
if arg.startswith(':') and idx != 0:
# : is used for multi-word arguments that last until the end
# of the message. We can use list splicing here to turn them all
# into one argument.
# Set the last arg to a joined version of the remaining args
arg = args[idx:]
arg = ' '.join(arg)[1:]
# Cut the original argument list right before the multi-word arg,
# and then append the multi-word arg.
real_args = args[:idx]
real_args.append(arg)
break
return real_args
def parseTS6Args(self, args): def parseTS6Args(self, args):
"""Similar to parseArgs(), but stripping leading colons from the first argument """Similar to parseArgs(), but stripping leading colons from the first argument
of a line (usually the sender field).""" of a line (usually the sender field)."""
@ -74,21 +51,6 @@ class TS6BaseProtocol(Protocol):
self._send(numeric, 'NICK %s %s' % (newnick, int(time.time()))) self._send(numeric, 'NICK %s %s' % (newnick, int(time.time())))
self.irc.users[numeric].nick = newnick self.irc.users[numeric].nick = newnick
def removeClient(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
del self.irc.channels[c]
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
sid = numeric[:3]
log.debug('Removing client %s from self.irc.users', numeric)
del self.irc.users[numeric]
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
def partClient(self, client, channel, reason=None): def partClient(self, client, channel, reason=None):
"""Sends a part from a PyLink client.""" """Sends a part from a PyLink client."""
channel = utils.toLower(self.irc, channel) channel = utils.toLower(self.irc, channel)

25
pylink
View File

@ -12,14 +12,11 @@ world.testing = False
import conf import conf
from log import log from log import log
import classes import classes
import utils
import coreplugin import coreplugin
if __name__ == '__main__': if __name__ == '__main__':
log.info('PyLink %s starting...', world.version) log.info('PyLink %s starting...', world.version)
if conf.conf['login']['password'] == 'changeme':
log.critical("You have not set the login details correctly! Exiting...")
sys.exit(2)
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
# Write a PID file. # Write a PID file.
with open('%s.pid' % conf.confname, 'w') as f: with open('%s.pid' % conf.confname, 'w') as f:
@ -27,16 +24,12 @@ 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.
world.plugins.append(coreplugin)
to_load = conf.conf['plugins'] to_load = conf.conf['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
# dynamically depending on which were configured. # dynamically depending on which were configured.
for plugin in to_load: for plugin in to_load:
try: try:
moduleinfo = imp.find_module(plugin, plugins_folder) world.plugins[plugin] = pl = utils.loadModuleFromFolder(plugin, world.plugins_folder)
pl = imp.load_source(plugin, moduleinfo[1])
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)
@ -48,17 +41,7 @@ if __name__ == '__main__':
pl.main() pl.main()
for network in conf.conf['servers']: for network in conf.conf['servers']:
protoname = conf.conf['servers'][network]['protocol'] proto = utils.getProtoModule(conf.conf['servers'][network]['protocol'])
try: world.networkobjects[network] = classes.Irc(network, proto, conf.conf)
moduleinfo = imp.find_module(protoname, protocols_folder)
proto = imp.load_source(protoname, moduleinfo[1])
except ImportError as e:
if str(e) == ('No module named %r' % protoname):
log.critical('Failed to load protocol module %r: The file could not be found.', protoname)
else:
log.critical('Failed to load protocol module: ImportError: %s', protoname, str(e))
sys.exit(2)
else:
world.networkobjects[network] = classes.Irc(network, proto)
world.started.set() world.started.set()
log.info("loaded plugins: %s", world.plugins) log.info("loaded plugins: %s", world.plugins)

View File

@ -10,7 +10,7 @@ cd "$WRAPPER_DIR"
if [[ ! -z "$(which cpulimit)" ]]; then if [[ ! -z "$(which cpulimit)" ]]; then
# -z makes cpulimit exit when PyLink dies. # -z makes cpulimit exit when PyLink dies.
cpulimit -l $LIMIT -z ./pylink cpulimit -l $LIMIT -z ./pylink $*
echo "PyLink has been started (daemonized) under cpulimit, and will automatically be throttled if it goes over the CPU limit of ${LIMIT}%." echo "PyLink has been started (daemonized) under cpulimit, and will automatically be throttled if it goes over the CPU limit of ${LIMIT}%."
echo "To kill the process manually, run ./kill.sh" echo "To kill the process manually, run ./kill.sh"
else else

View File

@ -26,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', world.bot_commands) self.assertIn('dummyf', world.commands)
self.assertIn('test', world.bot_commands) self.assertIn('test', world.commands)
self.assertNotIn('TEST', world.bot_commands) self.assertNotIn('TEST', world.commands)
def test_add_hook(self): def test_add_hook(self):
utils.add_hook(dummyf, 'join') utils.add_hook(dummyf, 'join')
self.assertIn('JOIN', world.command_hooks) self.assertIn('JOIN', world.hooks)
# Command names stored in uppercase. # Command names stored in uppercase.
self.assertNotIn('join', world.command_hooks) self.assertNotIn('join', world.hooks)
self.assertIn(dummyf, world.command_hooks['JOIN']) self.assertIn(dummyf, world.hooks['JOIN'])
def testIsNick(self): def testIsNick(self):
self.assertFalse(utils.isNick('abcdefgh', nicklen=3)) self.assertFalse(utils.isNick('abcdefgh', nicklen=3))

View File

@ -1,6 +1,7 @@
import string import string
import re import re
import inspect import inspect
import imp
from log import log from log import log
import world import world
@ -106,12 +107,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()
world.bot_commands[name].append(func) world.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()
world.command_hooks[command].append(func) world.hooks[command].append(func)
def toLower(irc, text): def toLower(irc, text):
"""Returns a lowercase representation of text based on the IRC object's """Returns a lowercase representation of text based on the IRC object's
@ -158,7 +159,8 @@ def isServerName(s):
hostmaskRe = re.compile(r'^\S+!\S+@\S+$') hostmaskRe = re.compile(r'^\S+!\S+@\S+$')
def isHostmask(text): def isHostmask(text):
"""Returns whether the given text is a valid hostmask.""" """Returns whether the given text is a valid hostmask."""
return bool(hostmaskRe.match(text)) # Band-aid patch here to prevent bad bans set by Janus forwarding people into invalid channels.
return hostmaskRe.match(text) and '#' not in text
def parseModes(irc, target, args): def parseModes(irc, target, args):
"""Parses a modestring list into a list of (mode, argument) tuples. """Parses a modestring list into a list of (mode, argument) tuples.
@ -176,10 +178,12 @@ def parseModes(irc, target, args):
args = args[1:] args = args[1:]
if usermodes: if usermodes:
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes) log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
assert target in irc.users, "Unknown user %r." % target
supported_modes = irc.umodes supported_modes = irc.umodes
oldmodes = irc.users[target].modes oldmodes = irc.users[target].modes
else: else:
log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes) log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes)
assert target in irc.channels, "Unknown channel %r." % target
supported_modes = irc.cmodes supported_modes = irc.cmodes
oldmodes = irc.channels[target].modes oldmodes = irc.channels[target].modes
res = [] res = []
@ -216,7 +220,8 @@ def parseModes(irc, target, args):
arg = nickToUid(irc, arg) or arg arg = nickToUid(irc, arg) or arg
if arg not in irc.users: # Target doesn't exist, skip it. if arg not in irc.users: # Target doesn't exist, skip it.
log.debug('(%s) Skipping setting mode "%s %s"; the ' log.debug('(%s) Skipping setting mode "%s %s"; the '
'target doesn\'t seem to exist!') 'target doesn\'t seem to exist!', self.name,
mode, arg)
continue continue
elif prefix == '+' and mode in supported_modes['*C']: elif prefix == '+' and mode in supported_modes['*C']:
# Only has parameter when setting. # Only has parameter when setting.
@ -494,3 +499,13 @@ def getHostmask(irc, user):
except AttributeError: except AttributeError:
host = '<unknown host>' host = '<unknown host>'
return '%s!%s@%s' % (nick, ident, host) return '%s!%s@%s' % (nick, ident, host)
def loadModuleFromFolder(name, folder):
"""Attempts an import of name from a specific folder, returning the resulting module."""
moduleinfo = imp.find_module(name, folder)
m = imp.load_source(name, moduleinfo[1])
return m
def getProtoModule(protoname):
"""Imports and returns the protocol module requested."""
return loadModuleFromFolder(protoname, world.protocols_folder)

View File

@ -3,21 +3,25 @@
from collections import defaultdict from collections import defaultdict
import threading import threading
import subprocess import subprocess
import os
# Global variable to indicate whether we're being ran directly, or imported # Global variable to indicate whether we're being ran directly, or imported
# for a testcase. # for a testcase.
testing = True testing = True
global bot_commands, command_hooks global commands, hooks
# This should be a mapping of command names to functions # This should be a mapping of command names to functions
bot_commands = defaultdict(list) commands = defaultdict(list)
command_hooks = defaultdict(list) hooks = defaultdict(list)
networkobjects = {} networkobjects = {}
schedulers = {} schedulers = {}
plugins = [] plugins = {}
whois_handlers = [] whois_handlers = []
started = threading.Event() started = threading.Event()
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
version = "<unknown>" version = "<unknown>"
source = "https://github.com/GLolol/PyLink" # CHANGE THIS IF YOU'RE FORKING!! source = "https://github.com/GLolol/PyLink" # CHANGE THIS IF YOU'RE FORKING!!