3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 01:09:22 +01:00

Merge branch 'devel' into wip/unrealircd

This commit is contained in:
James Lu 2015-09-10 18:27:46 -07:00
commit da1b101bc4
26 changed files with 2200 additions and 1522 deletions

View File

@ -69,7 +69,8 @@ class Irc():
self.serverdata = conf['servers'][netname]
self.sid = self.serverdata["sid"]
self.botdata = conf['bot']
self.proto = proto
self.protoname = proto.__name__
self.proto = proto.Class(self)
self.pingfreq = self.serverdata.get('pingfreq') or 30
self.pingtimeout = self.pingfreq * 2
@ -146,7 +147,7 @@ class Irc():
sha1fp)
if checks_ok:
self.proto.connect(self)
self.proto.connect()
self.spawnMain()
log.info('(%s) Starting ping schedulers....', self.name)
self.schedulePing()
@ -169,6 +170,15 @@ class Irc():
else:
return
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)
else:
self.proto.messageClient(source, target, text)
def _disconnect(self):
log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time())
self.connected.clear()
@ -204,7 +214,7 @@ class Irc():
log.debug("(%s) <- %s", self.name, line)
hook_args = None
try:
hook_args = self.proto.handle_events(self, line)
hook_args = self.proto.handle_events(line)
except Exception:
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
return
@ -253,7 +263,7 @@ class Irc():
log.debug("(%s) Dropping message %r; network isn't connected!", self.name, stripped_data)
def schedulePing(self):
self.proto.pingServer(self)
self.proto.pingServer()
self.pingTimer = threading.Timer(self.pingfreq, self.schedulePing)
self.pingTimer.daemon = True
self.pingTimer.start()
@ -265,9 +275,9 @@ class Irc():
host = self.serverdata["hostname"]
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
olduserobj = self.pseudoclient
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)})
self.pseudoclient = self.proto.spawnClient(nick, ident, host, modes={("+o", None)})
for chan in self.serverdata['channels']:
self.proto.joinClient(self, self.pseudoclient.uid, chan)
self.proto.joinClient(self.pseudoclient.uid, chan)
# PyLink internal hook called when spawnMain is called and the
# contents of Irc().pseudoclient change.
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
@ -343,7 +353,7 @@ class FakeIRC(Irc):
def run(self, data):
"""Queues a message to the fake IRC server."""
log.debug('<- ' + data)
hook_args = self.proto.handle_events(self, data)
hook_args = self.proto.handle_events(data)
if hook_args is not None:
self.hookmsgs.append(hook_args)
self.callHooks(hook_args)
@ -378,29 +388,29 @@ class FakeIRC(Irc):
self.hookmsgs = []
return hookmsgs
class FakeProto():
"""Dummy protocol module for testing purposes."""
def __init__(self):
self.hook_map = {}
class Protocol():
# TODO: Future state-keeping things will go here
def __init__(self, irc):
self.irc = irc
self.casemapping = 'rfc1459'
self.__name__ = 'FakeProto'
self.hook_map = {}
@staticmethod
def handle_events(irc, data):
class FakeProto(Protocol):
"""Dummy protocol module for testing purposes."""
def handle_events(self, data):
pass
@staticmethod
def connect(irc):
def connect(self):
pass
@staticmethod
def spawnClient(irc, nick, *args, **kwargs):
def spawnClient(self, nick, *args, **kwargs):
uid = randint(1, 10000000000)
ts = int(time.time())
irc.users[uid] = user = IrcUser(nick, ts, uid)
self.irc.users[uid] = user = IrcUser(nick, ts, uid)
return user
@staticmethod
def joinClient(irc, client, channel):
irc.channels[channel].users.add(client)
irc.users[client].channels.add(channel)
def joinClient(self, client, channel):
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
FakeProto.Class = FakeProto

View File

