mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-01 01:09:22 +01:00
Merge branch 'master' into wip/unrealircd
This commit is contained in:
commit
7c7f07b3a9
@ -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.
|
||||
|
||||
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 currently include:
|
||||
|
89
classes.py
89
classes.py
@ -9,8 +9,8 @@ import hashlib
|
||||
from copy import deepcopy
|
||||
|
||||
from log import log
|
||||
from conf import conf
|
||||
import world
|
||||
import utils
|
||||
|
||||
### Exceptions
|
||||
|
||||
@ -21,11 +21,20 @@ class ProtocolError(Exception):
|
||||
|
||||
class Irc():
|
||||
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.aborted.clear()
|
||||
self.pseudoclient = None
|
||||
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
|
||||
self.servers = {self.sid: IrcServer(None, self.serverdata['hostname'],
|
||||
internal=True, desc=self.serverdata.get('serverdesc')
|
||||
@ -58,7 +67,7 @@ class Irc():
|
||||
self.uplink = None
|
||||
self.start_ts = int(time.time())
|
||||
|
||||
def __init__(self, netname, proto):
|
||||
def __init__(self, netname, proto, conf):
|
||||
# Initialize some variables
|
||||
self.name = netname.lower()
|
||||
self.conf = conf
|
||||
@ -84,13 +93,14 @@ class Irc():
|
||||
self.pingTimer = None
|
||||
|
||||
def connect(self):
|
||||
ip = self.serverdata["ip"]
|
||||
port = self.serverdata["port"]
|
||||
while True:
|
||||
self.initVars()
|
||||
ip = self.serverdata["ip"]
|
||||
port = self.serverdata["port"]
|
||||
checks_ok = True
|
||||
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)
|
||||
# Initial connection timeout is a lot smaller than the timeout after
|
||||
# we've connected; this is intentional.
|
||||
@ -160,20 +170,41 @@ class Irc():
|
||||
self._disconnect()
|
||||
autoconnect = self.serverdata.get('autoconnect')
|
||||
log.debug('(%s) Autoconnect delay set to %s seconds.', self.name, autoconnect)
|
||||
if autoconnect is not None and autoconnect >= 0:
|
||||
if autoconnect is not None and autoconnect >= 1:
|
||||
log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect)
|
||||
time.sleep(autoconnect)
|
||||
else:
|
||||
log.info('(%s) Stopping connect loop (autoconnect value %r is < 1).', self.name, autoconnect)
|
||||
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):
|
||||
"""Handy function to send messages/notices to clients. Source
|
||||
is optional, and defaults to the main PyLink client if not specified."""
|
||||
source = source or self.pseudoclient.uid
|
||||
if notice:
|
||||
self.proto.noticeClient(source, target, text)
|
||||
cmd = 'PYLINK_SELF_NOTICE'
|
||||
else:
|
||||
self.proto.messageClient(source, target, text)
|
||||
cmd = 'PYLINK_SELF_PRIVMSG'
|
||||
self.callHooks([source, cmd, {'target': target, 'text': text}])
|
||||
|
||||
def _disconnect(self):
|
||||
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)',
|
||||
self.name, parsed_args, command, hook_cmd)
|
||||
# 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:
|
||||
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)
|
||||
except Exception:
|
||||
# We don't want plugins to crash our servers...
|
||||
@ -286,6 +318,9 @@ class Irc():
|
||||
# contents of Irc().pseudoclient change.
|
||||
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
|
||||
|
||||
def __repr__(self):
|
||||
return "<classes.Irc object for %r>" % self.name
|
||||
|
||||
class IrcUser():
|
||||
def __init__(self, nick, ts, uid, ident='null', host='null',
|
||||
realname='PyLink dummy client', realhost='null',
|
||||
@ -408,6 +443,44 @@ class Protocol():
|
||||
self.casemapping = 'rfc1459'
|
||||
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):
|
||||
"""Dummy protocol module for testing purposes."""
|
||||
def handle_events(self, data):
|
||||
|
34
conf.py
34
conf.py
@ -31,9 +31,36 @@ testconf = {'bot':
|
||||
'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:
|
||||
conf = testconf
|
||||
confname = 'testconf'
|
||||
fname = None
|
||||
else:
|
||||
try:
|
||||
# Get the config name from the command line, falling back to config.yml
|
||||
@ -46,9 +73,4 @@ else:
|
||||
# we load.
|
||||
confname = 'pylink'
|
||||
fname = 'config.yml'
|
||||
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)
|
||||
conf = validateConf(loadConf(fname))
|
||||
|
@ -15,6 +15,9 @@ bot:
|
||||
# Console log verbosity: see https://docs.python.org/3/library/logging.html#logging-levels
|
||||
loglevel: DEBUG
|
||||
|
||||
# Fantasy command prefix, if the fantasy plugin is loaded.
|
||||
prefix: "."
|
||||
|
||||
login:
|
||||
# PyLink administrative login - Change this, or the service will not start!
|
||||
user: admin
|
||||
@ -36,6 +39,10 @@ relay:
|
||||
# will see yours.
|
||||
show_ips: false
|
||||
|
||||
# Whether subservers should be spawned for each relay network (requires reload to change).
|
||||
# Defaults to true.
|
||||
spawn_servers: true
|
||||
|
||||
servers:
|
||||
yournet:
|
||||
# Server IP, port, and passwords
|
||||
@ -67,7 +74,7 @@ servers:
|
||||
# (omitting the .py extension).
|
||||
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
|
||||
|
||||
# Sets ping frequency (i.e. how long we should wait between sending pings to our uplink).
|
||||
@ -94,7 +101,11 @@ servers:
|
||||
# ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc"
|
||||
|
||||
ts6net:
|
||||
ip: 127.0.0.1
|
||||
ip: ::1
|
||||
|
||||
# Determines whether IPv6 should be used for this connection.
|
||||
ipv6: yes
|
||||
|
||||
port: 7000
|
||||
recvpass: "abcd"
|
||||
sendpass: "abcd"
|
||||
@ -129,7 +140,19 @@ servers:
|
||||
|
||||
# Plugins to load (omit the .py extension)
|
||||
plugins:
|
||||
# Commands plugin: provides core commands such as logging in, shutting down PyLink, and
|
||||
# various command help.
|
||||
- commands
|
||||
|
||||
# Networks plugin: allows you to manage (dis)connections to networks while PyLink is running.
|
||||
- networks
|
||||
|
||||
# Bots plugin: allows you to manipulate pseudo-clients (bots) on networks.
|
||||
# - bots
|
||||
|
||||
# Relay plugin: Janus-style server-side relay plugin.
|
||||
# - relay
|
||||
|
||||
# Fantasy plugin: allows you to trigger commands in channels by PyLink's nick or a
|
||||
# configurable prefix character.
|
||||
# - fantasy
|
||||
|
@ -20,23 +20,10 @@ utils.add_hook(handle_kick, 'KICK')
|
||||
|
||||
def handle_commands(irc, source, command, args):
|
||||
"""Handle commands sent to the PyLink client (PRIVMSG)."""
|
||||
if args['target'] == irc.pseudoclient.uid:
|
||||
text = args['text'].strip()
|
||||
cmd_args = text.split(' ')
|
||||
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)))
|
||||
if args['target'] == irc.pseudoclient.uid and not utils.isInternalClient(irc, source):
|
||||
irc.called_by = source
|
||||
irc.callCommand(source, args['text'])
|
||||
|
||||
utils.add_hook(handle_commands, 'PRIVMSG')
|
||||
|
||||
def handle_whois(irc, source, command, args):
|
||||
|
@ -18,6 +18,6 @@ PyLink is an a modular, plugin-based IRC PseudoService framework. It uses swappa
|
||||
|
||||
#### Future topics (not yet available)
|
||||
|
||||
- [Using PyLink's utils module](using-utils.md)
|
||||
- [PyLink hooks reference](hooks-reference.md)
|
||||
- [Writing tests for PyLink modules](writing-tests.md)
|
||||
- [Using PyLink's utils module](using-utils.md)
|
||||
|
@ -74,9 +74,11 @@ For InspIRCd, the only ENCAP command we handl
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST handler; sends a hook with empty contents.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event handler for the InspIRCd protocol. This passes most commands to<br>
|
||||
the various handle_ABCD() functions elsewhere in this module, but also<br>
|
||||
handles commands sent in the initial server linking phase.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event handler for the InspIRCd protocol.<br>
|
||||
<br>
|
||||
This passes most commands to the various handle_ABCD() functions<br>
|
||||
elsewhere in this module, but also handles commands sent in the<br>
|
||||
initial server linking phase.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles FHOST, used for denoting hostname changes.</tt></dd></dl>
|
||||
|
||||
@ -94,6 +96,8 @@ handles commands sent in the initial server l
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming INVITEs.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming user mode changes.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming OPERTYPE, which is used to denote an oper up.<br>
|
||||
<br>
|
||||
This calls the internal hook PYLINK_CLIENT_OPERED, sets the internal<br>
|
||||
@ -101,9 +105,11 @@ opertype of the client, and assumes setting u
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PING commands, so we don't time out.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PONG commands. This is used to keep track of whether<br>
|
||||
the uplink is alive by the Irc() internals - a server that fails to reply<br>
|
||||
to our PINGs eventually times out and is disconnected.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PONG commands.<br>
|
||||
<br>
|
||||
This is used to keep track of whether the uplink is alive by the Irc()<br>
|
||||
internals - a server that fails to reply to our PINGs eventually<br>
|
||||
times out and is disconnected.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming SERVER commands (introduction of servers).</tt></dd></dl>
|
||||
|
||||
@ -111,7 +117,7 @@ to our PINGs eventually times out and is 
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends an INVITE from a PyLink client..</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins an internal spawned client <client> to a channel.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins a PyLink client to a channel.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends a kill from a PyLink client.</tt></dd></dl>
|
||||
|
||||
@ -119,13 +125,13 @@ to our PINGs eventually times out and is 
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a KNOCK from a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends modes from a PyLink client. <modes> should be<br>
|
||||
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends mode changes from a PyLink client. <modes> should be<br>
|
||||
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends modes from a PyLink server. <list of modes> should be<br>
|
||||
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends mode changes from a PyLink server. <list of modes> should be<br>
|
||||
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() 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 a PING to a target server. Periodic PINGs are sent to our uplink<br>
|
||||
automatically by the Irc() internals; plugins shouldn't have to use this.</tt></dd></dl>
|
||||
@ -140,16 +146,19 @@ Example uses:<br>
|
||||
<a href="#Class-sjoinServer">sjoinServer</a>('100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])<br>
|
||||
<a href="#Class-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid, '#test', [('o', 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 a client with nick <nick> on the given IRC 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 a client with nick <nick> on the given IRC connection.<br>
|
||||
<br>
|
||||
Note: No nick collision / valid nickname checks are done here; it is<br>
|
||||
up to plugins to make sure they don't introduce anything 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 a server off a PyLink 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 a server off a PyLink server. desc (server description)<br>
|
||||
defaults to the one in the config. uplink defaults to the main PyLink<br>
|
||||
server, and sid (the server ID) is automatically generated if 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 a PyLink server.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a burst topic from a PyLink server. This is usally used on burst.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a topic change from a PyLink server. This is usually used on burst.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates the ident, host, or realname of a PyLink 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 incoming KILLs.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming user mode changes. For channel mode changes,<br>
|
||||
TMODE (TS6/charybdis) and FMODE (InspIRCd) are used instead.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming NICK 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 incoming PRIVMSG/NOTICE.</tt></dd></dl>
|
||||
@ -172,7 +178,7 @@ TMODE (TS6/charybdis) and FMODE (InspIRCd) are use
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming 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 incoming QUITs.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming QUIT commands.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming SAVE messages, used to handle nick collisions.</tt></dd></dl>
|
||||
|
||||
@ -191,9 +197,6 @@ TB (TS6/charybdis) and FTOPIC (InspIRCd) are used&
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a NOTICE from a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses a string of <a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style arguments split into a list, where ":" may<br>
|
||||
be used for multi-word arguments that last until the end of a line.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar to <a href="#Class-parseArgs">parseArgs</a>(), but stripping leading colons from the first argument<br>
|
||||
of a line (usually the sender field).</tt></dd></dl>
|
||||
|
||||
@ -201,10 +204,15 @@ of a line (usually the sender field).</tt></dd></d
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal function to remove a client from our internal state.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a TOPIC change from a PyLink 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 a string of <a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style arguments split into a list, where ":" may<br>
|
||||
be used for multi-word arguments that last until the end of a line.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal function to remove a client from our internal state.</tt></dd></dl>
|
||||
|
||||
<hr>
|
||||
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
|
||||
<dl><dt><strong>__dict__</strong></dt>
|
||||
@ -245,9 +253,11 @@ For InspIRCd, the only ENCAP command we handl
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST handler; sends a hook with empty contents.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event handler for the InspIRCd protocol. This passes most commands to<br>
|
||||
the various handle_ABCD() functions elsewhere in this module, but also<br>
|
||||
handles commands sent in the initial server linking phase.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event handler for the InspIRCd protocol.<br>
|
||||
<br>
|
||||
This passes most commands to the various handle_ABCD() functions<br>
|
||||
elsewhere in this module, but also handles commands sent in the<br>
|
||||
initial server linking phase.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles FHOST, used for denoting hostname changes.</tt></dd></dl>
|
||||
|
||||
@ -265,6 +275,8 @@ handles commands sent in the initial server l
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming INVITEs.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming user mode changes.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming OPERTYPE, which is used to denote an oper up.<br>
|
||||
<br>
|
||||
This calls the internal hook PYLINK_CLIENT_OPERED, sets the internal<br>
|
||||
@ -272,9 +284,11 @@ opertype of the client, and assumes setting u
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PING commands, so we don't time out.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PONG commands. This is used to keep track of whether<br>
|
||||
the uplink is alive by the Irc() internals - a server that fails to reply<br>
|
||||
to our PINGs eventually times out and is disconnected.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming PONG commands.<br>
|
||||
<br>
|
||||
This is used to keep track of whether the uplink is alive by the Irc()<br>
|
||||
internals - a server that fails to reply to our PINGs eventually<br>
|
||||
times out and is disconnected.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming SERVER commands (introduction of servers).</tt></dd></dl>
|
||||
|
||||
@ -282,7 +296,7 @@ to our PINGs eventually times out and is 
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends an INVITE from a PyLink client..</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins an internal spawned client <client> to a channel.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins a PyLink client to a channel.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends a kill from a PyLink client.</tt></dd></dl>
|
||||
|
||||
@ -290,13 +304,13 @@ to our PINGs eventually times out and is 
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a KNOCK from a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends modes from a PyLink client. <modes> should be<br>
|
||||
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends mode changes from a PyLink client. <modes> should be<br>
|
||||
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends modes from a PyLink server. <list of modes> should be<br>
|
||||
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends mode changes from a PyLink server. <list of modes> should be<br>
|
||||
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() 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 a PING to a target server. Periodic PINGs are sent to our uplink<br>
|
||||
automatically by the Irc() internals; plugins shouldn't have to use this.</tt></dd></dl>
|
||||
@ -311,16 +325,19 @@ Example uses:<br>
|
||||
<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>('100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])<br>
|
||||
<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid, '#test', [('o', 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 a client with nick <nick> on the given IRC 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 a client with nick <nick> on the given IRC connection.<br>
|
||||
<br>
|
||||
Note: No nick collision / valid nickname checks are done here; it is<br>
|
||||
up to plugins to make sure they don't introduce anything 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 a server off a PyLink 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 a server off a PyLink server. desc (server description)<br>
|
||||
defaults to the one in the config. uplink defaults to the main PyLink<br>
|
||||
server, and sid (the server ID) is automatically generated if 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 a PyLink server.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a burst topic from a PyLink server. This is usally used on burst.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a topic change from a PyLink server. This is usually used on burst.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates the ident, host, or realname of a PyLink 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 incoming KILLs.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming user mode changes. For channel mode changes,<br>
|
||||
TMODE (TS6/charybdis) and FMODE (InspIRCd) are used instead.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming NICK 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 incoming PRIVMSG/NOTICE.</tt></dd></dl>
|
||||
@ -343,7 +357,7 @@ TMODE (TS6/charybdis) and FMODE (InspIRCd) are use
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles incoming 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 incoming QUITs.</tt></dd></dl>
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming QUIT commands.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles incoming SAVE messages, used to handle nick collisions.</tt></dd></dl>
|
||||
|
||||
@ -362,9 +376,6 @@ TB (TS6/charybdis) and FTOPIC (InspIRCd) are used&
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a NOTICE from a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses a string of <a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style arguments split into a list, where ":" may<br>
|
||||
be used for multi-word arguments that last until the end of a line.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar to <a href="#InspIRCdProtocol-parseArgs">parseArgs</a>(), but stripping leading colons from the first argument<br>
|
||||
of a line (usually the sender field).</tt></dd></dl>
|
||||
|
||||
@ -372,10 +383,15 @@ of a line (usually the sender field).</tt></dd></d
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits a PyLink client.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal function to remove a client from our internal state.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends a TOPIC change from a PyLink 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 a string of <a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style arguments split into a list, where ":" may<br>
|
||||
be used for multi-word arguments that last until the end of a line.</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal function to remove a client from our internal state.</tt></dd></dl>
|
||||
|
||||
<hr>
|
||||
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
|
||||
<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>
|
||||
|
||||
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||
<td width="100%"><strong>conf</strong> = {'bot': {'loglevel': 'CRITICAL', 'nick': 'PyLink', 'realname': 'PyLink Service Client', 'serverdesc': 'PyLink unit tests', 'user': 'pylink'}, 'servers': defaultdict(<function <lambda> at 0x7f0dbb516c80>, {})}<br>
|
||||
<strong>curdir</strong> = 'protocols'<br>
|
||||
<td width="100%"><strong>curdir</strong> = 'protocols'<br>
|
||||
<strong>log</strong> = <logging.RootLogger object></td></tr></table>
|
||||
</body></html>
|
@ -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`
|
||||
- `inviteClient`
|
||||
- `joinClient`
|
||||
- `kickClient`
|
||||
- `kickServer`
|
||||
- `killClient`
|
||||
- `killServer`
|
||||
- `knockClient`
|
||||
- `messageClient`
|
||||
- `modeClient`
|
||||
- `modeServer`
|
||||
- `nickClient`
|
||||
- `noticeClient`
|
||||
- `numericServer`
|
||||
- `partClient`
|
||||
- `pingServer`
|
||||
- `quitClient`
|
||||
- `removeClient`
|
||||
- `sjoinServer`
|
||||
- `spawnClient`
|
||||
- `spawnServer`
|
||||
- `squitServer`
|
||||
- `topicClient`
|
||||
- `topicServer`
|
||||
- `updateClient`
|
||||
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.
|
||||
|
||||
See also: [inspircd.html](inspircd.html) for an auto-generated specification of the InspIRCd protocol module.
|
||||
|
||||
## Tasks
|
||||
|
||||
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:
|
||||
|
||||
1) Handle incoming commands from the uplink IRCd.
|
||||
|
||||
2) Return [hook data](hooks-reference.md) for relevant commands, so that plugins can receive data from IRC.
|
||||
|
||||
3) Make sure channel/user states are kept correctly. Joins, quits, parts, kicks, mode changes, nick changes, etc. **must** be handled accurately.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
## Core functions
|
||||
|
||||
The following functions *must* be implemented by any protocol module within its main class, since they are used by the IRC internals.
|
||||
|
||||
- **`connect`**`(self)` - Initializes a connection to a server.
|
||||
|
||||
- **`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.
|
||||
|
1
docs/technical/using-utils.md
Normal file
1
docs/technical/using-utils.md
Normal 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
233
docs/technical/utils.html
Normal 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> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"> <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> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
|
||||
|
||||
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </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> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
|
||||
|
||||
<tr><td bgcolor="#ee77aa"><tt> </tt></td><td> </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> <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> </tt></td>
|
||||
<td colspan=2><tt># This is separate from classes.py to prevent import loops.<br> </tt></td></tr>
|
||||
<tr><td> </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 of weak references to the object (if 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 self. See help(type(self)) for accurate 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 and return a new <a href="builtins.html#object">object</a>. See help(type) for accurate 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 delattr(self, name).</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="NotAuthenticatedError-__getattribute__"><strong>__getattribute__</strong></a>(self, name, /)</dt><dd><tt>Return getattr(self, 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 repr(self).</tt></dd></dl>
|
||||
|
||||
<dl><dt><a name="NotAuthenticatedError-__setattr__"><strong>__setattr__</strong></a>(self, name, value, /)</dt><dd><tt>Implement setattr(self, name, 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 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) --<br>
|
||||
set self.<strong>__traceback__</strong> to tb and return 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 cause</tt></dd>
|
||||
</dl>
|
||||
<dl><dt><strong>__context__</strong></dt>
|
||||
<dd><tt>exception 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> <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> </tt></td>
|
||||
<td colspan=2><tt>TS6 SID Generator. <query> is a 3 character string with any combination of<br>
|
||||
uppercase letters, digits, and #'s. it must contain at least one #,<br>
|
||||
which are used by the generator as a wildcard. On every <a href="#TS6SIDGenerator-next_sid">next_sid</a>() call,<br>
|
||||
the first available wildcard character (from the right) will be<br>
|
||||
incremented to generate the next SID.<br>
|
||||
<br>
|
||||
When there are no more available SIDs left (SIDs are not reused, only<br>
|
||||
incremented), RuntimeError is raised.<br>
|
||||
<br>
|
||||
Example queries:<br>
|
||||
"1#A" would give: 10A, 11A, 12A ... 19A, 1AA, 1BA ... 1ZA (36 total results)<br>
|
||||
"#BQ" would give: 0BQ, 1BQ, 2BQ ... 9BQ (10 total results)<br>
|
||||
"6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results)<br> </tt></td></tr>
|
||||
<tr><td> </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 for instance variables (if defined)</tt></dd>
|
||||
</dl>
|
||||
<dl><dt><strong>__weakref__</strong></dt>
|
||||
<dd><tt>list of weak references to the object (if 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> <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> </tt></td>
|
||||
<td colspan=2><tt>TS6 UID Generator module, adapted from InspIRCd source<br>
|
||||
https://github.com/inspircd/inspircd/blob/f449c6b296ab/src/server.cpp#L85-L156<br> </tt></td></tr>
|
||||
<tr><td> </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 for instance variables (if defined)</tt></dd>
|
||||
</dl>
|
||||
<dl><dt><strong>__weakref__</strong></dt>
|
||||
<dd><tt>list of weak references to the object (if 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> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
|
||||
|
||||
<tr><td bgcolor="#eeaa77"><tt> </tt></td><td> </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 a hook <func> for command <command>.</tt></dd></dl>
|
||||
<dl><dt><a name="-applyModes"><strong>applyModes</strong></a>(irc, target, changedmodes)</dt><dd><tt>Takes a list of parsed IRC modes, and applies them on the given target.<br>
|
||||
<br>
|
||||
The target can be either a channel or a user; this is handled automatically.</tt></dd></dl>
|
||||
<dl><dt><a name="-checkAuthenticated"><strong>checkAuthenticated</strong></a>(irc, uid, allowAuthed=True, allowOper=True)</dt><dd><tt>Checks whetherthe given user has operator status on PyLink, raising<br>
|
||||
<a href="#NotAuthenticatedError">NotAuthenticatedError</a> and logging the access denial if not.</tt></dd></dl>
|
||||
<dl><dt><a name="-clientToServer"><strong>clientToServer</strong></a>(irc, numeric)</dt><dd><tt>Finds the SID of the server a user is on.</tt></dd></dl>
|
||||
<dl><dt><a name="-getHostmask"><strong>getHostmask</strong></a>(irc, user)</dt><dd><tt>Gets the hostmask of the given user, if present.</tt></dd></dl>
|
||||
<dl><dt><a name="-getProtoModule"><strong>getProtoModule</strong></a>(protoname)</dt><dd><tt>Imports and returns the protocol module requested.</tt></dd></dl>
|
||||
<dl><dt><a name="-isChannel"><strong>isChannel</strong></a>(s)</dt><dd><tt>Checks whether the string given is a valid channel name.</tt></dd></dl>
|
||||
<dl><dt><a name="-isHostmask"><strong>isHostmask</strong></a>(text)</dt><dd><tt>Returns whether the given text is a valid hostmask.</tt></dd></dl>
|
||||
<dl><dt><a name="-isInternalClient"><strong>isInternalClient</strong></a>(irc, numeric)</dt><dd><tt>Checks whether the given numeric is a PyLink Client,<br>
|
||||
returning the SID of the server it's on if so.</tt></dd></dl>
|
||||
<dl><dt><a name="-isInternalServer"><strong>isInternalServer</strong></a>(irc, sid)</dt><dd><tt>Returns whether the given SID is an internal PyLink server.</tt></dd></dl>
|
||||
<dl><dt><a name="-isManipulatableClient"><strong>isManipulatableClient</strong></a>(irc, uid)</dt><dd><tt>Returns whether the given user is marked as an internal, manipulatable<br>
|
||||
client. Usually, automatically spawned services clients should have this<br>
|
||||
set True to prevent interactions with opers (like mode changes) from<br>
|
||||
causing desyncs.</tt></dd></dl>
|
||||
<dl><dt><a name="-isNick"><strong>isNick</strong></a>(s, nicklen=None)</dt><dd><tt>Checks whether the string given is a valid nick.</tt></dd></dl>
|
||||
<dl><dt><a name="-isOper"><strong>isOper</strong></a>(irc, uid, allowAuthed=True, allowOper=True)</dt><dd><tt>Returns whether the given user has operator status on PyLink. This can be achieved<br>
|
||||
by either identifying to PyLink as admin (if allowAuthed is True),<br>
|
||||
or having user mode +o set (if allowOper is True). At least one of<br>
|
||||
allowAuthed or allowOper must be True for this to give any meaningful<br>
|
||||
results.</tt></dd></dl>
|
||||
<dl><dt><a name="-isServerName"><strong>isServerName</strong></a>(s)</dt><dd><tt>Checks whether the string given is a server name.</tt></dd></dl>
|
||||
<dl><dt><a name="-joinModes"><strong>joinModes</strong></a>(modes)</dt><dd><tt>Takes a list of (mode, arg) tuples in <a href="#-parseModes">parseModes</a>() format, and<br>
|
||||
joins them into a string.<br>
|
||||
<br>
|
||||
See testJoinModes in tests/test_utils.py for some examples.</tt></dd></dl>
|
||||
<dl><dt><a name="-loadModuleFromFolder"><strong>loadModuleFromFolder</strong></a>(name, folder)</dt><dd><tt>Attempts an import of name from a specific folder, returning the resulting module.</tt></dd></dl>
|
||||
<dl><dt><a name="-nickToUid"><strong>nickToUid</strong></a>(irc, nick)</dt><dd><tt>Returns the UID of a user named nick, if present.</tt></dd></dl>
|
||||
<dl><dt><a name="-parseModes"><strong>parseModes</strong></a>(irc, target, args)</dt><dd><tt>Parses a modestring list into a list of (mode, argument) tuples.<br>
|
||||
['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')]</tt></dd></dl>
|
||||
<dl><dt><a name="-reverseModes"><strong>reverseModes</strong></a>(irc, target, modes, oldobj=None)</dt><dd><tt>Reverses/Inverts the mode string or mode list given.<br>
|
||||
<br>
|
||||
Optionally, an oldobj argument can be given to look at an earlier state of<br>
|
||||
a channel/user <a href="builtins.html#object">object</a>, e.g. for checking the op status of a mode setter<br>
|
||||
before their modes are processed and added to the channel state.<br>
|
||||
<br>
|
||||
This function allows both mode strings or mode lists. Example uses:<br>
|
||||
"+mi-lk test => "-mi+lk test"<br>
|
||||
"mi-k test => "-mi+k test"<br>
|
||||
[('+m', None), ('+r', None), ('+l', '3'), ('-o', 'person')<br>
|
||||
=> {('-m', None), ('-r', None), ('-l', None), ('+o', 'person')})<br>
|
||||
{('s', None), ('+o', 'whoever') => {('-s', None), ('-o', 'whoever')})</tt></dd></dl>
|
||||
<dl><dt><a name="-toLower"><strong>toLower</strong></a>(irc, text)</dt><dd><tt>Returns a lowercase representation of text based on the IRC <a href="builtins.html#object">object</a>'s<br>
|
||||
casemapping (rfc1459 or 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> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
|
||||
|
||||
<tr><td bgcolor="#55aa55"><tt> </tt></td><td> </td>
|
||||
<td width="100%"><strong>hostmaskRe</strong> = re.compile('^\\S+!\\S+@\\S+$')<br>
|
||||
<strong>log</strong> = <logging.RootLogger object></td></tr></table>
|
||||
</body></html>
|
@ -20,7 +20,7 @@ def spawnclient(irc, source, args):
|
||||
try:
|
||||
nick, ident, host = args[:3]
|
||||
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
|
||||
irc.proto.spawnClient(nick, ident, host, manipulatable=True)
|
||||
|
||||
@ -33,15 +33,15 @@ def quit(irc, source, args):
|
||||
try:
|
||||
nick = args[0]
|
||||
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
|
||||
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
|
||||
u = utils.nickToUid(irc, nick)
|
||||
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
||||
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
|
||||
irc.proto.quitClient(u, quitmsg)
|
||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
||||
@ -57,15 +57,15 @@ def joinclient(irc, source, args):
|
||||
if not clist:
|
||||
raise 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
|
||||
u = utils.nickToUid(irc, nick)
|
||||
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
|
||||
for channel in clist:
|
||||
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
|
||||
irc.proto.joinClient(u, channel)
|
||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
|
||||
@ -83,16 +83,16 @@ def nick(irc, source, args):
|
||||
nick = args[0]
|
||||
newnick = args[1]
|
||||
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
|
||||
u = utils.nickToUid(irc, nick)
|
||||
if newnick in ('0', u):
|
||||
newnick = u
|
||||
elif not utils.isNick(newnick):
|
||||
irc.msg(source, 'Error: Invalid nickname %r.' % newnick)
|
||||
irc.msg(irc.called_by, 'Error: Invalid nickname %r.' % newnick)
|
||||
return
|
||||
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
|
||||
irc.proto.nickClient(u, newnick)
|
||||
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(',')
|
||||
reason = ' '.join(args[2:])
|
||||
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
|
||||
u = utils.nickToUid(irc, nick)
|
||||
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
|
||||
for channel in clist:
|
||||
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
|
||||
irc.proto.partClient(u, channel, reason)
|
||||
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]
|
||||
reason = ' '.join(args[3:])
|
||||
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
|
||||
u = utils.nickToUid(irc, nick) or nick
|
||||
targetu = utils.nickToUid(irc, target)
|
||||
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
|
||||
if utils.isInternalServer(irc, u):
|
||||
irc.proto.kickServer(u, channel, targetu, reason)
|
||||
@ -155,20 +155,20 @@ def mode(irc, source, args):
|
||||
try:
|
||||
modesource, target, modes = args[0], args[1], args[2:]
|
||||
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
|
||||
target = utils.nickToUid(irc, target) or target
|
||||
extclient = target in irc.users and not utils.isInternalClient(irc, target)
|
||||
parsedmodes = utils.parseModes(irc, target, modes)
|
||||
ischannel = target in irc.channels
|
||||
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
|
||||
elif not parsedmodes:
|
||||
irc.msg(source, "Error: No valid modes were given.")
|
||||
irc.msg(irc.called_by, "Error: No valid modes were given.")
|
||||
return
|
||||
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
|
||||
if utils.isInternalServer(irc, modesource):
|
||||
# Setting modes from a server.
|
||||
@ -189,21 +189,21 @@ def msg(irc, source, args):
|
||||
try:
|
||||
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
|
||||
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
|
||||
sourceuid = utils.nickToUid(irc, msgsource)
|
||||
if not sourceuid:
|
||||
irc.msg(source, 'Error: Unknown user %r.' % msgsource)
|
||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % msgsource)
|
||||
return
|
||||
if not utils.isChannel(target):
|
||||
real_target = utils.nickToUid(irc, target)
|
||||
if real_target is None:
|
||||
irc.msg(source, 'Error: Unknown user %r.' % target)
|
||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
|
||||
return
|
||||
else:
|
||||
real_target = target
|
||||
if not text:
|
||||
irc.msg(source, 'Error: No text given.')
|
||||
irc.msg(irc.called_by, 'Error: No text given.')
|
||||
return
|
||||
irc.proto.messageClient(sourceuid, real_target, text)
|
||||
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
||||
|
@ -2,12 +2,15 @@
|
||||
import sys
|
||||
import os
|
||||
from time import ctime
|
||||
import itertools
|
||||
import gc
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import utils
|
||||
from conf import conf
|
||||
import conf
|
||||
from log import log
|
||||
import world
|
||||
import classes
|
||||
|
||||
@utils.add_cmd
|
||||
def status(irc, source, args):
|
||||
@ -16,24 +19,27 @@ def status(irc, source, args):
|
||||
Returns your current PyLink login status."""
|
||||
identified = irc.users[source].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:
|
||||
irc.msg(source, 'You are not identified as anyone.')
|
||||
irc.msg(source, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
|
||||
irc.msg(irc.called_by, 'You are not identified as anyone.')
|
||||
irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
|
||||
|
||||
@utils.add_cmd
|
||||
def identify(irc, source, args):
|
||||
"""<username> <password>
|
||||
|
||||
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:
|
||||
username, password = args[0], args[1]
|
||||
except IndexError:
|
||||
irc.msg(source, 'Error: Not enough arguments.')
|
||||
return
|
||||
# Usernames are case-insensitive, passwords are NOT.
|
||||
if username.lower() == conf['login']['user'].lower() and password == conf['login']['password']:
|
||||
realuser = conf['login']['user']
|
||||
if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']:
|
||||
realuser = irc.conf['login']['user']
|
||||
irc.users[source].identified = realuser
|
||||
irc.msg(source, 'Successfully logged in as %s.' % realuser)
|
||||
log.info("(%s) Successful login to %r by %s.",
|
||||
@ -48,14 +54,14 @@ def listcommands(irc, source, args):
|
||||
"""takes no arguments.
|
||||
|
||||
Returns a list of available commands PyLink has to offer."""
|
||||
cmds = list(world.bot_commands.keys())
|
||||
cmds = list(world.commands.keys())
|
||||
cmds.sort()
|
||||
for idx, cmd in enumerate(cmds):
|
||||
nfuncs = len(world.bot_commands[cmd])
|
||||
nfuncs = len(world.commands[cmd])
|
||||
if nfuncs > 1:
|
||||
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
|
||||
irc.msg(source, '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, 'Available commands include: %s' % ', '.join(cmds))
|
||||
irc.msg(irc.called_by, 'To see help on a specific command, type \x02help <command>\x02.')
|
||||
utils.add_cmd(listcommands, 'list')
|
||||
|
||||
@utils.add_cmd
|
||||
@ -68,13 +74,13 @@ def help(irc, source, args):
|
||||
except IndexError: # No argument given, just return 'list' output
|
||||
listcommands(irc, source, args)
|
||||
return
|
||||
if command not in world.bot_commands:
|
||||
if command not in world.commands:
|
||||
irc.msg(source, 'Error: Unknown command %r.' % command)
|
||||
return
|
||||
else:
|
||||
funcs = world.bot_commands[command]
|
||||
funcs = world.commands[command]
|
||||
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])))
|
||||
for func in funcs:
|
||||
doc = func.__doc__
|
||||
@ -85,7 +91,7 @@ def help(irc, source, args):
|
||||
# arguments the command takes.
|
||||
lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod)
|
||||
for line in lines:
|
||||
irc.msg(source, line.strip())
|
||||
irc.msg(irc.called_by, line.strip())
|
||||
else:
|
||||
irc.msg(source, "Error: Command %r (from plugin %r) "
|
||||
"doesn't offer any help." % (command, mod))
|
||||
@ -99,14 +105,14 @@ def showuser(irc, source, args):
|
||||
try:
|
||||
target = args[0]
|
||||
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
|
||||
u = utils.nickToUid(irc, target) or target
|
||||
# Only show private info if the person is calling 'showuser' on themselves,
|
||||
# or is an oper.
|
||||
verbose = utils.isOper(irc, source) or u == source
|
||||
if u not in irc.users:
|
||||
irc.msg(source, 'Error: Unknown user %r.' % target)
|
||||
irc.msg(irc.called_by, 'Error: Unknown user %r.' % target)
|
||||
return
|
||||
|
||||
f = lambda s: irc.msg(source, s)
|
||||
@ -134,10 +140,10 @@ def showchan(irc, source, args):
|
||||
try:
|
||||
channel = utils.toLower(irc, args[0])
|
||||
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
|
||||
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
|
||||
|
||||
f = lambda s: irc.msg(source, s)
|
||||
@ -191,5 +197,154 @@ def version(irc, source, args):
|
||||
"""takes no arguments.
|
||||
|
||||
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(source, "The source of this program is available at \x02%s\x02." % world.source)
|
||||
irc.msg(irc.called_by, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version)
|
||||
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.")
|
||||
|
@ -6,6 +6,9 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import utils
|
||||
from log import log
|
||||
|
||||
# Easier access to world through eval/exec.
|
||||
import world
|
||||
|
||||
def _exec(irc, source, args):
|
||||
"""<code>
|
||||
|
||||
@ -14,7 +17,7 @@ def _exec(irc, source, args):
|
||||
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||
args = ' '.join(args)
|
||||
if not args.strip():
|
||||
irc.msg(source, 'No code entered!')
|
||||
irc.msg(irc.called_by, 'No code entered!')
|
||||
return
|
||||
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
||||
exec(args, globals(), locals())
|
||||
@ -28,8 +31,8 @@ def _eval(irc, source, args):
|
||||
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||
args = ' '.join(args)
|
||||
if not args.strip():
|
||||
irc.msg(source, 'No code entered!')
|
||||
irc.msg(irc.called_by, 'No code entered!')
|
||||
return
|
||||
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')
|
||||
|
33
plugins/fantasy.py
Executable file
33
plugins/fantasy.py
Executable 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')
|
@ -22,12 +22,12 @@ def disconnect(irc, source, args):
|
||||
netname = args[0]
|
||||
network = world.networkobjects[netname]
|
||||
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
|
||||
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
|
||||
irc.msg(source, "Done.")
|
||||
irc.msg(irc.called_by, "Done.")
|
||||
# Abort the connection! Simple as that.
|
||||
network.aborted.set()
|
||||
|
||||
@ -41,18 +41,18 @@ def connect(irc, source, args):
|
||||
netname = args[0]
|
||||
network = world.networkobjects[netname]
|
||||
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
|
||||
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
|
||||
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!
|
||||
network.initVars()
|
||||
network.connection_thread = threading.Thread(target=network.connect)
|
||||
network.connection_thread.start()
|
||||
irc.msg(source, "Done.")
|
||||
irc.msg(irc.called_by, "Done.")
|
||||
|
||||
@utils.add_cmd
|
||||
def autoconnect(irc, source, args):
|
||||
@ -66,13 +66,13 @@ def autoconnect(irc, source, args):
|
||||
seconds = float(args[1])
|
||||
network = world.networkobjects[netname]
|
||||
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
|
||||
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
|
||||
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
|
||||
network.serverdata['autoconnect'] = seconds
|
||||
irc.msg(source, "Done.")
|
||||
irc.msg(irc.called_by, "Done.")
|
||||
|
141
plugins/relay.py
141
plugins/relay.py
@ -26,6 +26,7 @@ spawnlocks = defaultdict(threading.RLock)
|
||||
spawnlocks_servers = defaultdict(threading.RLock)
|
||||
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
|
||||
killcache = ExpiringDict(max_len=5, max_age_seconds=10)
|
||||
relay_started = True
|
||||
|
||||
### INTERNAL FUNCTIONS
|
||||
|
||||
@ -38,8 +39,10 @@ def initializeAll(irc):
|
||||
network, channel = link
|
||||
initializeChannel(irc, channel)
|
||||
|
||||
def main():
|
||||
def main(irc=None):
|
||||
"""Main function, called during plugin loading at start."""
|
||||
global relay_started
|
||||
relay_started = True
|
||||
loadDB()
|
||||
world.schedulers['relaydb'] = scheduler = sched.scheduler()
|
||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||
@ -48,6 +51,23 @@ def main():
|
||||
thread = threading.Thread(target=scheduler.run)
|
||||
thread.daemon = True
|
||||
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=''):
|
||||
"""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
|
||||
automatically."""
|
||||
scheduler = world.schedulers.get('relaydb')
|
||||
if reschedule and scheduler:
|
||||
if reschedule and scheduler and relay_started:
|
||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||
log.debug("Relay: exporting links database to %s", dbname)
|
||||
with open(dbname, 'wb') as f:
|
||||
@ -144,6 +164,12 @@ def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
|
||||
def getRemoteSid(irc, remoteirc):
|
||||
"""Gets the remote server SID representing remoteirc on irc, spawning
|
||||
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]:
|
||||
try:
|
||||
sid = relayservers[irc.name][remoteirc.name]
|
||||
@ -156,7 +182,9 @@ def getRemoteSid(irc, remoteirc):
|
||||
except ValueError: # Network not initialized yet.
|
||||
log.exception('(%s) Failed to spawn server for %r:',
|
||||
irc.name, remoteirc.name)
|
||||
return
|
||||
irc.aborted.set()
|
||||
else:
|
||||
irc.servers[sid].remote = remoteirc.name
|
||||
relayservers[irc.name][remoteirc.name] = sid
|
||||
return sid
|
||||
|
||||
@ -223,16 +251,11 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
||||
else:
|
||||
realhost = None
|
||||
ip = '0.0.0.0'
|
||||
try:
|
||||
u = remoteirc.proto.spawnClient(nick, ident=ident,
|
||||
u = remoteirc.proto.spawnClient(nick, ident=ident,
|
||||
host=host, realname=realname,
|
||||
modes=modes, ts=userobj.ts,
|
||||
opertype=opertype, server=rsid,
|
||||
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].opertype = opertype
|
||||
away = userobj.away
|
||||
@ -660,11 +683,11 @@ def handle_part(irc, numeric, command, args):
|
||||
del relayusers[(irc.name, numeric)][remoteirc.name]
|
||||
utils.add_hook(handle_part, 'PART')
|
||||
|
||||
def handle_privmsg(irc, numeric, command, args):
|
||||
notice = (command == 'NOTICE')
|
||||
def handle_messages(irc, numeric, command, args):
|
||||
notice = (command in ('NOTICE', 'PYLINK_SELF_NOTICE'))
|
||||
target = args['target']
|
||||
text = args['text']
|
||||
if target == irc.pseudoclient.uid:
|
||||
if utils.isInternalClient(irc, numeric) and utils.isInternalClient(irc, target):
|
||||
return
|
||||
relay = getRelay((irc.name, target))
|
||||
remoteusers = relayusers[(irc.name, numeric)]
|
||||
@ -685,11 +708,11 @@ def handle_privmsg(irc, numeric, command, args):
|
||||
'messages over the relay.' % target, notice=True)
|
||||
return
|
||||
if utils.isChannel(target):
|
||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||
remoteirc = world.networkobjects[netname]
|
||||
for name, remoteirc in world.networkobjects.items():
|
||||
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
|
||||
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
|
||||
real_target = prefix + real_target
|
||||
if notice:
|
||||
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)
|
||||
else:
|
||||
remoteirc.proto.messageClient(user, real_target, text)
|
||||
utils.add_hook(handle_privmsg, 'PRIVMSG')
|
||||
utils.add_hook(handle_privmsg, 'NOTICE')
|
||||
for cmd in ('PRIVMSG', 'NOTICE', 'PYLINK_SELF_NOTICE', 'PYLINK_SELF_PRIVMSG'):
|
||||
utils.add_hook(handle_messages, cmd)
|
||||
|
||||
def handle_kick(irc, source, command, args):
|
||||
channel = args['channel']
|
||||
@ -977,7 +1000,11 @@ def handle_disconnect(irc, numeric, command, args):
|
||||
if irc.name in v:
|
||||
del relayusers[k][irc.name]
|
||||
if k[0] == irc.name:
|
||||
del relayusers[k]
|
||||
try:
|
||||
handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'})
|
||||
del relayusers[k]
|
||||
except KeyError:
|
||||
pass
|
||||
for name, ircobj in world.networkobjects.items():
|
||||
if name != irc.name:
|
||||
rsid = getRemoteSid(ircobj, irc)
|
||||
@ -1033,22 +1060,22 @@ def create(irc, source, args):
|
||||
try:
|
||||
channel = utils.toLower(irc, args[0])
|
||||
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
|
||||
if not utils.isChannel(channel):
|
||||
irc.msg(source, 'Error: Invalid channel %r.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
||||
return
|
||||
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
|
||||
utils.checkAuthenticated(irc, source)
|
||||
localentry = getRelay((irc.name, channel))
|
||||
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
|
||||
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
|
||||
initializeChannel(irc, channel)
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
|
||||
@utils.add_cmd
|
||||
def destroy(irc, source, args):
|
||||
@ -1058,10 +1085,10 @@ def destroy(irc, source, args):
|
||||
try:
|
||||
channel = utils.toLower(irc, args[0])
|
||||
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
|
||||
if not utils.isChannel(channel):
|
||||
irc.msg(source, 'Error: Invalid channel %r.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
||||
return
|
||||
utils.checkAuthenticated(irc, source)
|
||||
|
||||
@ -1071,9 +1098,9 @@ def destroy(irc, source, args):
|
||||
removeChannel(world.networkobjects.get(link[0]), link[1])
|
||||
removeChannel(irc, channel)
|
||||
del db[entry]
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
else:
|
||||
irc.msg(source, 'Error: No such relay %r exists.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel)
|
||||
return
|
||||
|
||||
@utils.add_cmd
|
||||
@ -1086,7 +1113,7 @@ def link(irc, source, args):
|
||||
channel = utils.toLower(irc, args[1])
|
||||
remotenet = args[0].lower()
|
||||
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
|
||||
try:
|
||||
localchan = utils.toLower(irc, args[2])
|
||||
@ -1094,37 +1121,37 @@ def link(irc, source, args):
|
||||
localchan = channel
|
||||
for c in (channel, localchan):
|
||||
if not utils.isChannel(c):
|
||||
irc.msg(source, 'Error: Invalid channel %r.' % c)
|
||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % c)
|
||||
return
|
||||
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
|
||||
utils.checkAuthenticated(irc, source)
|
||||
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
|
||||
localentry = getRelay((irc.name, localchan))
|
||||
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
|
||||
try:
|
||||
entry = db[(remotenet, channel)]
|
||||
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
|
||||
else:
|
||||
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
|
||||
for link in entry['links']:
|
||||
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,
|
||||
channel, link[1]))
|
||||
return
|
||||
entry['links'].add((irc.name, localchan))
|
||||
initializeChannel(irc, localchan)
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
|
||||
@utils.add_cmd
|
||||
def delink(irc, source, args):
|
||||
@ -1135,7 +1162,7 @@ def delink(irc, source, args):
|
||||
try:
|
||||
channel = utils.toLower(irc, args[0])
|
||||
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
|
||||
try:
|
||||
remotenet = args[1].lower()
|
||||
@ -1143,13 +1170,13 @@ def delink(irc, source, args):
|
||||
remotenet = None
|
||||
utils.checkAuthenticated(irc, source)
|
||||
if not utils.isChannel(channel):
|
||||
irc.msg(source, 'Error: Invalid channel %r.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
||||
return
|
||||
entry = getRelay((irc.name, channel))
|
||||
if entry:
|
||||
if entry[0] == irc.name: # We own this channel.
|
||||
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 "
|
||||
"this relay entirely (it was created on the current "
|
||||
"network).")
|
||||
@ -1162,9 +1189,9 @@ def delink(irc, source, args):
|
||||
else:
|
||||
removeChannel(irc, channel)
|
||||
db[entry]['links'].remove((irc.name, channel))
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
else:
|
||||
irc.msg(source, 'Error: No such relay %r.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: No such relay %r.' % channel)
|
||||
|
||||
@utils.add_cmd
|
||||
def linked(irc, source, args):
|
||||
@ -1206,37 +1233,37 @@ def linkacl(irc, source, args):
|
||||
cmd = args[0].lower()
|
||||
channel = utils.toLower(irc, args[1])
|
||||
except IndexError:
|
||||
irc.msg(source, missingargs)
|
||||
irc.msg(irc.called_by, missingargs)
|
||||
return
|
||||
if not utils.isChannel(channel):
|
||||
irc.msg(source, 'Error: Invalid channel %r.' % channel)
|
||||
irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel)
|
||||
return
|
||||
relay = getRelay((irc.name, channel))
|
||||
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
|
||||
if cmd == 'list':
|
||||
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
|
||||
|
||||
try:
|
||||
remotenet = args[2]
|
||||
except IndexError:
|
||||
irc.msg(source, missingargs)
|
||||
irc.msg(irc.called_by, missingargs)
|
||||
return
|
||||
if cmd == 'deny':
|
||||
db[relay]['blocked_nets'].add(remotenet)
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
elif cmd == 'allow':
|
||||
try:
|
||||
db[relay]['blocked_nets'].remove(remotenet)
|
||||
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:
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
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
|
||||
def showuser(irc, source, args):
|
||||
@ -1281,7 +1308,7 @@ def save(irc, source, args):
|
||||
Saves the relay database to disk."""
|
||||
utils.checkAuthenticated(irc, source)
|
||||
exportDB()
|
||||
irc.msg(source, 'Done.')
|
||||
irc.msg(irc.called_by, 'Done.')
|
||||
|
||||
@utils.add_cmd
|
||||
def claim(irc, source, args):
|
||||
@ -1294,25 +1321,25 @@ def claim(irc, source, args):
|
||||
try:
|
||||
channel = utils.toLower(irc, args[0])
|
||||
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
|
||||
|
||||
# We override getRelay() here to limit the search to the current network.
|
||||
relay = (irc.name, channel)
|
||||
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
|
||||
claimed = db[relay]["claim"]
|
||||
try:
|
||||
nets = args[1].strip()
|
||||
except IndexError: # No networks given.
|
||||
irc.msg(source, 'Channel \x02%s\x02 is claimed by: %s' %
|
||||
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||
irc.msg(irc.called_by, 'Channel \x02%s\x02 is claimed by: %s' %
|
||||
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||
else:
|
||||
if nets == '-' or not nets:
|
||||
claimed = set()
|
||||
else:
|
||||
claimed = set(nets.split(','))
|
||||
db[relay]["claim"] = claimed
|
||||
irc.msg(source, 'CLAIM for channel \x02%s\x02 set to: %s' %
|
||||
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||
irc.msg(irc.called_by, 'CLAIM for channel \x02%s\x02 set to: %s' %
|
||||
(channel, ', '.join(claimed) or '\x1D(none)\x1D'))
|
||||
|
@ -15,7 +15,7 @@ from ts6_common import TS6BaseProtocol
|
||||
class InspIRCdProtocol(TS6BaseProtocol):
|
||||
def __init__(self, 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'
|
||||
|
||||
# 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.')
|
||||
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):
|
||||
"""Sends a kill from a PyLink server."""
|
||||
if not utils.isInternalServer(self.irc, numeric):
|
||||
raise LookupError('No such PyLink PseudoServer exists.')
|
||||
self._send(numeric, 'KILL %s :%s' % (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.
|
||||
self._sendKill(numeric, target, reason)
|
||||
|
||||
def killClient(self, numeric, target, reason):
|
||||
"""Sends a kill from a PyLink client."""
|
||||
if not utils.isInternalClient(self.irc, numeric):
|
||||
raise LookupError('No such PyLink PseudoClient exists.')
|
||||
self._send(numeric, 'KILL %s :%s' % (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.
|
||||
self._sendKill(numeric, target, reason)
|
||||
|
||||
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):
|
||||
raise LookupError('No such PyLink PseudoServer exists.')
|
||||
ts = int(time.time())
|
||||
@ -251,7 +255,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
if not (target is None or source is None):
|
||||
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 "
|
||||
"protocol module. WHOIS requests are handled "
|
||||
"locally by InspIRCd servers, so there is no "
|
||||
@ -266,7 +270,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
self._send(source, 'AWAY')
|
||||
|
||||
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
|
||||
uplink = uplink or self.irc.sid
|
||||
name = name.lower()
|
||||
|
@ -183,7 +183,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
assert target in self.irc.users, "Unknown target %r for killServer!" % target
|
||||
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
|
||||
removeClient(self.irc, target)
|
||||
self.removeClient(target)
|
||||
|
||||
def killClient(self, numeric, target, reason):
|
||||
"""Sends a kill from a PyLink client."""
|
||||
@ -191,10 +191,10 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
raise LookupError('No such PyLink PseudoClient exists.')
|
||||
assert target in self.irc.users, "Unknown target %r for killClient!" % target
|
||||
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
|
||||
removeClient(self.irc, target)
|
||||
self.removeClient(target)
|
||||
|
||||
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):
|
||||
raise LookupError('No such PyLink PseudoServer exists.')
|
||||
# TB
|
||||
@ -259,7 +259,12 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self._send(source, 'AWAY')
|
||||
|
||||
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
|
||||
uplink = uplink or self.irc.sid
|
||||
name = name.lower()
|
||||
|
@ -12,29 +12,6 @@ class TS6BaseProtocol(Protocol):
|
||||
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
|
||||
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):
|
||||
"""Similar to parseArgs(), but stripping leading colons from the first argument
|
||||
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.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):
|
||||
"""Sends a part from a PyLink client."""
|
||||
channel = utils.toLower(self.irc, channel)
|
||||
|
25
pylink
25
pylink
@ -12,14 +12,11 @@ world.testing = False
|
||||
import conf
|
||||
from log import log
|
||||
import classes
|
||||
import utils
|
||||
import coreplugin
|
||||
|
||||
if __name__ == '__main__':
|
||||
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.
|
||||
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
|
||||
# that happen before the connection phase.
|
||||
world.plugins.append(coreplugin)
|
||||
to_load = conf.conf['plugins']
|
||||
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
|
||||
# Here, we override the module lookup and import the plugins
|
||||
# dynamically depending on which were configured.
|
||||
for plugin in to_load:
|
||||
try:
|
||||
moduleinfo = imp.find_module(plugin, plugins_folder)
|
||||
pl = imp.load_source(plugin, moduleinfo[1])
|
||||
world.plugins.append(pl)
|
||||
world.plugins[plugin] = pl = utils.loadModuleFromFolder(plugin, world.plugins_folder)
|
||||
except ImportError as e:
|
||||
if str(e) == ('No module named %r' % plugin):
|
||||
log.error('Failed to load plugin %r: The plugin could not be found.', plugin)
|
||||
@ -48,17 +41,7 @@ if __name__ == '__main__':
|
||||
pl.main()
|
||||
|
||||
for network in conf.conf['servers']:
|
||||
protoname = conf.conf['servers'][network]['protocol']
|
||||
try:
|
||||
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)
|
||||
proto = utils.getProtoModule(conf.conf['servers'][network]['protocol'])
|
||||
world.networkobjects[network] = classes.Irc(network, proto, conf.conf)
|
||||
world.started.set()
|
||||
log.info("loaded plugins: %s", world.plugins)
|
||||
|
@ -10,7 +10,7 @@ cd "$WRAPPER_DIR"
|
||||
|
||||
if [[ ! -z "$(which cpulimit)" ]]; then
|
||||
# -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 "To kill the process manually, run ./kill.sh"
|
||||
else
|
||||
|
@ -26,16 +26,16 @@ class TestUtils(unittest.TestCase):
|
||||
utils.add_cmd(dummyf)
|
||||
utils.add_cmd(dummyf, 'TEST')
|
||||
# All command names should be automatically lowercased.
|
||||
self.assertIn('dummyf', world.bot_commands)
|
||||
self.assertIn('test', world.bot_commands)
|
||||
self.assertNotIn('TEST', world.bot_commands)
|
||||
self.assertIn('dummyf', world.commands)
|
||||
self.assertIn('test', world.commands)
|
||||
self.assertNotIn('TEST', world.commands)
|
||||
|
||||
def test_add_hook(self):
|
||||
utils.add_hook(dummyf, 'join')
|
||||
self.assertIn('JOIN', world.command_hooks)
|
||||
self.assertIn('JOIN', world.hooks)
|
||||
# Command names stored in uppercase.
|
||||
self.assertNotIn('join', world.command_hooks)
|
||||
self.assertIn(dummyf, world.command_hooks['JOIN'])
|
||||
self.assertNotIn('join', world.hooks)
|
||||
self.assertIn(dummyf, world.hooks['JOIN'])
|
||||
|
||||
def testIsNick(self):
|
||||
self.assertFalse(utils.isNick('abcdefgh', nicklen=3))
|
||||
|
23
utils.py
23
utils.py
@ -1,6 +1,7 @@
|
||||
import string
|
||||
import re
|
||||
import inspect
|
||||
import imp
|
||||
|
||||
from log import log
|
||||
import world
|
||||
@ -106,12 +107,12 @@ def add_cmd(func, name=None):
|
||||
if name is None:
|
||||
name = func.__name__
|
||||
name = name.lower()
|
||||
world.bot_commands[name].append(func)
|
||||
world.commands[name].append(func)
|
||||
|
||||
def add_hook(func, command):
|
||||
"""Add a hook <func> for command <command>."""
|
||||
command = command.upper()
|
||||
world.command_hooks[command].append(func)
|
||||
world.hooks[command].append(func)
|
||||
|
||||
def toLower(irc, text):
|
||||
"""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+$')
|
||||
def isHostmask(text):
|
||||
"""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):
|
||||
"""Parses a modestring list into a list of (mode, argument) tuples.
|
||||
@ -176,10 +178,12 @@ def parseModes(irc, target, args):
|
||||
args = args[1:]
|
||||
if usermodes:
|
||||
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
|
||||
oldmodes = irc.users[target].modes
|
||||
else:
|
||||
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
|
||||
oldmodes = irc.channels[target].modes
|
||||
res = []
|
||||
@ -216,7 +220,8 @@ def parseModes(irc, target, args):
|
||||
arg = nickToUid(irc, arg) or arg
|
||||
if arg not in irc.users: # Target doesn't exist, skip it.
|
||||
log.debug('(%s) Skipping setting mode "%s %s"; the '
|
||||
'target doesn\'t seem to exist!')
|
||||
'target doesn\'t seem to exist!', self.name,
|
||||
mode, arg)
|
||||
continue
|
||||
elif prefix == '+' and mode in supported_modes['*C']:
|
||||
# Only has parameter when setting.
|
||||
@ -494,3 +499,13 @@ def getHostmask(irc, user):
|
||||
except AttributeError:
|
||||
host = '<unknown 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)
|
||||
|
12
world.py
12
world.py
@ -3,21 +3,25 @@
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
# Global variable to indicate whether we're being ran directly, or imported
|
||||
# for a testcase.
|
||||
testing = True
|
||||
|
||||
global bot_commands, command_hooks
|
||||
global commands, hooks
|
||||
# This should be a mapping of command names to functions
|
||||
bot_commands = defaultdict(list)
|
||||
command_hooks = defaultdict(list)
|
||||
commands = defaultdict(list)
|
||||
hooks = defaultdict(list)
|
||||
networkobjects = {}
|
||||
schedulers = {}
|
||||
plugins = []
|
||||
plugins = {}
|
||||
whois_handlers = []
|
||||
started = threading.Event()
|
||||
|
||||
plugins_folder = [os.path.join(os.getcwd(), 'plugins')]
|
||||
protocols_folder = [os.path.join(os.getcwd(), 'protocols')]
|
||||
|
||||
version = "<unknown>"
|
||||
source = "https://github.com/GLolol/PyLink" # CHANGE THIS IF YOU'RE FORKING!!
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user