@ -15,7 +15,7 @@ def handle_kick(irc, source, command, args):
kicked = args['target']
channel = args['channel']
if kicked == irc.pseudoclient.uid:
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
irc.proto.joinClient(irc.pseudoclient.uid, channel)
utils.add_hook(handle_kick, 'KICK')
# Handle commands sent to the PyLink client (PRIVMSG)
@ -26,17 +26,17 @@ def handle_commands(irc, source, command, args):
cmd = cmd_args[0].lower()
cmd_args = cmd_args[1:]
if cmd not in world.bot_commands:
utils.msg(irc, source, 'Error: Unknown command %r.' % cmd)
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:
utils.msg(irc, source, 'Error: You are not authorized to perform this operation.')
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)
utils.msg(irc, source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
irc.msg(source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
utils.add_hook(handle_commands, 'PRIVMSG')
# Handle WHOIS queries, for IRCds that send them across servers (charybdis, UnrealIRCd; NOT InspIRCd).
@ -51,7 +51,7 @@ def handle_whois(irc, source, command, args):
sourceisOper = ('o', None) in irc.users[source].modes
# https://www.alien.net.au/irc/irc2numerics.html
# 311: sends nick!user@host information
f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
f(server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
# 319: RPL_WHOISCHANNELS, shows channel list
public_chans = []
for chan in user.channels:
@ -69,9 +69,9 @@ def handle_whois(irc, source, command, args):
chan = prefixchar + chan
public_chans.append(chan)
if public_chans:
f(irc, server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
f(server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
# 312: sends the server the target is on, and its server description.
f(irc, server, 312, source, "%s %s :%s" % (nick, irc.serverdata['hostname'],
f(server, 312, source, "%s %s :%s" % (nick, irc.servers[server].name,
irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
# 313: sends a string denoting the target's operator privilege,
# only if they have umode +o.
@ -82,15 +82,15 @@ def handle_whois(irc, source, command, args):
opertype = "IRC Operator"
# Let's be gramatically correct.
n = 'n' if opertype[0].lower() in 'aeiou' else ''
f(irc, server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
f(server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
# Only show this to opers!
if sourceisOper:
f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
f(server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
# 317: shows idle and signon time. However, we don't track the user's real
# idle time, so we simply return 0.
# <- 317 GL GL 15 1437632859 :seconds idle, signon time
f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
f(server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
for func in world.whois_handlers:
# Iterate over custom plugin WHOIS handlers. They return a tuple
# or list with two arguments: the numeric, and the text to send.
@ -98,11 +98,11 @@ def handle_whois(irc, source, command, args):
res = func(irc, target)
if res:
num, text = res
f(irc, server, num, source, text)
f(server, num, source, text)
except Exception as e:
# Again, we wouldn't want this to crash our service, in case
# something goes wrong!
log.exception('(%s) Error caught in WHOIS handler: %s', irc.name, e)
# 318: End of WHOIS.
f(irc, server, 318, source, "%s :End of /WHOIS list" % nick)
f(server, 318, source, "%s :End of /WHOIS list" % nick)
utils.add_hook(handle_whois, 'WHOIS')

23
docs/technical/README.md Normal file
View File

@ -0,0 +1,23 @@
## PyLink Developer Documentation
Please note that as PyLink is still in its development phase, the API is subject to change.
Any documentation here is provided for reference only.
It is also really incomplete (contributors welcome!)
### Introduction
PyLink is an a modular, plugin-based IRC PseudoService framework. It uses swappable protocol modules and a hook-based system for calling plugins, allowing them to function regardless of the IRCd used.
<img src="core-structure.png" width="50%" height="50%">
### Contents
- [Writing plugins for PyLink](writing-plugins.md)
- [PyLink protocol module specification](pmodule-spec.md)
#### Future topics (not yet available)
- [PyLink hooks reference](hooks-reference.md)
- [Writing tests for PyLink modules](writing-tests.md)
- [Using PyLink's utils module](using-utils.md)

View File

@ -0,0 +1,27 @@
digraph G {
ratio = 1.3;
subgraph cluster_core {
label="PyLink Application Structure";
style="filled";
node [style="filled",color="white"];
color="lightblue";
subgraph cluster_testsuite {
label="Test Suite API";
style="filled";
node [style="filled",color="white"];
color=moccasin;
"Dummy protocol\nmodule" -> "Dummy IRC\nobject (FakeIRC)" [color=darkgreen];
"Dummy IRC\nobject (FakeIRC)" -> "Dummy protocol\nmodule" [color=darkgreen];
}
"IRC object" -> "Protocol module" -> "PyLink hooks" -> Plugins;
"Main program" -> "IRC object" [color=indigo] [label="Spawns 1/net"] [fontcolor=indigo];
"Main program" -> "Dummy IRC\nobject (FakeIRC)" [color=darkgreen] [label="(test suite runner)"] [fontcolor=darkgreen];
}
"Protocol module" -> "Remote IRCd" -> "Protocol module";
Plugins -> "Protocol module" [label="Communicates \nvia*Client/*Server\nfunctions"] [color=navyblue] [fontcolor=navyblue];
Plugins -> "Main program" [label="Registers commands\n& hook handlers"] [color=brown] [fontcolor=brown];
"Dummy protocol\nmodule" -> "PyLink hooks" [color=darkgreen];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,397 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: module inspircd</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body bgcolor="#f0f0f8">
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong>inspircd</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/protocols/inspircd.py">/home/gl/pylink/protocols/inspircd.py</a></font></td></tr></table>
<p></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Modules</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="hashlib.html">hashlib</a><br>
<a href="os.html">os</a><br>
<a href="re.html">re</a><br>
</td><td width="25%" valign=top><a href="socket.html">socket</a><br>
<a href="ssl.html">ssl</a><br>
<a href="sys.html">sys</a><br>
</td><td width="25%" valign=top><a href="threading.html">threading</a><br>
<a href="time.html">time</a><br>
<a href="utils.html">utils</a><br>
</td><td width="25%" valign=top><a href="world.html">world</a><br>
</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a>(<a href="classes.html#Protocol">classes.Protocol</a>)
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="inspircd.html#InspIRCdProtocol">InspIRCdProtocol</a>
</font></dt></dl>
</dd>
</dl>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><strong>Class</strong> = <a name="Class">class InspIRCdProtocol</a>(<a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a>)</font></td></tr>
<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="inspircd.html#InspIRCdProtocol">InspIRCdProtocol</a></dd>
<dd><a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a></dd>
<dd><a href="classes.html#Protocol">classes.Protocol</a></dd>
<dd><a href="builtins.html#object">builtins.object</a></dd>
</dl>
<hr>
Methods defined here:<br>
<dl><dt><a name="InspIRCdProtocol-__init__"><strong>__init__</strong></a>(self, irc)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-awayClient"><strong>awayClient</strong></a>(self, source, text)</dt><dd><tt>Sends&nbsp;an&nbsp;AWAY&nbsp;message&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;text&gt;&nbsp;can&nbsp;be&nbsp;an&nbsp;empty&nbsp;string<br>
to&nbsp;unset&nbsp;AWAY&nbsp;status.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-connect"><strong>connect</strong></a>(self)</dt><dd><tt>Initializes&nbsp;a&nbsp;connection&nbsp;to&nbsp;a&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_away"><strong>handle_away</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;AWAY&nbsp;messages.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_encap"><strong>handle_encap</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;encapsulated&nbsp;commands&nbsp;(ENCAP).&nbsp;Hook&nbsp;arguments<br>
returned&nbsp;by&nbsp;this&nbsp;should&nbsp;have&nbsp;a&nbsp;parse_as&nbsp;field,&nbsp;that&nbsp;sets&nbsp;the&nbsp;correct<br>
hook&nbsp;name&nbsp;for&nbsp;the&nbsp;message.<br>
&nbsp;<br>
For&nbsp;InspIRCd,&nbsp;the&nbsp;only&nbsp;ENCAP&nbsp;command&nbsp;we&nbsp;handle&nbsp;right&nbsp;now&nbsp;is&nbsp;KNOCK.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.&nbsp;This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to<br>
the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions&nbsp;elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also<br>
handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fident"><strong>handle_fident</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FIDENT,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;ident&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fjoin"><strong>handle_fjoin</strong></a>(self, servernumeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;FJOIN&nbsp;commands&nbsp;(InspIRCd&nbsp;equivalent&nbsp;of&nbsp;JOIN/SJOIN).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fmode"><strong>handle_fmode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;the&nbsp;FMODE&nbsp;command,&nbsp;used&nbsp;for&nbsp;channel&nbsp;mode&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fname"><strong>handle_fname</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FNAME,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;real&nbsp;name/gecos&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_ftopic"><strong>handle_ftopic</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;FTOPIC&nbsp;(sets&nbsp;topic&nbsp;on&nbsp;burst).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_idle"><strong>handle_idle</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;the&nbsp;IDLE&nbsp;command,&nbsp;sent&nbsp;between&nbsp;servers&nbsp;in&nbsp;remote&nbsp;WHOIS&nbsp;queries.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br>
&nbsp;<br>
This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br>
opertype&nbsp;of&nbsp;the&nbsp;client,&nbsp;and&nbsp;assumes&nbsp;setting&nbsp;user&nbsp;mode&nbsp;+o&nbsp;on&nbsp;the&nbsp;caller.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.&nbsp;This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether<br>
the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply<br>
to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_uid"><strong>handle_uid</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;UID&nbsp;commands&nbsp;(user&nbsp;introduction).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;an&nbsp;internal&nbsp;spawned&nbsp;client&nbsp;&lt;client&gt;&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killServer"><strong>killServer</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, text)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br>
automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-sjoinServer"><strong>sjoinServer</strong></a>(self, server, channel, users, ts=None)</dt><dd><tt>Sends&nbsp;an&nbsp;SJOIN&nbsp;for&nbsp;a&nbsp;group&nbsp;of&nbsp;users&nbsp;to&nbsp;a&nbsp;channel.<br>
&nbsp;<br>
The&nbsp;sender&nbsp;should&nbsp;always&nbsp;be&nbsp;a&nbsp;Server&nbsp;ID&nbsp;(SID).&nbsp;TS&nbsp;is&nbsp;optional,&nbsp;and&nbsp;defaults<br>
to&nbsp;the&nbsp;one&nbsp;we've&nbsp;stored&nbsp;in&nbsp;the&nbsp;channel&nbsp;state&nbsp;if&nbsp;not&nbsp;given.<br>
&lt;users&gt;&nbsp;is&nbsp;a&nbsp;list&nbsp;of&nbsp;(prefix&nbsp;mode,&nbsp;UID)&nbsp;pairs:<br>
&nbsp;<br>
Example&nbsp;uses:<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#Class-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br>
&nbsp;<br>
Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br>
up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;burst&nbsp;topic&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usally&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Methods inherited from <a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a>:<br>
<dl><dt><a name="InspIRCdProtocol-handle_error"><strong>handle_error</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;ERROR&nbsp;messages&nbsp;-&nbsp;these&nbsp;mean&nbsp;that&nbsp;our&nbsp;uplink&nbsp;has&nbsp;disconnected&nbsp;us!</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_kick"><strong>handle_kick</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KICKs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.&nbsp;For&nbsp;channel&nbsp;mode&nbsp;changes,<br>
TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_part"><strong>handle_part</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PART&nbsp;commands.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUITs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_squit"><strong>handle_squit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SQUITs&nbsp;(netsplits).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_topic"><strong>handle_topic</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;TOPIC&nbsp;changes&nbsp;from&nbsp;clients.&nbsp;For&nbsp;topic&nbsp;bursts,<br>
TB&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FTOPIC&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-kickClient"><strong>kickClient</strong></a>(self, numeric, channel, target, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;kick&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-kickServer"><strong>kickServer</strong></a>(self, numeric, channel, target, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;kick&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-messageClient"><strong>messageClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;PRIVMSG&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-nickClient"><strong>nickClient</strong></a>(self, numeric, newnick)</dt><dd><tt>Changes&nbsp;the&nbsp;nick&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#Class-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br>
of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-partClient"><strong>partClient</strong></a>(self, client, channel, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;part&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table> <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="InspIRCdProtocol">class <strong>InspIRCdProtocol</strong></a>(<a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a>)</font></td></tr>
<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="inspircd.html#InspIRCdProtocol">InspIRCdProtocol</a></dd>
<dd><a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a></dd>
<dd><a href="classes.html#Protocol">classes.Protocol</a></dd>
<dd><a href="builtins.html#object">builtins.object</a></dd>
</dl>
<hr>
Methods defined here:<br>
<dl><dt><a name="InspIRCdProtocol-__init__"><strong>__init__</strong></a>(self, irc)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-awayClient"><strong>awayClient</strong></a>(self, source, text)</dt><dd><tt>Sends&nbsp;an&nbsp;AWAY&nbsp;message&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;text&gt;&nbsp;can&nbsp;be&nbsp;an&nbsp;empty&nbsp;string<br>
to&nbsp;unset&nbsp;AWAY&nbsp;status.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-connect"><strong>connect</strong></a>(self)</dt><dd><tt>Initializes&nbsp;a&nbsp;connection&nbsp;to&nbsp;a&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_away"><strong>handle_away</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;AWAY&nbsp;messages.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_encap"><strong>handle_encap</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;encapsulated&nbsp;commands&nbsp;(ENCAP).&nbsp;Hook&nbsp;arguments<br>
returned&nbsp;by&nbsp;this&nbsp;should&nbsp;have&nbsp;a&nbsp;parse_as&nbsp;field,&nbsp;that&nbsp;sets&nbsp;the&nbsp;correct<br>
hook&nbsp;name&nbsp;for&nbsp;the&nbsp;message.<br>
&nbsp;<br>
For&nbsp;InspIRCd,&nbsp;the&nbsp;only&nbsp;ENCAP&nbsp;command&nbsp;we&nbsp;handle&nbsp;right&nbsp;now&nbsp;is&nbsp;KNOCK.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_endburst"><strong>handle_endburst</strong></a>(self, numeric, command, args)</dt><dd><tt>ENDBURST&nbsp;handler;&nbsp;sends&nbsp;a&nbsp;hook&nbsp;with&nbsp;empty&nbsp;contents.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_events"><strong>handle_events</strong></a>(self, data)</dt><dd><tt>Event&nbsp;handler&nbsp;for&nbsp;the&nbsp;InspIRCd&nbsp;protocol.&nbsp;This&nbsp;passes&nbsp;most&nbsp;commands&nbsp;to<br>
the&nbsp;various&nbsp;handle_ABCD()&nbsp;functions&nbsp;elsewhere&nbsp;in&nbsp;this&nbsp;module,&nbsp;but&nbsp;also<br>
handles&nbsp;commands&nbsp;sent&nbsp;in&nbsp;the&nbsp;initial&nbsp;server&nbsp;linking&nbsp;phase.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fhost"><strong>handle_fhost</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FHOST,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;hostname&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fident"><strong>handle_fident</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FIDENT,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;ident&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fjoin"><strong>handle_fjoin</strong></a>(self, servernumeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;FJOIN&nbsp;commands&nbsp;(InspIRCd&nbsp;equivalent&nbsp;of&nbsp;JOIN/SJOIN).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fmode"><strong>handle_fmode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;the&nbsp;FMODE&nbsp;command,&nbsp;used&nbsp;for&nbsp;channel&nbsp;mode&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_fname"><strong>handle_fname</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;FNAME,&nbsp;used&nbsp;for&nbsp;denoting&nbsp;real&nbsp;name/gecos&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_ftopic"><strong>handle_ftopic</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;FTOPIC&nbsp;(sets&nbsp;topic&nbsp;on&nbsp;burst).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_idle"><strong>handle_idle</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;the&nbsp;IDLE&nbsp;command,&nbsp;sent&nbsp;between&nbsp;servers&nbsp;in&nbsp;remote&nbsp;WHOIS&nbsp;queries.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_invite"><strong>handle_invite</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;INVITEs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_opertype"><strong>handle_opertype</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;OPERTYPE,&nbsp;which&nbsp;is&nbsp;used&nbsp;to&nbsp;denote&nbsp;an&nbsp;oper&nbsp;up.<br>
&nbsp;<br>
This&nbsp;calls&nbsp;the&nbsp;internal&nbsp;hook&nbsp;PYLINK_CLIENT_OPERED,&nbsp;sets&nbsp;the&nbsp;internal<br>
opertype&nbsp;of&nbsp;the&nbsp;client,&nbsp;and&nbsp;assumes&nbsp;setting&nbsp;user&nbsp;mode&nbsp;+o&nbsp;on&nbsp;the&nbsp;caller.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_ping"><strong>handle_ping</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PING&nbsp;commands,&nbsp;so&nbsp;we&nbsp;don't&nbsp;time&nbsp;out.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_pong"><strong>handle_pong</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PONG&nbsp;commands.&nbsp;This&nbsp;is&nbsp;used&nbsp;to&nbsp;keep&nbsp;track&nbsp;of&nbsp;whether<br>
the&nbsp;uplink&nbsp;is&nbsp;alive&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals&nbsp;-&nbsp;a&nbsp;server&nbsp;that&nbsp;fails&nbsp;to&nbsp;reply<br>
to&nbsp;our&nbsp;PINGs&nbsp;eventually&nbsp;times&nbsp;out&nbsp;and&nbsp;is&nbsp;disconnected.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_server"><strong>handle_server</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SERVER&nbsp;commands&nbsp;(introduction&nbsp;of&nbsp;servers).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_uid"><strong>handle_uid</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;UID&nbsp;commands&nbsp;(user&nbsp;introduction).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-inviteClient"><strong>inviteClient</strong></a>(self, numeric, target, channel)</dt><dd><tt>Sends&nbsp;an&nbsp;INVITE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client..</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-joinClient"><strong>joinClient</strong></a>(self, client, channel)</dt><dd><tt>Joins&nbsp;an&nbsp;internal&nbsp;spawned&nbsp;client&nbsp;&lt;client&gt;&nbsp;to&nbsp;a&nbsp;channel.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killClient"><strong>killClient</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-killServer"><strong>killServer</strong></a>(self, numeric, target, reason)</dt><dd><tt>Sends&nbsp;a&nbsp;kill&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-knockClient"><strong>knockClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;KNOCK&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeClient"><strong>modeClient</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.&nbsp;&lt;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-modeServer"><strong>modeServer</strong></a>(self, numeric, target, modes, ts=None)</dt><dd><tt>Sends&nbsp;modes&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;&lt;list&nbsp;of&nbsp;modes&gt;&nbsp;should&nbsp;be<br>
a&nbsp;list&nbsp;of&nbsp;(mode,&nbsp;arg)&nbsp;tuples,&nbsp;i.e.&nbsp;the&nbsp;format&nbsp;of&nbsp;utils.parseModes()&nbsp;output.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-numericServer"><strong>numericServer</strong></a>(self, source, numeric, text)</dt></dl>
<dl><dt><a name="InspIRCdProtocol-pingServer"><strong>pingServer</strong></a>(self, source=None, target=None)</dt><dd><tt>Sends&nbsp;a&nbsp;PING&nbsp;to&nbsp;a&nbsp;target&nbsp;server.&nbsp;Periodic&nbsp;PINGs&nbsp;are&nbsp;sent&nbsp;to&nbsp;our&nbsp;uplink<br>
automatically&nbsp;by&nbsp;the&nbsp;Irc()&nbsp;internals;&nbsp;plugins&nbsp;shouldn't&nbsp;have&nbsp;to&nbsp;use&nbsp;this.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-sjoinServer"><strong>sjoinServer</strong></a>(self, server, channel, users, ts=None)</dt><dd><tt>Sends&nbsp;an&nbsp;SJOIN&nbsp;for&nbsp;a&nbsp;group&nbsp;of&nbsp;users&nbsp;to&nbsp;a&nbsp;channel.<br>
&nbsp;<br>
The&nbsp;sender&nbsp;should&nbsp;always&nbsp;be&nbsp;a&nbsp;Server&nbsp;ID&nbsp;(SID).&nbsp;TS&nbsp;is&nbsp;optional,&nbsp;and&nbsp;defaults<br>
to&nbsp;the&nbsp;one&nbsp;we've&nbsp;stored&nbsp;in&nbsp;the&nbsp;channel&nbsp;state&nbsp;if&nbsp;not&nbsp;given.<br>
&lt;users&gt;&nbsp;is&nbsp;a&nbsp;list&nbsp;of&nbsp;(prefix&nbsp;mode,&nbsp;UID)&nbsp;pairs:<br>
&nbsp;<br>
Example&nbsp;uses:<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>('100',&nbsp;'#test',&nbsp;[('',&nbsp;'100AAABBC'),&nbsp;('qo',&nbsp;100AAABBB'),&nbsp;('h',&nbsp;'100AAADDD')])<br>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#InspIRCdProtocol-sjoinServer">sjoinServer</a>(self.<strong>irc</strong>.sid,&nbsp;'#test',&nbsp;[('o',&nbsp;self.<strong>irc</strong>.pseudoclient.uid)])</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnClient"><strong>spawnClient</strong></a>(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;client&nbsp;with&nbsp;nick&nbsp;&lt;nick&gt;&nbsp;on&nbsp;the&nbsp;given&nbsp;IRC&nbsp;connection.<br>
&nbsp;<br>
Note:&nbsp;No&nbsp;nick&nbsp;collision&nbsp;/&nbsp;valid&nbsp;nickname&nbsp;checks&nbsp;are&nbsp;done&nbsp;here;&nbsp;it&nbsp;is<br>
up&nbsp;to&nbsp;plugins&nbsp;to&nbsp;make&nbsp;sure&nbsp;they&nbsp;don't&nbsp;introduce&nbsp;anything&nbsp;invalid.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-spawnServer"><strong>spawnServer</strong></a>(self, name, sid=None, uplink=None, desc=None)</dt><dd><tt>Spawns&nbsp;a&nbsp;server&nbsp;off&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-squitServer"><strong>squitServer</strong></a>(self, source, target, text='No reason given')</dt><dd><tt>SQUITs&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicServer"><strong>topicServer</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;burst&nbsp;topic&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.&nbsp;This&nbsp;is&nbsp;usally&nbsp;used&nbsp;on&nbsp;burst.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-updateClient"><strong>updateClient</strong></a>(self, numeric, field, text)</dt><dd><tt>Updates&nbsp;the&nbsp;ident,&nbsp;host,&nbsp;or&nbsp;realname&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Methods inherited from <a href="ts6_common.html#TS6BaseProtocol">ts6_common.TS6BaseProtocol</a>:<br>
<dl><dt><a name="InspIRCdProtocol-handle_error"><strong>handle_error</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;ERROR&nbsp;messages&nbsp;-&nbsp;these&nbsp;mean&nbsp;that&nbsp;our&nbsp;uplink&nbsp;has&nbsp;disconnected&nbsp;us!</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_kick"><strong>handle_kick</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KICKs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_kill"><strong>handle_kill</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;KILLs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_mode"><strong>handle_mode</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;user&nbsp;mode&nbsp;changes.&nbsp;For&nbsp;channel&nbsp;mode&nbsp;changes,<br>
TMODE&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FMODE&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_nick"><strong>handle_nick</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;NICK&nbsp;changes.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_notice"><strong>handle_notice</strong></a> = handle_privmsg(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_part"><strong>handle_part</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PART&nbsp;commands.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_privmsg"><strong>handle_privmsg</strong></a>(self, source, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;PRIVMSG/NOTICE.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_quit"><strong>handle_quit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;QUITs.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_save"><strong>handle_save</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SAVE&nbsp;messages,&nbsp;used&nbsp;to&nbsp;handle&nbsp;nick&nbsp;collisions.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_squit"><strong>handle_squit</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;SQUITs&nbsp;(netsplits).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-handle_topic"><strong>handle_topic</strong></a>(self, numeric, command, args)</dt><dd><tt>Handles&nbsp;incoming&nbsp;TOPIC&nbsp;changes&nbsp;from&nbsp;clients.&nbsp;For&nbsp;topic&nbsp;bursts,<br>
TB&nbsp;(TS6/charybdis)&nbsp;and&nbsp;FTOPIC&nbsp;(InspIRCd)&nbsp;are&nbsp;used&nbsp;instead.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-kickClient"><strong>kickClient</strong></a>(self, numeric, channel, target, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;kick&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-kickServer"><strong>kickServer</strong></a>(self, numeric, channel, target, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;kick&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;server.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-messageClient"><strong>messageClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;PRIVMSG&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-nickClient"><strong>nickClient</strong></a>(self, numeric, newnick)</dt><dd><tt>Changes&nbsp;the&nbsp;nick&nbsp;of&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-noticeClient"><strong>noticeClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;NOTICE&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseArgs"><strong>parseArgs</strong></a>(self, args)</dt><dd><tt>Parses&nbsp;a&nbsp;string&nbsp;of&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1459.txt">RFC1459</a>-style&nbsp;arguments&nbsp;split&nbsp;into&nbsp;a&nbsp;list,&nbsp;where&nbsp;":"&nbsp;may<br>
be&nbsp;used&nbsp;for&nbsp;multi-word&nbsp;arguments&nbsp;that&nbsp;last&nbsp;until&nbsp;the&nbsp;end&nbsp;of&nbsp;a&nbsp;line.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-parseTS6Args"><strong>parseTS6Args</strong></a>(self, args)</dt><dd><tt>Similar&nbsp;to&nbsp;<a href="#InspIRCdProtocol-parseArgs">parseArgs</a>(),&nbsp;but&nbsp;stripping&nbsp;leading&nbsp;colons&nbsp;from&nbsp;the&nbsp;first&nbsp;argument<br>
of&nbsp;a&nbsp;line&nbsp;(usually&nbsp;the&nbsp;sender&nbsp;field).</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-partClient"><strong>partClient</strong></a>(self, client, channel, reason=None)</dt><dd><tt>Sends&nbsp;a&nbsp;part&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-quitClient"><strong>quitClient</strong></a>(self, numeric, reason)</dt><dd><tt>Quits&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-removeClient"><strong>removeClient</strong></a>(self, numeric)</dt><dd><tt>Internal&nbsp;function&nbsp;to&nbsp;remove&nbsp;a&nbsp;client&nbsp;from&nbsp;our&nbsp;internal&nbsp;state.</tt></dd></dl>
<dl><dt><a name="InspIRCdProtocol-topicClient"><strong>topicClient</strong></a>(self, numeric, target, text)</dt><dd><tt>Sends&nbsp;a&nbsp;TOPIC&nbsp;change&nbsp;from&nbsp;a&nbsp;PyLink&nbsp;client.</tt></dd></dl>
<hr>
Data descriptors inherited from <a href="classes.html#Protocol">classes.Protocol</a>:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#55aa55">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><strong>conf</strong> = {'bot': {'loglevel': 'CRITICAL', 'nick': 'PyLink', 'realname': 'PyLink Service Client', 'serverdesc': 'PyLink unit tests', 'user': 'pylink'}, 'servers': defaultdict(&lt;function &lt;lambda&gt; at 0x7f0dbb516c80&gt;, {})}<br>
<strong>curdir</strong> = 'protocols'<br>
<strong>log</strong> = &lt;logging.RootLogger object&gt;</td></tr></table>
</body></html>

View File

@ -0,0 +1,58 @@
# plugin_example.py: An example PyLink plugin.
# These two lines add PyLink's root directory to the PATH, so that importing things like
# 'utils' and 'log' work.
import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils
from log import log
import random
# Example PRIVMSG hook that returns "hi there!" when PyLink's nick is mentioned
# in a channel.
# irc: The IRC object where the hook was called.
# source: The UID/numeric of the sender.
# command: The true command name where the hook originates. This may or may not be
# the same as the name of the hook, depending on context.
# args: The hook data (a dict) associated with the command. The available data
# keys differ by hook name (see the hooks reference for a list of which can
# be used).
def hook_privmsg(irc, source, command, args):
channel = args['target']
text = args['text']
# irc.pseudoclient stores the IrcUser object of the main PyLink client.
# (i.e. the user defined in the bot: section of the config)
if utils.isChannel(channel) and irc.pseudoclient.nick in text:
irc.msg(channel, 'hi there!')
log.info('%s said my name on channel %s (PRIVMSG hook caught)' % (source, channel))
utils.add_hook(hook_privmsg, 'PRIVMSG')
# Example command function. @utils.add_cmd binds it to an IRC command of the same name,
# but you can also use a different name by specifying a second 'name' argument (see below).
@utils.add_cmd
# irc: The IRC object where the command was called.
# source: The UID/numeric of the calling user.
# args: A list of command args (excluding the command name) that the command was called with.
def randint(irc, source, args):
# The docstring here is used as command help by the 'help' command, and formatted using the
# same line breaks as the raw string. You shouldn't make this text or any one line too long,
# to prevent flooding users or getting long lines cut off.
"""[<min>] [<max>]
Returns a random number between <min> and <max>. <min> and <max> default
to 1 and 10 respectively, if both aren't given."""
try:
rmin = args[0]
rmax = args[1]
except IndexError:
rmin, rmax = 1, 10
n = random.randint(rmin, rmax)
irc.msg(source, str(n))
# You can also bind a command function multiple times, and/or to different command names via a
# second argument.
# Note: no checking is done at the moment to prevent multiple plugins from binding to the same
# command name. The older command just gets replaced by the new one!
utils.add_cmd(randint, "random")

View File

@ -0,0 +1,27 @@
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:
- `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`

View File

@ -0,0 +1,70 @@
# Writing plugins for PyLink
PyLink plugins are modules that extend its functionality by giving it something to do. Without any plugins loaded, PyLink can only sit on a server and do absolutely nothing.
This guide, along with the sample plugin [`plugin_example.py`](plugin_example.py), aim to show the basics of writing plugins for PyLink.
### Receiving data from IRC
Plugins have three main ways of communicating with IRC: hooks, WHOIS handlers, and commands sent in PM to the main PyLink client. A simple plugin can use one, or any mixture of these.
### Hooks
Hooks are probably the most versatile form of communication. Each hook payload is formatted as a Python `dict`, with different data keys depending on the command.
For example, a `PRIVMSG` payload would give you the fields `target` and `text`, while a `PART` payload would only give you `channels` and `reason` fields.
There are many hook types available (one for each supported IRC command), and you can read more about them in the [PyLink hooks reference](hooks-reference.md).
Plugins can bind to hooks using the `utils.add_hook()` function like so: `utils.add_hook(function_name, 'PRIVMSG')`, where `function_name` is your function definition, and `PRIVMSG` is whatever hook name you want to bind to. Once set up, `function_name` will be called whenever the protocol module receives a `PRIVMSG` command.
Each hook-bound function takes 4 arguments: `irc, source, command, args`.
- **irc**: The IRC object where the hook was called. Plugins are globally loaded, so there will be one of these per network.
- **source**: The numeric of the sender. This will usually be a UID (for users) or a SID (for server).
- **command**: The true command name where the hook originates. This may or may not be the same as the name of the hook, depending on context.
- **args**: The hook data (a `dict`) associated with the command. Again, the available data keys differ by hook name
(see the [hooks reference](hooks-reference.md) for a list of which can be used).
Hook functions do not return anything, and can raise exceptions to be caught by the core.
### PyLink commands
For plugins that interact with IRC users, there is also the option of binding to PM commands.
Commands are bound to using the `utils.add_cmd()` function: `utils.add_cmd(testcommand, "hello")`. Here, `testcommand` is the name of your function, and `hello` is the (optional) name of the command to bind to; if it is not specified, it'll use the same name as the function.
Now, your command function will be called whenever someone PMs the PyLink client with the command (e.g. `/msg PyLink hello`, case-insensitive).
Each command function takes 3 arguments: `irc, source, args`.
- **irc**: The IRC object where the command was called.
- **source**: The numeric of the sender. This will usually be a UID (for users) or a SID (for server).
- **args**: A `list` of space-separated command args (excluding the command name) that the command was called with. For example, `/msg PyLink hello world 1234` would give an `args` list of `['world', '1234']`
Command handlers do not return anything and can raise exceptions, which are caught by the core and automatically return an error message.
### WHOIS handlers
The third option, `WHOIS` handlers, are a lot more limited compared to the other options. They are solely used for `WHOIS` replies, **and only work on IRCds where WHOIS commands are sent to remote servers!** This includes Charybdis and UnrealIRCd, but **not** InspIRCd, which handles all `WHOIS` requests locally (the only thing sent between servers is an IDLE time query).
WHOIS replies are special in that any plugins wishing to add lines to a WHOIS reply must do so after the regular WHOIS lines (handled by the core), but before a special "End of WHOIS" line. This means that the regular hooks mechanism, which are only called after core handling, doesn't work here.
An example of a plugin WHOIS handler is in the relay plugin. WHOIS handler functions are added to the `world.whois_handlers` list using a simple `append()`. They should return either nothing or a two-length list: the first item being the WHOIS numeric, and the second the raw whois text.
```
def relayWhoisHandler(irc, target):
user = irc.users[target]
orig = getLocalUser(irc, target)
if orig:
network, remoteuid = orig
remotenick = world.networkobjects[network].users[remoteuid].nick
return [320, "%s :is a remote user connected via PyLink Relay. Home "
"network: %s; Home nick: %s" % (user.nick, network,
remotenick)]
world.whois_handlers.append(relayWhoisHandler)
```
### Sending data to IRC
Plugins receive data from the underlying protocol module, and communicate back using outgoing [command functions](pmodule-spec.md) implemented by the protocol module. They should *never* send raw data directly back to IRC, because that wouldn't be portable across different IRCds.
These functions are usually called in this fashion: `irc.proto.abcdClient(arg1, arg2)`. For example, the command `irc.proto.joinClient('10XAAAAAB', '#bots')` would join a PyLink client with UID `10XAAAAAB` to channel `#bots`.
For sending messages (e.g. replies to commands), a simpler form of `irc.msg(targetUID, text, notice=False, source=None)` is also used. The sender UID can be set here with the `source` argument, and defaults to the main PyLink client.

View File

@ -20,9 +20,9 @@ def spawnclient(irc, source, args):
try:
nick, ident, host = args[:3]
except ValueError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 3: nick, user, host.")
irc.msg(source, "Error: Not enough arguments. Needs 3: nick, user, host.")
return
irc.proto.spawnClient(irc, nick, ident, host)
irc.proto.spawnClient(nick, ident, host)
@utils.add_cmd
def quit(irc, source, args):
@ -33,14 +33,14 @@ def quit(irc, source, args):
try:
nick = args[0]
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).")
irc.msg(source, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).")
return
if irc.pseudoclient.uid == utils.nickToUid(irc, nick):
utils.msg(irc, source, "Error: Cannot quit the main PyLink PseudoClient!")
irc.msg(source, "Error: Cannot quit the main PyLink PseudoClient!")
return
u = utils.nickToUid(irc, nick)
quitmsg = ' '.join(args[1:]) or 'Client Quit'
irc.proto.quitClient(irc, u, quitmsg)
irc.proto.quitClient(u, quitmsg)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
def joinclient(irc, source, args):
@ -54,14 +54,14 @@ def joinclient(irc, source, args):
if not clist:
raise IndexError
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
irc.msg(source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return
u = utils.nickToUid(irc, nick)
for channel in clist:
if not utils.isChannel(channel):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
irc.msg(source, "Error: Invalid channel name %r." % channel)
return
irc.proto.joinClient(irc, u, channel)
irc.proto.joinClient(u, channel)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
'modes': irc.channels[channel].modes,
'parse_as': 'JOIN'}])
@ -77,15 +77,15 @@ def nick(irc, source, args):
nick = args[0]
newnick = args[1]
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, newnick.")
irc.msg(source, "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):
utils.msg(irc, source, 'Error: Invalid nickname %r.' % newnick)
irc.msg(source, 'Error: Invalid nickname %r.' % newnick)
return
irc.proto.nickClient(irc, u, newnick)
irc.proto.nickClient(u, newnick)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
@utils.add_cmd
@ -99,14 +99,14 @@ def part(irc, source, args):
clist = args[1].split(',')
reason = ' '.join(args[2:])
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
irc.msg(source, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.")
return
u = utils.nickToUid(irc, nick)
for channel in clist:
if not utils.isChannel(channel):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
irc.msg(source, "Error: Invalid channel name %r." % channel)
return
irc.proto.partClient(irc, u, channel, reason)
irc.proto.partClient(u, channel, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
@utils.add_cmd
@ -121,17 +121,17 @@ def kick(irc, source, args):
target = args[2]
reason = ' '.join(args[3:])
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
irc.msg(source, "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):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
irc.msg(source, "Error: Invalid channel name %r." % channel)
return
if utils.isInternalServer(irc, u):
irc.proto.kickServer(irc, u, channel, targetu, reason)
irc.proto.kickServer(u, channel, targetu, reason)
else:
irc.proto.kickClient(irc, u, channel, targetu, reason)
irc.proto.kickClient(u, channel, targetu, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])
@utils.add_cmd
@ -143,24 +143,24 @@ def mode(irc, source, args):
try:
modesource, target, modes = args[0], args[1], args[2:]
except IndexError:
utils.msg(irc, source, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
irc.msg(source, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.')
return
if not modes:
utils.msg(irc, source, "Error: No modes given to set!")
irc.msg(source, "Error: No modes given to set!")
return
parsedmodes = utils.parseModes(irc, target, modes)
targetuid = utils.nickToUid(irc, target)
if targetuid:
target = targetuid
elif not utils.isChannel(target):
utils.msg(irc, source, "Error: Invalid channel or nick %r." % target)
irc.msg(source, "Error: Invalid channel or nick %r." % target)
return
if utils.isInternalServer(irc, modesource):
irc.proto.modeServer(irc, modesource, target, parsedmodes)
irc.proto.modeServer(modesource, target, parsedmodes)
irc.callHooks([modesource, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
else:
sourceuid = utils.nickToUid(irc, modesource)
irc.proto.modeClient(irc, sourceuid, target, parsedmodes)
irc.proto.modeClient(sourceuid, target, parsedmodes)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
@utils.add_cmd
@ -172,21 +172,21 @@ def msg(irc, source, args):
try:
msgsource, target, text = args[0], args[1], ' '.join(args[2:])
except IndexError:
utils.msg(irc, source, 'Error: Not enough arguments. Needs 3: source nick, target, text.')
irc.msg(source, 'Error: Not enough arguments. Needs 3: source nick, target, text.')
return
sourceuid = utils.nickToUid(irc, msgsource)
if not sourceuid:
utils.msg(irc, source, 'Error: Unknown user %r.' % msgsource)
irc.msg(source, 'Error: Unknown user %r.' % msgsource)
return
if not utils.isChannel(target):
real_target = utils.nickToUid(irc, target)
if real_target is None:
utils.msg(irc, source, 'Error: Unknown user %r.' % target)
irc.msg(source, 'Error: Unknown user %r.' % target)
return
else:
real_target = target
if not text:
utils.msg(irc, source, 'Error: No text given.')
irc.msg(source, 'Error: No text given.')
return
irc.proto.messageClient(irc, sourceuid, real_target, text)
irc.proto.messageClient(sourceuid, real_target, text)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])

View File

@ -16,10 +16,10 @@ def status(irc, source, args):
Returns your current PyLink login status."""
identified = irc.users[source].identified
if identified:
utils.msg(irc, source, 'You are identified as \x02%s\x02.' % identified)
irc.msg(source, 'You are identified as \x02%s\x02.' % identified)
else:
utils.msg(irc, source, 'You are not identified as anyone.')
utils.msg(irc, source, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
irc.msg(source, 'You are not identified as anyone.')
irc.msg(source, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source)))
@utils.add_cmd
def identify(irc, source, args):
@ -29,17 +29,17 @@ def identify(irc, source, args):
try:
username, password = args[0], args[1]
except IndexError:
utils.msg(irc, source, 'Error: Not enough arguments.')
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']
irc.users[source].identified = realuser
utils.msg(irc, source, 'Successfully logged in as %s.' % realuser)
irc.msg(source, 'Successfully logged in as %s.' % realuser)
log.info("(%s) Successful login to %r by %s.",
irc.name, username, utils.getHostmask(irc, source))
else:
utils.msg(irc, source, 'Error: Incorrect credentials.')
irc.msg(source, 'Error: Incorrect credentials.')
u = irc.users[source]
log.warning("(%s) Failed login to %r from %s.",
irc.name, username, utils.getHostmask(irc, source))
@ -54,8 +54,8 @@ def listcommands(irc, source, args):
nfuncs = len(world.bot_commands[cmd])
if nfuncs > 1:
cmds[idx] = '%s(x%s)' % (cmd, nfuncs)
utils.msg(irc, source, 'Available commands include: %s' % ', '.join(cmds))
utils.msg(irc, source, 'To see help on a specific command, type \x02help <command>\x02.')
irc.msg(source, 'Available commands include: %s' % ', '.join(cmds))
irc.msg(source, 'To see help on a specific command, type \x02help <command>\x02.')
utils.add_cmd(listcommands, 'list')
@utils.add_cmd
@ -69,12 +69,12 @@ def help(irc, source, args):
listcommands(irc, source, args)
return
if command not in world.bot_commands:
utils.msg(irc, source, 'Error: Unknown command %r.' % command)
irc.msg(source, 'Error: Unknown command %r.' % command)
return
else:
funcs = world.bot_commands[command]
if len(funcs) > 1:
utils.msg(irc, source, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
irc.msg(source, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s'
% (len(funcs), command, ', '.join([func.__module__ for func in funcs])))
for func in funcs:
doc = func.__doc__
@ -85,9 +85,9 @@ 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:
utils.msg(irc, source, line.strip())
irc.msg(source, line.strip())
else:
utils.msg(irc, source, "Error: Command %r (from plugin %r) "
irc.msg(source, "Error: Command %r (from plugin %r) "
"doesn't offer any help." % (command, mod))
return
@ -99,17 +99,17 @@ def showuser(irc, source, args):
try:
target = args[0]
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: nick.")
irc.msg(source, "Error: Not enough arguments. Needs 1: nick.")
return
u = utils.nickToUid(irc, target) or target
# Only show private info if the person is calling 'showuser' on themselves,
# or is an oper.
verbose = utils.isOper(irc, source) or u == source
if u not in irc.users:
utils.msg(irc, source, 'Error: Unknown user %r.' % target)
irc.msg(source, 'Error: Unknown user %r.' % target)
return
f = lambda s: utils.msg(irc, source, s)
f = lambda s: irc.msg(source, s)
userobj = irc.users[u]
f('Information on user \x02%s\x02 (%s@%s): %s' % (userobj.nick, userobj.ident,
userobj.host, userobj.realname))

View File

@ -14,7 +14,7 @@ def _exec(irc, source, args):
utils.checkAuthenticated(irc, source, allowOper=False)
args = ' '.join(args)
if not args.strip():
utils.msg(irc, source, 'No code entered!')
irc.msg(source, 'No code entered!')
return
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
exec(args, globals(), locals())

View File

@ -1,19 +0,0 @@
# hooks.py: test of PyLink hooks
import sys, os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils
from log import log
def hook_join(irc, source, command, args):
channel = args['channel']
users = args['users']
log.info('%s joined channel %s (JOIN hook caught)' % (users, channel))
utils.add_hook(hook_join, 'JOIN')
def hook_privmsg(irc, source, command, args):
channel = args['target']
text = args['text']
if utils.isChannel(channel) and irc.pseudoclient.nick in text:
utils.msg(irc, channel, 'hi there!')
log.info('%s said my name on channel %s (PRIVMSG hook caught)' % (source, channel))
utils.add_hook(hook_privmsg, 'PRIVMSG')

View File

@ -22,12 +22,12 @@ def disconnect(irc, source, args):
netname = args[0]
network = world.networkobjects[netname]
except IndexError: # No argument given.
utils.msg(irc, source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
irc.msg(source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
return
except KeyError: # Unknown network.
utils.msg(irc, source, 'Error: No such network "%s" (case sensitive).' % netname)
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname)
return
utils.msg(irc, source, "Done.")
irc.msg(source, "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.
utils.msg(irc, source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
irc.msg(source, 'Error: Not enough arguments (needs 1: network name (case sensitive)).')
return
except KeyError: # Unknown network.
utils.msg(irc, source, 'Error: No such network "%s" (case sensitive).' % netname)
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname)
return
if network.connection_thread.is_alive():
utils.msg(irc, source, 'Error: Network "%s" seems to be already connected.' % netname)
irc.msg(source, '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()
utils.msg(irc, source, "Done.")
irc.msg(source, "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.
utils.msg(irc, source, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).')
irc.msg(source, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).')
return
except KeyError: # Unknown network.
utils.msg(irc, source, 'Error: No such network "%s" (case sensitive).' % netname)
irc.msg(source, 'Error: No such network "%s" (case sensitive).' % netname)
return
except ValueError:
utils.msg(irc, source, 'Error: Invalid argument "%s" for <seconds>.' % seconds)
irc.msg(source, 'Error: Invalid argument "%s" for <seconds>.' % seconds)
return
network.serverdata['autoconnect'] = seconds
utils.msg(irc, source, "Done.")
irc.msg(source, "Done.")

View File

@ -24,7 +24,7 @@ relayusers = defaultdict(dict)
spawnlocks = defaultdict(threading.Lock)
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
def relayWhoisHandlers(irc, target):
def relayWhoisHandler(irc, target):
user = irc.users[target]
orig = getLocalUser(irc, target)
if orig:
@ -33,13 +33,13 @@ def relayWhoisHandlers(irc, target):
return [320, "%s :is a remote user connected via PyLink Relay. Home "
"network: %s; Home nick: %s" % (user.nick, network,
remotenick)]
world.whois_handlers.append(relayWhoisHandlers)
world.whois_handlers.append(relayWhoisHandler)
def normalizeNick(irc, netname, nick, separator=None, uid=''):
separator = separator or irc.serverdata.get('separator') or "/"
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
orig_nick = nick
protoname = irc.proto.__name__
protoname = irc.protoname
maxnicklen = irc.maxnicklen
if '/' not in separator or not protoname.startswith(('insp', 'unreal')):
# Charybdis doesn't allow / in usernames, and will SQUIT with
@ -109,9 +109,9 @@ def save(irc, source, args):
Saves the relay database to disk."""
if utils.isOper(irc, source):
exportDB()
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
else:
utils.msg(irc, source, 'Error: You are not authenticated!')
irc.msg(source, 'Error: You are not authenticated!')
return
def getPrefixModes(irc, remoteirc, channel, user):
@ -172,7 +172,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
hideoper_mode = remoteirc.umodes.get('hideoper')
if hideoper_mode:
modes.append((hideoper_mode, None))
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
u = remoteirc.proto.spawnClient(nick, ident=ident,
host=host, realname=realname,
modes=modes, ts=userobj.ts,
opertype=opertype).uid
@ -180,7 +180,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
remoteirc.users[u].opertype = opertype
away = userobj.away
if away:
remoteirc.proto.awayClient(remoteirc, u, away)
remoteirc.proto.awayClient(u, away)
relayusers[(irc.name, user)][remoteirc.name] = u
return u
@ -275,11 +275,11 @@ def initializeChannel(irc, channel):
# Only update the topic if it's different from what we already have,
# and topic bursting is complete.
if remoteirc.channels[remotechan].topicset and topic != irc.channels[channel].topic:
irc.proto.topicServer(irc, irc.sid, channel, topic)
irc.proto.topicServer(irc.sid, channel, topic)
# Send our users and channel modes to the other nets
log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users)
relayJoins(irc, channel, c.users, c.ts)
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
irc.proto.joinClient(irc.pseudoclient.uid, channel)
def handle_join(irc, numeric, command, args):
channel = args['channel']
@ -294,7 +294,7 @@ utils.add_hook(handle_join, 'JOIN')
def handle_quit(irc, numeric, command, args):
for netname, user in relayusers[(irc.name, numeric)].copy().items():
remoteirc = world.networkobjects[netname]
remoteirc.proto.quitClient(remoteirc, user, args['text'])
remoteirc.proto.quitClient(user, args['text'])
del relayusers[(irc.name, numeric)]
utils.add_hook(handle_quit, 'QUIT')
@ -310,7 +310,7 @@ def handle_nick(irc, numeric, command, args):
remoteirc = world.networkobjects[netname]
newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
if remoteirc.users[user].nick != newnick:
remoteirc.proto.nickClient(remoteirc, user, newnick)
remoteirc.proto.nickClient(user, newnick)
utils.add_hook(handle_nick, 'NICK')
def handle_part(irc, numeric, command, args):
@ -325,9 +325,9 @@ def handle_part(irc, numeric, command, args):
remotechan = findRemoteChan(irc, remoteirc, channel)
if remotechan is None:
continue
remoteirc.proto.partClient(remoteirc, user, remotechan, text)
remoteirc.proto.partClient(user, remotechan, text)
if not remoteirc.users[user].channels:
remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.')
remoteirc.proto.quitClient(user, 'Left all shared channels.')
del relayusers[(irc.name, numeric)][remoteirc.name]
utils.add_hook(handle_part, 'PART')
@ -352,7 +352,7 @@ def handle_privmsg(irc, numeric, command, args):
# it's the only way we can make sure they have a spawned client on ALL
# of the linked networks. This affects -n channels too; see
# https://github.com/GLolol/PyLink/issues/91 for an explanation of why.
utils.msg(irc, numeric, 'Error: You must be in %r in order to send '
irc.msg(numeric, 'Error: You must be in %r in order to send '
'messages over the relay.' % target, notice=True)
return
if utils.isChannel(target):
@ -363,9 +363,9 @@ def handle_privmsg(irc, numeric, command, args):
continue
real_target = prefix + real_target
if notice:
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
remoteirc.proto.noticeClient(user, real_target, text)
else:
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
remoteirc.proto.messageClient(user, real_target, text)
else:
remoteuser = getLocalUser(irc, target)
if remoteuser is None:
@ -376,16 +376,16 @@ def handle_privmsg(irc, numeric, command, args):
# on the remote network, and we won't have anything to send our
# messages from.
if homenet not in remoteusers.keys():
utils.msg(irc, numeric, 'Error: You must be in a common channel '
irc.msg(numeric, 'Error: You must be in a common channel '
'with %r in order to send messages.' % \
irc.users[target].nick, notice=True)
return
remoteirc = world.networkobjects[homenet]
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
if notice:
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
remoteirc.proto.noticeClient(user, real_target, text)
else:
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
remoteirc.proto.messageClient(user, real_target, text)
utils.add_hook(handle_privmsg, 'PRIVMSG')
utils.add_hook(handle_privmsg, 'NOTICE')
@ -431,12 +431,12 @@ def handle_kick(irc, source, command, args):
# kick ops, admins can't kick owners, etc.
modes = getPrefixModes(remoteirc, irc, remotechan, real_target)
# Join the kicked client back with its respective modes.
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
irc.proto.sjoinServer(irc.sid, channel, [(modes, target)])
if kicker in irc.users:
log.info('(%s) Relay claim: Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
irc.name, args['text'], irc.users[source].nick,
remoteirc.users[real_target].nick, remoteirc.name, channel)
utils.msg(irc, kicker, "This channel is claimed; your kick to "
irc.msg(kicker, "This channel is claimed; your kick to "
"%s has been blocked because you are not "
"(half)opped." % channel, notice=True)
else:
@ -450,7 +450,7 @@ def handle_kick(irc, source, command, args):
# Propogate the kick!
if real_kicker:
log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,real_kicker, kicker, irc.name)
remoteirc.proto.kickClient(remoteirc, real_kicker,
remoteirc.proto.kickClient(real_kicker,
remotechan, real_target, text)
else:
# Kick originated from a server, or the kicker isn't in any
@ -464,17 +464,17 @@ def handle_kick(irc, source, command, args):
text = "(%s/%s) %s" % (kname, irc.name, text)
except AttributeError:
text = "(<unknown kicker>@%s) %s" % (irc.name, text)
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
remoteirc.proto.kickServer(remoteirc.sid,
remotechan, real_target, text)
# If the target isn't on any channels, quit them.
if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels:
del relayusers[origuser][remoteirc.name]
remoteirc.proto.quitClient(remoteirc, real_target, 'Left all shared channels.')
remoteirc.proto.quitClient(real_target, 'Left all shared channels.')
if origuser and not irc.users[target].channels:
del relayusers[origuser][irc.name]
irc.proto.quitClient(irc, target, 'Left all shared channels.')
irc.proto.quitClient(target, 'Left all shared channels.')
utils.add_hook(handle_kick, 'KICK')
@ -493,7 +493,7 @@ def handle_chgclient(irc, source, command, args):
for netname, user in relayusers[(irc.name, target)].items():
remoteirc = world.networkobjects[netname]
try:
remoteirc.proto.updateClient(remoteirc, user, field, text)
remoteirc.proto.updateClient(user, field, text)
except NotImplementedError: # IRCd doesn't support changing the field we want
log.debug('(%s) Ignoring changing field %r of %s on %s (for %s/%s);'
' remote IRCd doesn\'t support it', irc.name, field,
@ -584,9 +584,9 @@ def relayModes(irc, remoteirc, sender, channel, modes=None):
# Check if the sender is a user; remember servers are allowed to set modes too.
u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False)
if u:
remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes)
remoteirc.proto.modeClient(u, remotechan, supported_modes)
else:
remoteirc.proto.modeServer(remoteirc, remoteirc.sid, remotechan, supported_modes)
remoteirc.proto.modeServer(remoteirc.sid, remotechan, supported_modes)
def getSupportedUmodes(irc, remoteirc, modes):
supported_modes = []
@ -613,7 +613,7 @@ def getSupportedUmodes(irc, remoteirc, modes):
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
"the remote network (%s)'s IRCd (%s) doesn't support it.",
irc.name, modechar, arg, remoteirc.name,
remoteirc.proto.__name__)
remoteirc.protoname)
return supported_modes
def handle_mode(irc, numeric, command, args):
@ -636,7 +636,7 @@ def handle_mode(irc, numeric, command, args):
modes.append(('-%s' % hideoper_mode, None))
remoteuser = getRemoteUser(irc, remoteirc, target, spawnIfMissing=False)
if remoteuser and modes:
remoteirc.proto.modeClient(remoteirc, remoteuser, remoteuser, modes)
remoteirc.proto.modeClient(remoteuser, remoteuser, modes)
utils.add_hook(handle_mode, 'MODE')
@ -654,9 +654,9 @@ def handle_topic(irc, numeric, command, args):
# This might originate from a server too.
remoteuser = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
if remoteuser:
remoteirc.proto.topicClient(remoteirc, remoteuser, remotechan, topic)
remoteirc.proto.topicClient(remoteuser, remotechan, topic)
else:
remoteirc.proto.topicServer(remoteirc, remoteirc.sid, remotechan, topic)
remoteirc.proto.topicServer(remoteirc.sid, remotechan, topic)
utils.add_hook(handle_topic, 'TOPIC')
def handle_kill(irc, numeric, command, args):
@ -676,12 +676,12 @@ def handle_kill(irc, numeric, command, args):
modes = getPrefixModes(remoteirc, irc, localchan, realuser[1])
log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
client = getRemoteUser(remoteirc, irc, realuser[1])
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
irc.proto.sjoinServer(irc.sid, localchan, [(modes, client)])
if userdata and numeric in irc.users:
log.info('(%s) Relay claim: Blocked KILL (reason %r) from %s to relay client %s/%s.',
irc.name, args['text'], irc.users[numeric].nick,
remoteirc.users[realuser[1]].nick, realuser[0])
utils.msg(irc, numeric, "Your kill to %s has been blocked "
irc.msg(numeric, "Your kill to %s has been blocked "
"because PyLink does not allow killing"
" users over the relay at this time." % \
userdata.nick, notice=True)
@ -745,10 +745,10 @@ def relayJoins(irc, channel, users, ts, burst=True):
# Burst was explicitly given, or we're trying to join multiple
# users/someone with a prefix.
if burst or len(queued_users) > 1 or queued_users[0][0]:
remoteirc.proto.sjoinServer(remoteirc, remoteirc.sid, remotechan, queued_users, ts=ts)
remoteirc.proto.sjoinServer(remoteirc.sid, remotechan, queued_users, ts=ts)
relayModes(irc, remoteirc, irc.sid, channel, irc.channels[channel].modes)
else:
remoteirc.proto.joinClient(remoteirc, queued_users[0][1], remotechan)
remoteirc.proto.joinClient(queued_users[0][1], remotechan)
def relayPart(irc, channel, user):
for name, remoteirc in world.networkobjects.items():
@ -762,16 +762,16 @@ def relayPart(irc, channel, user):
log.debug('(%s) relayPart: remoteuser for %s/%s found as %s', irc.name, user, irc.name, remoteuser)
if remotechan is None or remoteuser is None:
continue
remoteirc.proto.partClient(remoteirc, remoteuser, remotechan, 'Channel delinked.')
remoteirc.proto.partClient(remoteuser, remotechan, 'Channel delinked.')
if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels:
remoteirc.proto.quitClient(remoteirc, remoteuser, 'Left all shared channels.')
remoteirc.proto.quitClient(remoteuser, 'Left all shared channels.')
del relayusers[(irc.name, user)][remoteirc.name]
def removeChannel(irc, channel):
if irc is None:
return
if channel not in map(str.lower, irc.serverdata['channels']):
irc.proto.partClient(irc, irc.pseudoclient.uid, channel, 'Channel delinked.')
irc.proto.partClient(irc.pseudoclient.uid, channel, 'Channel delinked.')
relay = findRelay((irc.name, channel))
if relay:
for user in irc.channels[channel].users.copy():
@ -782,12 +782,12 @@ def removeChannel(irc, channel):
if user == irc.pseudoclient.uid and channel in \
irc.serverdata['channels']:
continue
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
irc.proto.partClient(user, channel, 'Channel delinked.')
# Don't ever quit it either...
if user != irc.pseudoclient.uid and not irc.users[user].channels:
remoteuser = getLocalUser(irc, user)
del relayusers[remoteuser][irc.name]
irc.proto.quitClient(irc, user, 'Left all shared channels.')
irc.proto.quitClient(user, 'Left all shared channels.')
@utils.add_cmd
def create(irc, source, args):
@ -797,20 +797,20 @@ def create(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: channel.")
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.")
return
if not utils.isChannel(channel):
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
irc.msg(source, 'Error: Invalid channel %r.' % channel)
return
if source not in irc.channels[channel].users:
utils.msg(irc, source, 'Error: You must be in %r to complete this operation.' % channel)
irc.msg(source, 'Error: You must be in %r to complete this operation.' % channel)
return
if not utils.isOper(irc, source):
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
irc.msg(source, 'Error: You must be opered in order to complete this operation.')
return
db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()}
initializeChannel(irc, channel)
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
@utils.add_cmd
def destroy(irc, source, args):
@ -820,13 +820,13 @@ def destroy(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 1: channel.")
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.")
return
if not utils.isChannel(channel):
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
irc.msg(source, 'Error: Invalid channel %r.' % channel)
return
if not utils.isOper(irc, source):
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
irc.msg(source, 'Error: You must be opered in order to complete this operation.')
return
entry = (irc.name, channel)
@ -835,9 +835,9 @@ def destroy(irc, source, args):
removeChannel(world.networkobjects.get(link[0]), link[1])
removeChannel(irc, channel)
del db[entry]
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
else:
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
irc.msg(source, 'Error: No such relay %r exists.' % channel)
return
@utils.add_cmd
@ -850,7 +850,7 @@ def link(irc, source, args):
channel = utils.toLower(irc, args[1])
remotenet = args[0].lower()
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
irc.msg(source, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).")
return
try:
localchan = utils.toLower(irc, args[2])
@ -858,39 +858,39 @@ def link(irc, source, args):
localchan = channel
for c in (channel, localchan):
if not utils.isChannel(c):
utils.msg(irc, source, 'Error: Invalid channel %r.' % c)
irc.msg(source, 'Error: Invalid channel %r.' % c)
return
if source not in irc.channels[localchan].users:
utils.msg(irc, source, 'Error: You must be in %r to complete this operation.' % localchan)
irc.msg(source, 'Error: You must be in %r to complete this operation.' % localchan)
return
if not utils.isOper(irc, source):
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
irc.msg(source, 'Error: You must be opered in order to complete this operation.')
return
if remotenet not in world.networkobjects:
utils.msg(irc, source, 'Error: No network named %r exists.' % remotenet)
irc.msg(source, 'Error: No network named %r exists.' % remotenet)
return
localentry = findRelay((irc.name, localchan))
if localentry:
utils.msg(irc, source, 'Error: Channel %r is already part of a relay.' % localchan)
irc.msg(source, 'Error: Channel %r is already part of a relay.' % localchan)
return
try:
entry = db[(remotenet, channel)]
except KeyError:
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
irc.msg(source, 'Error: No such relay %r exists.' % channel)
return
else:
if irc.name in entry['blocked_nets']:
utils.msg(irc, source, 'Error: Access denied (network is banned from linking to this channel).')
irc.msg(source, 'Error: Access denied (network is banned from linking to this channel).')
return
for link in entry['links']:
if link[0] == irc.name:
utils.msg(irc, source, "Error: Remote channel '%s%s' is already"
irc.msg(source, "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)
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
@utils.add_cmd
def delink(irc, source, args):
@ -901,23 +901,23 @@ def delink(irc, source, args):
try:
channel = utils.toLower(irc, args[0])
except IndexError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
irc.msg(source, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).")
return
try:
remotenet = args[1].lower()
except IndexError:
remotenet = None
if not utils.isOper(irc, source):
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
irc.msg(source, 'Error: You must be opered in order to complete this operation.')
return
if not utils.isChannel(channel):
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
irc.msg(source, 'Error: Invalid channel %r.' % channel)
return
entry = findRelay((irc.name, channel))
if entry:
if entry[0] == irc.name: # We own this channel.
if not remotenet:
utils.msg(irc, source, "Error: You must select a network to "
irc.msg(source, "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).")
@ -930,9 +930,9 @@ def delink(irc, source, args):
else:
removeChannel(irc, channel)
db[entry]['links'].remove((irc.name, channel))
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
else:
utils.msg(irc, source, 'Error: No such relay %r.' % channel)
irc.msg(source, 'Error: No such relay %r.' % channel)
def initializeAll(irc):
log.debug('(%s) initializeAll: waiting for world.started', irc.name)
@ -986,7 +986,7 @@ def handle_save(irc, numeric, command, args):
newnick = normalizeNick(irc, remotenet, nick)
log.info('(%s) SAVE received for relay client %r (%s), fixing nick to %s',
irc.name, target, nick, newnick)
irc.proto.nickClient(irc, target, newnick)
irc.proto.nickClient(target, newnick)
else:
log.warning('(%s) SAVE received for relay client %r (%s), not '
'fixing nick again due to 5 failed attempts in '
@ -1007,7 +1007,7 @@ def linked(irc, source, args):
networks = list(world.networkobjects.keys())
networks.remove(irc.name)
s = 'Connected networks: \x02%s\x02 %s' % (irc.name, ' '.join(networks))
utils.msg(irc, source, s)
irc.msg(source, s)
# Sort relay DB by channel name, and then sort.
for k, v in sorted(db.items(), key=lambda channel: channel[0][1]):
s = '\x02%s%s\x02 ' % k
@ -1015,12 +1015,12 @@ def linked(irc, source, args):
s += ' '.join([''.join(link) for link in v['links']])
else:
s += '(no relays yet)'
utils.msg(irc, source, s)
irc.msg(source, s)
def handle_away(irc, numeric, command, args):
for netname, user in relayusers[(irc.name, numeric)].items():
remoteirc = world.networkobjects[netname]
remoteirc.proto.awayClient(remoteirc, user, args['text'])
remoteirc.proto.awayClient(user, args['text'])
utils.add_hook(handle_away, 'AWAY')
def handle_spawnmain(irc, numeric, command, args):
@ -1039,16 +1039,16 @@ def handle_invite(irc, source, command, args):
remotechan = findRemoteChan(irc, remoteirc, channel)
remotesource = getRemoteUser(irc, remoteirc, source, spawnIfMissing=False)
if remotesource is None:
utils.msg(irc, source, 'Error: You must be in a common channel '
irc.msg(source, 'Error: You must be in a common channel '
'with %s to invite them to channels.' % \
irc.users[target].nick,
notice=True)
elif remotechan is None:
utils.msg(irc, source, 'Error: You cannot invite someone to a '
irc.msg(source, 'Error: You cannot invite someone to a '
'channel not on their network!',
notice=True)
else:
remoteirc.proto.inviteClient(remoteirc, remotesource, remoteuser,
remoteirc.proto.inviteClient(remotesource, remoteuser,
remotechan)
utils.add_hook(handle_invite, 'INVITE')
@ -1060,43 +1060,43 @@ def linkacl(irc, source, args):
LINKACL LIST returns a list of blocked networks for a channel, while the ALLOW and DENY subcommands allow manipulating this blacklist."""
missingargs = "Error: Not enough arguments. Needs 2-3: subcommand (ALLOW/DENY/LIST), channel, remote network (for ALLOW/DENY)."
if not utils.isOper(irc, source):
utils.msg(irc, source, 'Error: You must be opered in order to complete this operation.')
irc.msg(source, 'Error: You must be opered in order to complete this operation.')
return
try:
cmd = args[0].lower()
channel = utils.toLower(irc, args[1])
except IndexError:
utils.msg(irc, source, missingargs)
irc.msg(source, missingargs)
return
if not utils.isChannel(channel):
utils.msg(irc, source, 'Error: Invalid channel %r.' % channel)
irc.msg(source, 'Error: Invalid channel %r.' % channel)
return
relay = findRelay((irc.name, channel))
if not relay:
utils.msg(irc, source, 'Error: No such relay %r exists.' % channel)
irc.msg(source, '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)')
utils.msg(irc, source, s)
irc.msg(source, s)
return
try:
remotenet = args[2]
except IndexError:
utils.msg(irc, source, missingargs)
irc.msg(source, missingargs)
return
if cmd == 'deny':
db[relay]['blocked_nets'].add(remotenet)
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
elif cmd == 'allow':
try:
db[relay]['blocked_nets'].remove(remotenet)
except KeyError:
utils.msg(irc, source, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
irc.msg(source, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel))
else:
utils.msg(irc, source, 'Done.')
irc.msg(source, 'Done.')
else:
utils.msg(irc, source, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
irc.msg(source, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd)
@utils.add_cmd
def showuser(irc, source, args):
@ -1125,11 +1125,11 @@ def showuser(irc, source, args):
remotenet, remoteuser = r
remoteirc = world.networkobjects[remotenet]
nicks.append('%s: \x02%s\x02' % (remotenet, remoteirc.users[remoteuser].nick))
utils.msg(irc, source, "\x02Relay nicks\x02: %s" % ', '.join(nicks))
irc.msg(source, "\x02Relay nicks\x02: %s" % ', '.join(nicks))
relaychannels = []
for ch in irc.users[u].channels:
relay = findRelay((irc.name, ch))
if relay:
relaychannels.append(''.join(relay))
if relaychannels and (utils.isOper(irc, source) or u == source):
utils.msg(irc, source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels))
irc.msg(source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,245 +7,240 @@ import utils
from log import log
from classes import *
def _send(irc, sid, msg):
irc.send(':%s %s' % (sid, msg))
class TS6BaseProtocol(Protocol):
def _send(self, source, msg):
"""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(args):
"""<arg list>
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]
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)
break
return real_args
# 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(args):
"""<arg list>
def parseTS6Args(self, args):
"""Similar to parseArgs(), but stripping leading colons from the first argument
of a line (usually the sender field)."""
args = self.parseArgs(args)
args[0] = args[0].split(':', 1)[1]
return args
Similar to parseArgs(), but stripping leading colons from the first argument
of a line (usually the sender field)."""
args = parseArgs(args)
args[0] = args[0].split(':', 1)[1]
return args
### OUTGOING COMMANDS
### OUTGOING COMMANDS
def _sendKick(self, numeric, channel, target, reason=None):
"""Internal function to send kicks from a PyLink client/server."""
channel = utils.toLower(self.irc, channel)
if not reason:
reason = 'No reason given'
self._send(numeric, 'KICK %s %s :%s' % (channel, target, reason))
# We can pretend the target left by its own will; all we really care about
# is that the target gets removed from the channel userlist, and calling
# handle_part() does that just fine.
self.handle_part(target, 'KICK', [channel])
def _sendKick(irc, numeric, channel, target, reason=None):
"""<irc object> <kicker client numeric>
def kickClient(self, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink client."""
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
self._sendKick(numeric, channel, target, reason=reason)
Sends a kick from a PyLink PseudoClient."""
channel = utils.toLower(irc, channel)
if not reason:
reason = 'No reason given'
_send(irc, numeric, 'KICK %s %s :%s' % (channel, target, reason))
# We can pretend the target left by its own will; all we really care about
# is that the target gets removed from the channel userlist, and calling
# handle_part() does that just fine.
handle_part(irc, target, 'KICK', [channel])
def kickServer(self, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink server."""
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
self._sendKick(numeric, channel, target, reason=reason)
def kickClient(irc, numeric, channel, target, reason=None):
if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_sendKick(irc, numeric, channel, target, reason=reason)
def nickClient(self, numeric, newnick):
"""Changes the nick of a PyLink client."""
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
self._send(numeric, 'NICK %s %s' % (newnick, int(time.time())))
self.irc.users[numeric].nick = newnick
def kickServer(irc, numeric, channel, target, reason=None):
if not utils.isInternalServer(irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
_sendKick(irc, numeric, channel, target, reason=reason)
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]
def nickClient(irc, numeric, newnick):
"""<irc object> <client numeric> <new nickname>
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]', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
Changes the nick of a PyLink PseudoClient."""
if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NICK %s %s' % (newnick, int(time.time())))
irc.users[numeric].nick = newnick
def partClient(self, client, channel, reason=None):
"""Sends a part from a PyLink client."""
channel = utils.toLower(self.irc, channel)
if not utils.isInternalClient(self.irc, client):
log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', self.irc.name, client, channel)
raise LookupError('No such PyLink PseudoClient exists.')
msg = "PART %s" % channel
if reason:
msg += " :%s" % reason
self._send(client, msg)
self.handle_part(client, 'PART', [channel])
def removeClient(irc, numeric):
"""<irc object> <client numeric>
def quitClient(self, numeric, reason):
"""Quits a PyLink client."""
if utils.isInternalClient(self.irc, numeric):
self._send(numeric, "QUIT :%s" % reason)
self.removeClient(numeric)
else:
raise LookupError("No such PyLink PseudoClient exists.")
Removes a client from our internal databases, regardless
of whether it's one of our pseudoclients or not."""
for c, v in irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (irc.channels[c].users or ((irc.cmodes.get('permanent'), None) in irc.channels[c].modes)):
del irc.channels[c]
def messageClient(self, numeric, target, text):
"""Sends a PRIVMSG from a PyLink client."""
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
self._send(numeric, 'PRIVMSG %s :%s' % (target, text))
sid = numeric[:3]
log.debug('Removing client %s from irc.users', numeric)
del irc.users[numeric]
log.debug('Removing client %s from irc.servers[%s]', numeric, sid)
irc.servers[sid].users.discard(numeric)
def noticeClient(self, numeric, target, text):
"""Sends a NOTICE from a PyLink client."""
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
self._send(numeric, 'NOTICE %s :%s' % (target, text))
def partClient(irc, client, channel, reason=None):
channel = utils.toLower(irc, channel)
if not utils.isInternalClient(irc, client):
log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
raise LookupError('No such PyLink PseudoClient exists.')
msg = "PART %s" % channel
if reason:
msg += " :%s" % reason
_send(irc, client, msg)
handle_part(irc, client, 'PART', [channel])
def topicClient(self, numeric, target, text):
"""Sends a TOPIC change from a PyLink client."""
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
self._send(numeric, 'TOPIC %s :%s' % (target, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
def quitClient(irc, numeric, reason):
"""<irc object> <client numeric>
### HANDLERS
Quits a PyLink PseudoClient."""
if utils.isInternalClient(irc, numeric):
_send(irc, numeric, "QUIT :%s" % reason)
removeClient(irc, numeric)
else:
raise LookupError("No such PyLink PseudoClient exists.")
def handle_privmsg(self, source, command, args):
"""Handles incoming PRIVMSG/NOTICE."""
# <- :70MAAAAAA PRIVMSG #dev :afasfsa
# <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
target = args[0]
# We use lowercase channels internally, but uppercase UIDs.
if utils.isChannel(target):
target = utils.toLower(self.irc, target)
return {'target': target, 'text': args[1]}
def messageClient(irc, numeric, target, text):
"""<irc object> <client numeric> <text>
handle_notice = handle_privmsg
Sends PRIVMSG <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'PRIVMSG %s :%s' % (target, text))
def handle_kill(self, source, command, args):
"""Handles incoming KILLs."""
killed = args[0]
# Depending on whether the self.ircd sends explicit QUIT messages for
# KILLed clients, the user may or may not have automatically been removed.
# If not, we have to assume that KILL = QUIT and remove them ourselves.
data = self.irc.users.get(killed)
if data:
self.removeClient(killed)
return {'target': killed, 'text': args[1], 'userdata': data}
def noticeClient(irc, numeric, target, text):
"""<irc object> <client numeric> <text>
def handle_kick(self, source, command, args):
"""Handles incoming KICKs."""
# :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason
channel = utils.toLower(self.irc, args[0])
kicked = args[1]
self.handle_part(kicked, 'KICK', [channel, args[2]])
return {'channel': channel, 'target': kicked, 'text': args[2]}
Sends NOTICE <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NOTICE %s :%s' % (target, text))
def handle_error(self, numeric, command, args):
"""Handles ERROR messages - these mean that our uplink has disconnected us!"""
self.irc.connected.clear()
raise ProtocolError('Received an ERROR, disconnecting!')
def topicClient(irc, numeric, target, text):
if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'TOPIC %s :%s' % (target, text))
irc.channels[target].topic = text
irc.channels[target].topicset = True
def handle_nick(self, numeric, command, args):
"""Handles incoming NICK changes."""
# <- :70MAAAAAA NICK GL-devel 1434744242
oldnick = self.irc.users[numeric].nick
newnick = self.irc.users[numeric].nick = args[0]
return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])}
### HANDLERS
def handle_quit(self, numeric, command, args):
"""Handles incoming QUIT commands."""
# <- :1SRAAGB4T QUIT :Quit: quit message goes here
self.removeClient(numeric)
return {'text': args[0]}
def handle_privmsg(irc, source, command, args):
# <- :70MAAAAAA PRIVMSG #dev :afasfsa
# <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
target = args[0]
# We use lowercase channels internally, but uppercase UIDs.
if utils.isChannel(target):
target = utils.toLower(irc, target)
return {'target': target, 'text': args[1]}
def handle_save(self, numeric, command, args):
"""Handles incoming SAVE messages, used to handle nick collisions."""
# In this below example, the client Derp_ already exists,
# and trying to change someone's nick to it will cause a nick
# collision. On TS6 self.ircds, this will simply set the collided user's
# nick to its UID.
handle_notice = handle_privmsg
# <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_
# -> :0AL000001 NICK Derp_ 1433728673
# <- :70M SAVE 0AL000001 1433728673
user = args[0]
oldnick = self.irc.users[user].nick
self.irc.users[user].nick = user
return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick}
def handle_kill(irc, source, command, args):
killed = args[0]
data = irc.users.get(killed)
if data:
removeClient(irc, killed)
return {'target': killed, 'text': args[1], 'userdata': data}
def handle_squit(self, numeric, command, args):
"""Handles incoming SQUITs (netsplits)."""
# :70M SQUIT 1ML :Server quit by GL!gl@0::1
split_server = args[0]
affected_users = []
log.info('(%s) Netsplit on server %s', self.irc.name, split_server)
# Prevent RuntimeError: dictionary changed size during iteration
old_servers = self.irc.servers.copy()
for sid, data in old_servers.items():
if data.uplink == split_server:
log.debug('Server %s also hosts server %s, removing those users too...', split_server, sid)
args = self.handle_squit(sid, 'SQUIT', [sid, "PyLink: Automatically splitting leaf servers of %s" % sid])
affected_users += args['users']
for user in self.irc.servers[split_server].users.copy():
affected_users.append(user)
log.debug('Removing client %s (%s)', user, self.irc.users[user].nick)
self.removeClient(user)
del self.irc.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', self.irc.name, affected_users)
return {'target': split_server, 'users': affected_users}
def handle_kick(irc, source, command, args):
# :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason
channel = utils.toLower(irc, args[0])
kicked = args[1]
handle_part(irc, kicked, 'KICK', [channel, args[2]])
return {'channel': channel, 'target': kicked, 'text': args[2]}
def handle_topic(self, numeric, command, args):
"""Handles incoming TOPIC changes from clients. For topic bursts,
TB (TS6/charybdis) and FTOPIC (InspIRCd) are used instead."""
# <- :70MAAAAAA TOPIC #test :test
channel = utils.toLower(self.irc, args[0])
topic = args[1]
ts = int(time.time())
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic}
def handle_error(irc, numeric, command, args):
irc.connected.clear()
raise ProtocolError('Received an ERROR, killing!')
def handle_nick(irc, numeric, command, args):
# <- :70MAAAAAA NICK GL-devel 1434744242
oldnick = irc.users[numeric].nick
newnick = irc.users[numeric].nick = args[0]
return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])}
def handle_quit(irc, numeric, command, args):
# <- :1SRAAGB4T QUIT :Quit: quit message goes here
removeClient(irc, numeric)
return {'text': args[0]}
def handle_save(irc, numeric, command, args):
# This is used to handle nick collisions. Here, the client Derp_ already exists,
# so trying to change nick to it will cause a nick collision. On InspIRCd,
# this will simply set the collided user's nick to its UID.
# <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_
# -> :0AL000001 NICK Derp_ 1433728673
# <- :70M SAVE 0AL000001 1433728673
user = args[0]
oldnick = irc.users[user].nick
irc.users[user].nick = user
return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick}
def handle_squit(irc, numeric, command, args):
# :70M SQUIT 1ML :Server quit by GL!gl@0::1
split_server = args[0]
affected_users = []
log.info('(%s) Netsplit on server %s', irc.name, split_server)
# Prevent RuntimeError: dictionary changed size during iteration
old_servers = irc.servers.copy()
for sid, data in old_servers.items():
if data.uplink == split_server:
log.debug('Server %s also hosts server %s, removing those users too...', split_server, sid)
args = handle_squit(irc, sid, 'SQUIT', [sid, "PyLink: Automatically splitting leaf servers of %s" % sid])
affected_users += args['users']
for user in irc.servers[split_server].users.copy():
affected_users.append(user)
log.debug('Removing client %s (%s)', user, irc.users[user].nick)
removeClient(irc, user)
del irc.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', irc.name, affected_users)
return {'target': split_server, 'users': affected_users}
def handle_mode(irc, numeric, command, args):
# In InspIRCd, MODE is used for setting user modes and
# FMODE is used for channel modes:
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
target = args[0]
modestrings = args[1:]
changedmodes = utils.parseModes(irc, numeric, modestrings)
utils.applyModes(irc, target, changedmodes)
return {'target': target, 'modes': changedmodes}
def handle_topic(irc, numeric, command, args):
# <- :70MAAAAAA TOPIC #test :test
channel = utils.toLower(irc, args[0])
topic = args[1]
ts = int(time.time())
irc.channels[channel].topic = topic
irc.channels[channel].topicset = True
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic}
def handle_part(irc, source, command, args):
channels = utils.toLower(irc, args[0]).split(',')
for channel in channels:
# We should only get PART commands for channels that exist, right??
irc.channels[channel].removeuser(source)
try:
irc.users[source].channels.discard(channel)
except KeyError:
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", irc.name, channel, source)
try:
reason = args[1]
except IndexError:
reason = ''
# Clear empty non-permanent channels.
if not (irc.channels[channel].users or ((irc.cmodes.get('permanent'), None) in irc.channels[channel].modes)):
del irc.channels[channel]
return {'channels': channels, 'text': reason}
def handle_part(self, source, command, args):
"""Handles incoming PART commands."""
channels = utils.toLower(self.irc, args[0]).split(',')
for channel in channels:
# We should only get PART commands for channels that exist, right??
self.irc.channels[channel].removeuser(source)
try:
self.irc.users[source].channels.discard(channel)
except KeyError:
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", self.irc.name, channel, source)
try:
reason = args[1]
except IndexError:
reason = ''
# Clear empty non-permanent channels.
if not (self.irc.channels[channel].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[channel].modes)):
del self.irc.channels[channel]
return {'channels': channels, 'text': reason}

View File

@ -10,7 +10,7 @@ cd "$WRAPPER_DIR"
if [[ ! -z "$(which cpulimit)" ]]; then
# -z makes cpulimit exit when PyLink dies.
cpulimit -l $LIMIT -z ./main.py
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

View File

@ -29,7 +29,7 @@ class CorePluginTestCase(tests_common.PluginTestCase):
self.assertNotEqual(self.irc.pseudoclient.uid, spmain[0]['olduser'])
def testKickrejoin(self):
self.proto.kickClient(self.irc, self.u, '#pylink', self.u, 'test')
self.proto.kickClient(self.u, '#pylink', self.u, 'test')
msgs = self.irc.takeMsgs()
commands = self.irc.takeCommands(msgs)
self.assertIn('FJOIN', commands)

View File

@ -7,7 +7,7 @@ import unittest
class TestFakeIRC(unittest.TestCase):
def setUp(self):
self.irc = classes.FakeIRC('unittest', classes.FakeProto())
self.irc = classes.FakeIRC('unittest', classes.FakeProto)
def testFakeIRC(self):
self.irc.run('this should do nothing')

View File

@ -19,7 +19,7 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
self.assertRaises(classes.ProtocolError, self.irc.run, 'SERVER somehow.someday BADPASS 0 0AL :Somehow Server - McMurdo Station, Antarctica')
def testConnect(self):
self.proto.connect(self.irc)
self.proto.connect()
initial_messages = self.irc.takeMsgs()
commands = self.irc.takeCommands(initial_messages)
# SERVER pylink.unittest abcd 0 9PY :PyLink Service
@ -40,20 +40,20 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
def testHandleSQuit(self):
# Spawn a messy network map, just because!
self.proto.spawnServer(self.irc, 'level1.pylink', '34P')
self.proto.spawnServer(self.irc, 'level2.pylink', '34Q', uplink='34P')
self.proto.spawnServer(self.irc, 'level3.pylink', '34Z', uplink='34Q')
self.proto.spawnServer(self.irc, 'level4.pylink', '34Y', uplink='34Z')
self.proto.spawnServer('level1.pylink', '34P')
self.proto.spawnServer('level2.pylink', '34Q', uplink='34P')
self.proto.spawnServer('level3.pylink', '34Z', uplink='34Q')
self.proto.spawnServer('level4.pylink', '34Y', uplink='34Z')
self.assertEqual(self.irc.servers['34Y'].uplink, '34Z')
s4u = self.proto.spawnClient(self.irc, 'person1', 'person', 'users.overdrive.pw', server='34Y').uid
s3u = self.proto.spawnClient(self.irc, 'person2', 'person', 'users.overdrive.pw', server='34Z').uid
self.proto.joinClient(self.irc, s3u, '#pylink')
self.proto.joinClient(self.irc, s4u, '#pylink')
s4u = self.proto.spawnClient('person1', 'person', 'users.overdrive.pw', server='34Y').uid
s3u = self.proto.spawnClient('person2', 'person', 'users.overdrive.pw', server='34Z').uid
self.proto.joinClient(s3u, '#pylink')
self.proto.joinClient(s4u, '#pylink')
self.irc.run(':34Z SQUIT 34Y :random squit messsage')
self.assertNotIn(s4u, self.irc.users)
self.assertNotIn('34Y', self.irc.servers)
# Netsplits are obviously recursive, so all these should be removed.
self.proto.handle_squit(self.irc, '9PY', 'SQUIT', ['34P'])
self.proto.handle_squit('9PY', 'SQUIT', ['34P'])
self.assertNotIn(s3u, self.irc.users)
self.assertNotIn('34P', self.irc.servers)
self.assertNotIn('34Q', self.irc.servers)
@ -136,11 +136,16 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
self.assertEqual(expected, hookdata)
def testHandleFMode(self):
# Default channels start with +nt
self.irc.run(':70M FMODE #pylink 1423790412 -nt')
self.assertEqual(set(), self.irc.channels['#pylink'].modes)
self.irc.takeHooks()
self.irc.run(':70M FMODE #pylink 1423790412 +ikl herebedragons 100')
self.assertEqual({('i', None), ('k', 'herebedragons'), ('l', '100')}, self.irc.channels['#pylink'].modes)
self.irc.run(':70M FMODE #pylink 1423790413 -ilk+m herebedragons')
self.assertEqual({('m', None)}, self.irc.channels['#pylink'].modes)
hookdata = self.irc.takeHooks()
expected = [['70M', 'FMODE', {'target': '#pylink', 'modes':
[('+i', None), ('+k', 'herebedragons'),
@ -175,9 +180,10 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
def testHandleFModeRemovesOldParams(self):
self.irc.run(':70M FMODE #pylink 1423790412 +l 50')
self.assertEqual({('l', '50')}, self.irc.channels['#pylink'].modes)
self.assertIn(('l', '50'), self.irc.channels['#pylink'].modes)
self.irc.run(':70M FMODE #pylink 1423790412 +l 30')
self.assertEqual({('l', '30')}, self.irc.channels['#pylink'].modes)
self.assertIn(('l', '30'), self.irc.channels['#pylink'].modes)
self.assertNotIn(('l', '50'), self.irc.channels['#pylink'].modes)
hookdata = self.irc.takeHooks()
expected = [['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '50')], 'ts': 1423790412}],
['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '30')], 'ts': 1423790412}]]

View File

@ -12,13 +12,13 @@ def dummyf():
class TestRelay(unittest.TestCase):
def setUp(self):
self.irc = classes.FakeIRC('unittest', classes.FakeProto())
self.irc = classes.FakeIRC('fakeirc', classes.FakeProto)
self.irc.maxnicklen = 20
self.f = lambda nick: relay.normalizeNick(self.irc, 'unittest', nick)
# Fake our protocol name to something that supports slashes in nicks.
# relay uses a whitelist for this to prevent accidentally introducing
# bad nicks:
self.irc.proto.__name__ = "inspircd"
self.irc.protoname = "inspircd"
def testNormalizeNick(self):
# Second argument simply states the suffix.
@ -36,10 +36,10 @@ class TestRelay(unittest.TestCase):
self.assertEqual(self.f('helloworld'), 'helloworl///unittest')
def testNormalizeNickRemovesSlashes(self):
self.irc.proto.__name__ = "charybdis"
self.irc.protoname = "charybdis"
try:
self.assertEqual(self.f('helloworld'), 'helloworld|unittest')
self.assertEqual(self.f('abcde/eJanus'), 'abcde|eJanu|unittest')
self.assertEqual(self.f('ObnoxiouslyLongNick'), 'Obnoxiously|unittest')
finally:
self.irc.proto.__name__ = "inspircd"
self.irc.protoname = "inspircd"

View File

@ -13,7 +13,7 @@ def dummyf():
class TestUtils(unittest.TestCase):
def setUp(self):
self.irc = classes.FakeIRC('fakeirc', classes.FakeProto())
self.irc = classes.FakeIRC('fakeirc', classes.FakeProto)
def testTS6UIDGenerator(self):
uidgen = utils.TS6UIDGenerator('9PY')

View File

@ -17,80 +17,82 @@ class PluginTestCase(unittest.TestCase):
self.u = self.irc.pseudoclient.uid
self.maxDiff = None
# Dummy servers/users used in tests below.
self.proto.spawnServer(self.irc, 'whatever.', sid='10X')
self.proto.spawnServer('whatever.', sid='10X')
for x in range(3):
self.proto.spawnClient(self.irc, 'user%s' % x, server='10X')
self.proto.spawnClient('user%s' % x, server='10X')
class CommonProtoTestCase(PluginTestCase):
def testJoinClient(self):
u = self.u
self.proto.joinClient(self.irc, u, '#Channel')
self.proto.joinClient(u, '#Channel')
self.assertIn(u, self.irc.channels['#channel'].users)
# Non-existant user.
self.assertRaises(LookupError, self.proto.joinClient, self.irc, '9PYZZZZZZ', '#test')
self.assertRaises(LookupError, self.proto.joinClient, '9PYZZZZZZ', '#test')
def testKickClient(self):
target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid
self.proto.joinClient(self.irc, target, '#pylink')
target = self.proto.spawnClient('soccerball', 'soccerball', 'abcd').uid
self.proto.joinClient(target, '#pylink')
self.assertIn(self.u, self.irc.channels['#pylink'].users)
self.assertIn(target, self.irc.channels['#pylink'].users)
self.proto.kickClient(self.irc, self.u, '#pylink', target, 'Pow!')
self.proto.kickClient(self.u, '#pylink', target, 'Pow!')
self.assertNotIn(target, self.irc.channels['#pylink'].users)
def testModeClient(self):
testuser = self.proto.spawnClient(self.irc, 'testcakes')
testuser = self.proto.spawnClient('testcakes')
self.irc.takeMsgs()
self.proto.modeClient(self.irc, self.u, testuser.uid, [('+i', None), ('+w', None)])
self.proto.modeClient(self.u, testuser.uid, [('+i', None), ('+w', None)])
self.assertEqual({('i', None), ('w', None)}, testuser.modes)
self.proto.modeClient(self.irc, self.u, '#pylink', [('+s', None), ('+l', '30')])
self.assertEqual({('s', None), ('l', '30')}, self.irc.channels['#pylink'].modes)
# Default channels start with +nt
self.assertEqual({('n', None), ('t', None)}, self.irc.channels['#pylink'].modes)
self.proto.modeClient(self.u, '#pylink', [('+s', None), ('+l', '30')])
self.assertEqual({('s', None), ('l', '30'), ('n', None), ('t', None)}, self.irc.channels['#pylink'].modes)
cmds = self.irc.takeCommands(self.irc.takeMsgs())
self.assertEqual(cmds, ['MODE', 'FMODE'])
def testNickClient(self):
self.proto.nickClient(self.irc, self.u, 'NotPyLink')
self.proto.nickClient(self.u, 'NotPyLink')
self.assertEqual('NotPyLink', self.irc.users[self.u].nick)
def testPartClient(self):
u = self.u
self.proto.joinClient(self.irc, u, '#channel')
self.proto.partClient(self.irc, u, '#channel')
self.proto.joinClient(u, '#channel')
self.proto.partClient(u, '#channel')
self.assertNotIn(u, self.irc.channels['#channel'].users)
def testQuitClient(self):
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
self.proto.joinClient(self.irc, u, '#channel')
self.assertRaises(LookupError, self.proto.quitClient, self.irc, '9PYZZZZZZ', 'quit reason')
self.proto.quitClient(self.irc, u, 'quit reason')
u = self.proto.spawnClient('testuser3', 'moo', 'hello.world').uid
self.proto.joinClient(u, '#channel')
self.assertRaises(LookupError, self.proto.quitClient, '9PYZZZZZZ', 'quit reason')
self.proto.quitClient(u, 'quit reason')
self.assertNotIn(u, self.irc.channels['#channel'].users)
self.assertNotIn(u, self.irc.users)
self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
def testSpawnClient(self):
u = self.proto.spawnClient(self.irc, 'testuser3', 'moo', 'hello.world').uid
u = self.proto.spawnClient('testuser3', 'moo', 'hello.world').uid
# Check the server index and the user index
self.assertIn(u, self.irc.servers[self.irc.sid].users)
self.assertIn(u, self.irc.users)
# Raise ValueError when trying to spawn a client on a server that's not ours
self.assertRaises(ValueError, self.proto.spawnClient, self.irc, 'abcd', 'user', 'dummy.user.net', server='44A')
self.assertRaises(ValueError, self.proto.spawnClient, 'abcd', 'user', 'dummy.user.net', server='44A')
# Unfilled args should get placeholder fields and not error.
self.proto.spawnClient(self.irc, 'testuser4')
self.proto.spawnClient('testuser4')
def testSpawnClientOnServer(self):
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
u = self.proto.spawnClient(self.irc, 'person1', 'person', 'users.overdrive.pw', server='34Q')
self.proto.spawnServer('subserver.pylink', '34Q')
u = self.proto.spawnClient('person1', 'person', 'users.overdrive.pw', server='34Q')
# We're spawning clients on the right server, hopefully...
self.assertIn(u.uid, self.irc.servers['34Q'].users)
self.assertNotIn(u.uid, self.irc.servers[self.irc.sid].users)
def testSpawnServer(self):
# Incorrect SID length
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'subserver.pylink', '34Q0')
self.proto.spawnServer(self.irc, 'subserver.pylink', '34Q')
self.assertRaises(Exception, self.proto.spawnServer, 'subserver.pylink', '34Q0')
self.proto.spawnServer('subserver.pylink', '34Q')
# Duplicate server name
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'Subserver.PyLink', '34Z')
self.assertRaises(Exception, self.proto.spawnServer, 'Subserver.PyLink', '34Z')
# Duplicate SID
self.assertRaises(Exception, self.proto.spawnServer, self.irc, 'another.Subserver.PyLink', '34Q')
self.assertRaises(Exception, self.proto.spawnServer, 'another.Subserver.PyLink', '34Q')
self.assertIn('34Q', self.irc.servers)

View File

@ -98,12 +98,6 @@ class TS6SIDGenerator():
self.increment()
return sid
def msg(irc, target, text, notice=False):
if notice:
irc.proto.noticeClient(irc, irc.pseudoclient.uid, target, text)
else:
irc.proto.messageClient(irc, irc.pseudoclient.uid, target, text)
def add_cmd(func, name=None):
if name is None:
name = func.__name__