mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-28 05:29:25 +01:00
Merge branch 'devel' into wip/document-everything
This commit is contained in:
commit
834136e848
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ __pycache__/
|
|||||||
*.save*
|
*.save*
|
||||||
*.db
|
*.db
|
||||||
*.pid
|
*.pid
|
||||||
|
*.pem
|
||||||
|
@ -4,7 +4,7 @@ PyLink is an extensible, plugin-based IRC PseudoService written in Python. It ai
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
**PyLink is a work in progress and thus may be very unstable**! No warranty is provided if this completely wrecks your network and causes widespread rioting throughout your user base!
|
**PyLink is a work in progress and thus may be very unstable**! No warranty is provided if this completely wrecks your network and causes widespread rioting amongst your users!
|
||||||
|
|
||||||
That said, please report any bugs you find to the [issue tracker](https://github.com/GLolol/PyLink/issues). Pull requests are open if you'd like to contribute.
|
That said, please report any bugs you find to the [issue tracker](https://github.com/GLolol/PyLink/issues). Pull requests are open if you'd like to contribute.
|
||||||
|
|
||||||
@ -17,8 +17,9 @@ Dependencies currently include:
|
|||||||
|
|
||||||
#### Supported IRCds
|
#### Supported IRCds
|
||||||
|
|
||||||
* InspIRCd 2.0.x - module: `inspircd`
|
* InspIRCd 2.0.x - module `inspircd`
|
||||||
* charybdis (3.5.x / git master) - module: `ts6`
|
* charybdis (3.5.x / git master) - module `ts6`
|
||||||
|
* Elemental-IRCd (6.6.x / git master) - module `ts6`
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ class IrcUser():
|
|||||||
|
|
||||||
self.identified = False
|
self.identified = False
|
||||||
self.channels = set()
|
self.channels = set()
|
||||||
|
self.away = ''
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.__dict__)
|
return repr(self.__dict__)
|
||||||
|
@ -53,6 +53,17 @@ servers:
|
|||||||
# PyLink might introduce a nick that is too long and cause netsplits!
|
# PyLink might introduce a nick that is too long and cause netsplits!
|
||||||
maxnicklen: 30
|
maxnicklen: 30
|
||||||
|
|
||||||
|
# Toggles SSL for this network. Defaults to false if not specified, and requires the
|
||||||
|
# ssl_certfile and ssl_keyfile options to work.
|
||||||
|
# ssl: true
|
||||||
|
|
||||||
|
# ssl_certfile: pylink-cert.pem
|
||||||
|
# ssl_keyfile: pylink-key.pem
|
||||||
|
|
||||||
|
# Optionally, you can set this option to verify the SSL certificate
|
||||||
|
# fingerprint (SHA1) of your uplink.
|
||||||
|
# ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc"
|
||||||
|
|
||||||
ts6net:
|
ts6net:
|
||||||
ip: 127.0.0.1
|
ip: 127.0.0.1
|
||||||
port: 7000
|
port: 7000
|
||||||
|
@ -20,7 +20,8 @@ utils.add_hook(handle_kick, 'KICK')
|
|||||||
# Handle commands sent to the PyLink client (PRIVMSG)
|
# Handle commands sent to the PyLink client (PRIVMSG)
|
||||||
def handle_commands(irc, source, command, args):
|
def handle_commands(irc, source, command, args):
|
||||||
if args['target'] == irc.pseudoclient.uid:
|
if args['target'] == irc.pseudoclient.uid:
|
||||||
cmd_args = args['text'].split(' ')
|
text = args['text'].strip()
|
||||||
|
cmd_args = text.split(' ')
|
||||||
cmd = cmd_args[0].lower()
|
cmd = cmd_args[0].lower()
|
||||||
cmd_args = cmd_args[1:]
|
cmd_args = cmd_args[1:]
|
||||||
try:
|
try:
|
||||||
|
96
main.py
96
main.py
@ -7,6 +7,8 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import threading
|
import threading
|
||||||
|
import ssl
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import conf
|
import conf
|
||||||
@ -17,6 +19,9 @@ import coreplugin
|
|||||||
class Irc():
|
class Irc():
|
||||||
|
|
||||||
def initVars(self):
|
def initVars(self):
|
||||||
|
self.pseudoclient = None
|
||||||
|
self.connected = threading.Event()
|
||||||
|
self.lastping = time.time()
|
||||||
# Server, channel, and user indexes to be populated by our protocol module
|
# Server, channel, and user indexes to be populated by our protocol module
|
||||||
self.servers = {self.sid: classes.IrcServer(None, self.serverdata['hostname'], internal=True)}
|
self.servers = {self.sid: classes.IrcServer(None, self.serverdata['hostname'], internal=True)}
|
||||||
self.users = {}
|
self.users = {}
|
||||||
@ -52,7 +57,6 @@ class Irc():
|
|||||||
|
|
||||||
def __init__(self, netname, proto, conf):
|
def __init__(self, netname, proto, conf):
|
||||||
# Initialize some variables
|
# Initialize some variables
|
||||||
self.connected = threading.Event()
|
|
||||||
self.name = netname.lower()
|
self.name = netname.lower()
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.serverdata = conf['servers'][netname]
|
self.serverdata = conf['servers'][netname]
|
||||||
@ -67,26 +71,78 @@ class Irc():
|
|||||||
self.connection_thread = threading.Thread(target = self.connect)
|
self.connection_thread = threading.Thread(target = self.connect)
|
||||||
self.connection_thread.start()
|
self.connection_thread.start()
|
||||||
self.pingTimer = None
|
self.pingTimer = None
|
||||||
self.lastping = time.time()
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
ip = self.serverdata["ip"]
|
ip = self.serverdata["ip"]
|
||||||
port = self.serverdata["port"]
|
port = self.serverdata["port"]
|
||||||
while True:
|
while True:
|
||||||
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
|
||||||
self.initVars()
|
self.initVars()
|
||||||
|
checks_ok = True
|
||||||
try:
|
try:
|
||||||
|
self.socket = socket.socket()
|
||||||
|
self.socket.setblocking(0)
|
||||||
# Initial connection timeout is a lot smaller than the timeout after
|
# Initial connection timeout is a lot smaller than the timeout after
|
||||||
# we've connected; this is intentional.
|
# we've connected; this is intentional.
|
||||||
self.socket = socket.create_connection((ip, port), timeout=self.pingfreq)
|
self.socket.settimeout(self.pingfreq)
|
||||||
self.socket.setblocking(0)
|
self.ssl = self.serverdata.get('ssl')
|
||||||
|
if self.ssl:
|
||||||
|
log.info('(%s) Attempting SSL for this connection...', self.name)
|
||||||
|
certfile = self.serverdata.get('ssl_certfile')
|
||||||
|
keyfile = self.serverdata.get('ssl_keyfile')
|
||||||
|
if certfile and keyfile:
|
||||||
|
try:
|
||||||
|
self.socket = ssl.wrap_socket(self.socket,
|
||||||
|
certfile=certfile,
|
||||||
|
keyfile=keyfile)
|
||||||
|
except OSError:
|
||||||
|
log.exception('(%s) Caught OSError trying to '
|
||||||
|
'initialize the SSL connection; '
|
||||||
|
'are "ssl_certfile" and '
|
||||||
|
'"ssl_keyfile" set correctly?',
|
||||||
|
self.name)
|
||||||
|
checks_ok = False
|
||||||
|
else:
|
||||||
|
log.error('(%s) SSL certfile/keyfile was not set '
|
||||||
|
'correctly, aborting... ', self.name)
|
||||||
|
checks_ok = False
|
||||||
|
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
||||||
|
self.socket.connect((ip, port))
|
||||||
self.socket.settimeout(self.pingtimeout)
|
self.socket.settimeout(self.pingtimeout)
|
||||||
self.proto.connect(self)
|
|
||||||
self.spawnMain()
|
if self.ssl and checks_ok:
|
||||||
log.info('(%s) Starting ping schedulers....', self.name)
|
peercert = self.socket.getpeercert(binary_form=True)
|
||||||
self.schedulePing()
|
sha1fp = hashlib.sha1(peercert).hexdigest()
|
||||||
log.info('(%s) Server ready; listening for data.', self.name)
|
expected_fp = self.serverdata.get('ssl_fingerprint')
|
||||||
self.run()
|
if expected_fp:
|
||||||
|
if sha1fp != expected_fp:
|
||||||
|
log.error('(%s) Uplink\'s SSL certificate '
|
||||||
|
'fingerprint (SHA1) does not match the '
|
||||||
|
'one configured: expected %r, got %r; '
|
||||||
|
'disconnecting...', self.name,
|
||||||
|
expected_fp, sha1fp)
|
||||||
|
checks_ok = False
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink SSL certificate fingerprint '
|
||||||
|
'(SHA1) verified: %r', self.name, sha1fp)
|
||||||
|
else:
|
||||||
|
log.info('(%s) Uplink\'s SSL certificate fingerprint '
|
||||||
|
'is %r. You can enhance the security of your '
|
||||||
|
'link by specifying this in a "ssl_fingerprint"'
|
||||||
|
' option in your server block.', self.name,
|
||||||
|
sha1fp)
|
||||||
|
|
||||||
|
if checks_ok:
|
||||||
|
self.proto.connect(self)
|
||||||
|
self.spawnMain()
|
||||||
|
log.info('(%s) Starting ping schedulers....', self.name)
|
||||||
|
self.schedulePing()
|
||||||
|
log.info('(%s) Server ready; listening for data.', self.name)
|
||||||
|
self.run()
|
||||||
|
else:
|
||||||
|
log.error('(%s) A configuration error was encountered '
|
||||||
|
'trying to set up this connection. Please check'
|
||||||
|
' your configuration file and try again.',
|
||||||
|
self.name)
|
||||||
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
||||||
log.warning('(%s) Disconnected from IRC: %s: %s',
|
log.warning('(%s) Disconnected from IRC: %s: %s',
|
||||||
self.name, type(e).__name__, str(e))
|
self.name, type(e).__name__, str(e))
|
||||||
@ -107,19 +163,21 @@ class Irc():
|
|||||||
self.pingTimer.cancel()
|
self.pingTimer.cancel()
|
||||||
except: # Socket timed out during creation; ignore
|
except: # Socket timed out during creation; ignore
|
||||||
pass
|
pass
|
||||||
|
# Internal hook signifying that a network has disconnected.
|
||||||
self.callHooks([None, 'PYLINK_DISCONNECT', {}])
|
self.callHooks([None, 'PYLINK_DISCONNECT', {}])
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
buf = b""
|
buf = b""
|
||||||
data = b""
|
data = b""
|
||||||
while (time.time() - self.lastping) < self.pingtimeout:
|
while True:
|
||||||
log.debug('(%s) time_since_last_ping: %s', self.name, (time.time() - self.lastping))
|
|
||||||
log.debug('(%s) self.pingtimeout: %s', self.name, self.pingtimeout)
|
|
||||||
data = self.socket.recv(2048)
|
data = self.socket.recv(2048)
|
||||||
buf += data
|
buf += data
|
||||||
if self.connected and not data:
|
if self.connected.is_set() and not data:
|
||||||
log.warn('(%s) No data received and self.connected is not set; disconnecting!', self.name)
|
log.warning('(%s) No data received and self.connected is set; disconnecting!', self.name)
|
||||||
break
|
return
|
||||||
|
elif (time.time() - self.lastping) > self.pingtimeout:
|
||||||
|
log.warning('(%s) Connection timed out.', self.name)
|
||||||
|
return
|
||||||
while b'\n' in buf:
|
while b'\n' in buf:
|
||||||
line, buf = buf.split(b'\n', 1)
|
line, buf = buf.split(b'\n', 1)
|
||||||
line = line.strip(b'\r')
|
line = line.strip(b'\r')
|
||||||
@ -188,9 +246,13 @@ class Irc():
|
|||||||
ident = self.botdata.get('ident') or 'pylink'
|
ident = self.botdata.get('ident') or 'pylink'
|
||||||
host = self.serverdata["hostname"]
|
host = self.serverdata["hostname"]
|
||||||
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
|
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(self, nick, ident, host, modes={("+o", None)})
|
||||||
for chan in self.serverdata['channels']:
|
for chan in self.serverdata['channels']:
|
||||||
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
||||||
|
# PyLink internal hook called when spawnMain is called and the
|
||||||
|
# contents of Irc().pseudoclient change.
|
||||||
|
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log.info('PyLink starting...')
|
log.info('PyLink starting...')
|
||||||
|
@ -62,6 +62,7 @@ def quit(irc, source, args):
|
|||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
quitmsg = ' '.join(args[1:]) or 'Client quit'
|
quitmsg = ' '.join(args[1:]) or 'Client quit'
|
||||||
irc.proto.quitClient(irc, u, quitmsg)
|
irc.proto.quitClient(irc, u, quitmsg)
|
||||||
|
irc.callHooks([u, 'PYLINK_ADMIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
||||||
|
|
||||||
def joinclient(irc, source, args):
|
def joinclient(irc, source, args):
|
||||||
"""<target> <channel1>,[<channel2>], etc.
|
"""<target> <channel1>,[<channel2>], etc.
|
||||||
@ -82,6 +83,9 @@ def joinclient(irc, source, args):
|
|||||||
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
||||||
return
|
return
|
||||||
irc.proto.joinClient(irc, u, channel)
|
irc.proto.joinClient(irc, u, channel)
|
||||||
|
irc.callHooks([u, 'PYLINK_ADMIN_JOIN', {'channel': channel, 'users': [u],
|
||||||
|
'modes': irc.channels[channel].modes,
|
||||||
|
'parse_as': 'JOIN'}])
|
||||||
utils.add_cmd(joinclient, name='join')
|
utils.add_cmd(joinclient, name='join')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
@ -103,6 +107,7 @@ def nick(irc, source, args):
|
|||||||
utils.msg(irc, source, 'Error: Invalid nickname %r.' % newnick)
|
utils.msg(irc, source, 'Error: Invalid nickname %r.' % newnick)
|
||||||
return
|
return
|
||||||
irc.proto.nickClient(irc, u, newnick)
|
irc.proto.nickClient(irc, u, newnick)
|
||||||
|
irc.callHooks([u, 'PYLINK_ADMIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def part(irc, source, args):
|
def part(irc, source, args):
|
||||||
@ -123,6 +128,7 @@ def part(irc, source, args):
|
|||||||
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
||||||
return
|
return
|
||||||
irc.proto.partClient(irc, u, channel, reason)
|
irc.proto.partClient(irc, u, channel, reason)
|
||||||
|
irc.callHooks([u, 'PYLINK_ADMIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def kick(irc, source, args):
|
def kick(irc, source, args):
|
||||||
@ -144,6 +150,7 @@ def kick(irc, source, args):
|
|||||||
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
|
||||||
return
|
return
|
||||||
irc.proto.kickClient(irc, u, channel, targetu, reason)
|
irc.proto.kickClient(irc, u, channel, targetu, reason)
|
||||||
|
irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def showuser(irc, source, args):
|
def showuser(irc, source, args):
|
||||||
@ -186,7 +193,7 @@ def showchan(irc, source, args):
|
|||||||
def mode(irc, source, args):
|
def mode(irc, source, args):
|
||||||
"""<source> <target> <modes>
|
"""<source> <target> <modes>
|
||||||
|
|
||||||
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is the nick of a PyLink client."""
|
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is either the nick of a PyLink client, or the SID of a PyLink server."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
modesource, target, modes = args[0], args[1], args[2:]
|
modesource, target, modes = args[0], args[1], args[2:]
|
||||||
@ -205,6 +212,36 @@ def mode(irc, source, args):
|
|||||||
return
|
return
|
||||||
if utils.isInternalServer(irc, modesource):
|
if utils.isInternalServer(irc, modesource):
|
||||||
irc.proto.modeServer(irc, modesource, target, parsedmodes)
|
irc.proto.modeServer(irc, modesource, target, parsedmodes)
|
||||||
|
irc.callHooks([modesource, 'PYLINK_ADMIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
|
||||||
else:
|
else:
|
||||||
sourceuid = utils.nickToUid(irc, modesource)
|
sourceuid = utils.nickToUid(irc, modesource)
|
||||||
irc.proto.modeClient(irc, sourceuid, target, parsedmodes)
|
irc.proto.modeClient(irc, sourceuid, target, parsedmodes)
|
||||||
|
irc.callHooks([sourceuid, 'PYLINK_ADMIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def msg(irc, source, args):
|
||||||
|
"""<source> <target> <text>
|
||||||
|
|
||||||
|
Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client."""
|
||||||
|
checkauthenticated(irc, source)
|
||||||
|
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.')
|
||||||
|
return
|
||||||
|
sourceuid = utils.nickToUid(irc, msgsource)
|
||||||
|
if not sourceuid:
|
||||||
|
utils.msg(irc, 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)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
real_target = target
|
||||||
|
if not text:
|
||||||
|
utils.msg(irc, source, 'Error: no text given.')
|
||||||
|
return
|
||||||
|
irc.proto.messageClient(irc, sourceuid, real_target, text)
|
||||||
|
irc.callHooks([sourceuid, 'PYLINK_ADMIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
||||||
|
287
plugins/relay.py
287
plugins/relay.py
@ -16,7 +16,9 @@ dbname = "pylinkrelay"
|
|||||||
if confname != 'pylink':
|
if confname != 'pylink':
|
||||||
dbname += '-%s' % confname
|
dbname += '-%s' % confname
|
||||||
dbname += '.db'
|
dbname += '.db'
|
||||||
|
|
||||||
relayusers = defaultdict(dict)
|
relayusers = defaultdict(dict)
|
||||||
|
spawnlocks = defaultdict(threading.Lock)
|
||||||
|
|
||||||
def relayWhoisHandlers(irc, target):
|
def relayWhoisHandlers(irc, target):
|
||||||
user = irc.users[target]
|
user = irc.users[target]
|
||||||
@ -32,7 +34,6 @@ utils.whois_handlers.append(relayWhoisHandlers)
|
|||||||
def normalizeNick(irc, netname, nick, separator=None, oldnick=''):
|
def normalizeNick(irc, netname, nick, separator=None, oldnick=''):
|
||||||
separator = separator or irc.serverdata.get('separator') or "/"
|
separator = separator or irc.serverdata.get('separator') or "/"
|
||||||
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
|
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
|
||||||
|
|
||||||
orig_nick = nick
|
orig_nick = nick
|
||||||
protoname = irc.proto.__name__
|
protoname = irc.proto.__name__
|
||||||
maxnicklen = irc.maxnicklen
|
maxnicklen = irc.maxnicklen
|
||||||
@ -59,14 +60,14 @@ def normalizeNick(irc, netname, nick, separator=None, oldnick=''):
|
|||||||
nick += suffix
|
nick += suffix
|
||||||
# FIXME: factorize
|
# FIXME: factorize
|
||||||
while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \
|
while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \
|
||||||
utils.isInternalClient(irc, utils.nickToUid(irc, nick)):
|
isRelayClient(irc, utils.nickToUid(irc, nick)):
|
||||||
# The nick we want exists? Darn, create another one then, but only if
|
# The nick we want exists? Darn, create another one then, but only if
|
||||||
# the target isn't an internal client!
|
# the target isn't an internal client!
|
||||||
# Increase the separator length by 1 if the user was already tagged,
|
# Increase the separator length by 1 if the user was already tagged,
|
||||||
# but couldn't be created due to a nick conflict.
|
# but couldn't be created due to a nick conflict.
|
||||||
# This can happen when someone steals a relay user's nick.
|
# This can happen when someone steals a relay user's nick.
|
||||||
new_sep = separator + separator[-1]
|
new_sep = separator + separator[-1]
|
||||||
log.debug('(%s) normalizeNick: using %r as new_sep.', irc.name, separator)
|
log.debug('(%s) normalizeNick: nick %r is in use; using %r as new_sep.', irc.name, nick, new_sep)
|
||||||
nick = normalizeNick(irc, netname, orig_nick, separator=new_sep)
|
nick = normalizeNick(irc, netname, orig_nick, separator=new_sep)
|
||||||
finalLength = len(nick)
|
finalLength = len(nick)
|
||||||
assert finalLength <= maxnicklen, "Normalized nick %r went over max " \
|
assert finalLength <= maxnicklen, "Normalized nick %r went over max " \
|
||||||
@ -109,7 +110,8 @@ def getPrefixModes(irc, remoteirc, channel, user):
|
|||||||
for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
||||||
if pmode in remoteirc.cmodes: # Mode supported by IRCd
|
if pmode in remoteirc.cmodes: # Mode supported by IRCd
|
||||||
mlist = irc.channels[channel].prefixmodes[pmode+'s']
|
mlist = irc.channels[channel].prefixmodes[pmode+'s']
|
||||||
log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
|
log.debug('(%s) getPrefixModes: checking if %r is in %s list: %r',
|
||||||
|
irc.name, user, pmode, mlist)
|
||||||
if user in mlist:
|
if user in mlist:
|
||||||
modes += remoteirc.cmodes[pmode]
|
modes += remoteirc.cmodes[pmode]
|
||||||
return modes
|
return modes
|
||||||
@ -123,27 +125,31 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
|||||||
return remoteirc.pseudoclient.uid
|
return remoteirc.pseudoclient.uid
|
||||||
except AttributeError: # Network hasn't been initialized yet?
|
except AttributeError: # Network hasn't been initialized yet?
|
||||||
pass
|
pass
|
||||||
try:
|
with spawnlocks[irc.name]:
|
||||||
u = relayusers[(irc.name, user)][remoteirc.name]
|
try:
|
||||||
except KeyError:
|
u = relayusers[(irc.name, user)][remoteirc.name]
|
||||||
userobj = irc.users.get(user)
|
except KeyError:
|
||||||
if userobj is None or (not spawnIfMissing) or (not remoteirc.connected.is_set()):
|
userobj = irc.users.get(user)
|
||||||
# The query wasn't actually a valid user, or the network hasn't
|
if userobj is None or (not spawnIfMissing) or (not remoteirc.connected.is_set()):
|
||||||
# been connected yet... Oh well!
|
# The query wasn't actually a valid user, or the network hasn't
|
||||||
return
|
# been connected yet... Oh well!
|
||||||
nick = normalizeNick(remoteirc, irc.name, userobj.nick)
|
return
|
||||||
# Truncate idents at 10 characters, because TS6 won't like them otherwise!
|
nick = normalizeNick(remoteirc, irc.name, userobj.nick)
|
||||||
ident = userobj.ident[:10]
|
# Truncate idents at 10 characters, because TS6 won't like them otherwise!
|
||||||
# Ditto hostname at 64 chars.
|
ident = userobj.ident[:10]
|
||||||
host = userobj.host[:64]
|
# Ditto hostname at 64 chars.
|
||||||
realname = userobj.realname
|
host = userobj.host[:64]
|
||||||
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
realname = userobj.realname
|
||||||
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
||||||
host=host, realname=realname,
|
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
||||||
modes=modes, ts=userobj.ts).uid
|
host=host, realname=realname,
|
||||||
remoteirc.users[u].remote = irc.name
|
modes=modes, ts=userobj.ts).uid
|
||||||
relayusers[(irc.name, user)][remoteirc.name] = u
|
remoteirc.users[u].remote = (irc.name, user)
|
||||||
return u
|
away = userobj.away
|
||||||
|
if away:
|
||||||
|
remoteirc.proto.awayClient(remoteirc, u, away)
|
||||||
|
relayusers[(irc.name, user)][remoteirc.name] = u
|
||||||
|
return u
|
||||||
|
|
||||||
def getLocalUser(irc, user, targetirc=None):
|
def getLocalUser(irc, user, targetirc=None):
|
||||||
"""<irc object> <pseudoclient uid> [<target irc object>]
|
"""<irc object> <pseudoclient uid> [<target irc object>]
|
||||||
@ -156,23 +162,10 @@ def getLocalUser(irc, user, targetirc=None):
|
|||||||
representing the original user on the target network, similar to what
|
representing the original user on the target network, similar to what
|
||||||
getRemoteUser() does."""
|
getRemoteUser() does."""
|
||||||
# First, iterate over everyone!
|
# First, iterate over everyone!
|
||||||
remoteuser = None
|
try:
|
||||||
for k, v in relayusers.items():
|
remoteuser = irc.users[user].remote
|
||||||
log.debug('(%s) getLocalUser: processing %s, %s in relayusers', irc.name, k, v)
|
except (AttributeError, KeyError):
|
||||||
if k[0] == irc.name:
|
remoteuser = None
|
||||||
# We don't need to do anything if the target users is on
|
|
||||||
# the same network as us.
|
|
||||||
log.debug('(%s) getLocalUser: skipping %s since the target network matches the source network.', irc.name, k)
|
|
||||||
continue
|
|
||||||
if v.get(irc.name) == user:
|
|
||||||
# If the stored pseudoclient UID for the kicked user on
|
|
||||||
# this network matches the target we have, set that user
|
|
||||||
# as the one we're kicking! It's a handful, but remember
|
|
||||||
# we're mapping (home network, UID) pairs to their
|
|
||||||
# respective relay pseudoclients on other networks.
|
|
||||||
remoteuser = k
|
|
||||||
log.debug('(%s) getLocalUser: found %s to correspond to %s.', irc.name, v, k)
|
|
||||||
break
|
|
||||||
log.debug('(%s) getLocalUser: remoteuser set to %r (looking up %s/%s).', irc.name, remoteuser, user, irc.name)
|
log.debug('(%s) getLocalUser: remoteuser set to %r (looking up %s/%s).', irc.name, remoteuser, user, irc.name)
|
||||||
if remoteuser:
|
if remoteuser:
|
||||||
# If targetirc is given, we'll return simply the UID of the user on the
|
# If targetirc is given, we'll return simply the UID of the user on the
|
||||||
@ -221,6 +214,11 @@ def initializeChannel(irc, channel):
|
|||||||
log.debug('(%s) initializeChannel: relay pair found to be %s', irc.name, relay)
|
log.debug('(%s) initializeChannel: relay pair found to be %s', irc.name, relay)
|
||||||
queued_users = []
|
queued_users = []
|
||||||
if relay:
|
if relay:
|
||||||
|
# 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)
|
||||||
|
|
||||||
all_links = db[relay]['links'].copy()
|
all_links = db[relay]['links'].copy()
|
||||||
all_links.update((relay,))
|
all_links.update((relay,))
|
||||||
log.debug('(%s) initializeChannel: all_links: %s', irc.name, all_links)
|
log.debug('(%s) initializeChannel: all_links: %s', irc.name, all_links)
|
||||||
@ -237,37 +235,30 @@ def initializeChannel(irc, channel):
|
|||||||
if not (remoteirc.connected.is_set() and findRemoteChan(remoteirc, irc, remotechan)):
|
if not (remoteirc.connected.is_set() and findRemoteChan(remoteirc, irc, remotechan)):
|
||||||
continue # They aren't connected, don't bother!
|
continue # They aren't connected, don't bother!
|
||||||
# Join their (remote) users and set their modes.
|
# Join their (remote) users and set their modes.
|
||||||
relayJoins(remoteirc, remotechan, rc.users,
|
relayJoins(remoteirc, remotechan, rc.users, rc.ts)
|
||||||
rc.ts, rc.modes)
|
relayModes(remoteirc, irc, remoteirc.sid, remotechan, rc.modes)
|
||||||
relayModes(irc, remoteirc, irc.sid, channel)
|
relayModes(irc, remoteirc, irc.sid, channel, modes)
|
||||||
topic = remoteirc.channels[relay[1]].topic
|
topic = remoteirc.channels[remotechan].topic
|
||||||
# Only update the topic if it's different from what we already have,
|
# Only update the topic if it's different from what we already have,
|
||||||
# and topic bursting is complete.
|
# and topic bursting is complete.
|
||||||
if remoteirc.channels[channel].topicset and topic != irc.channels[channel].topic:
|
if remoteirc.channels[remotechan].topicset and topic != irc.channels[channel].topic:
|
||||||
irc.proto.topicServer(irc, irc.sid, channel, topic)
|
irc.proto.topicServer(irc, irc.sid, channel, topic)
|
||||||
|
|
||||||
log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users)
|
|
||||||
# After that's done, we'll send our users to them.
|
|
||||||
relayJoins(irc, channel, c.users, c.ts, c.modes)
|
|
||||||
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
|
|
||||||
|
|
||||||
def handle_join(irc, numeric, command, args):
|
def handle_join(irc, numeric, command, args):
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
if not findRelay((irc.name, channel)):
|
if not findRelay((irc.name, channel)):
|
||||||
# No relay here, return.
|
# No relay here, return.
|
||||||
return
|
return
|
||||||
modes = args['modes']
|
|
||||||
ts = args['ts']
|
ts = args['ts']
|
||||||
users = set(args['users'])
|
users = set(args['users'])
|
||||||
relayJoins(irc, channel, users, ts, modes)
|
relayJoins(irc, channel, users, ts)
|
||||||
utils.add_hook(handle_join, 'JOIN')
|
utils.add_hook(handle_join, 'JOIN')
|
||||||
|
|
||||||
def handle_quit(irc, numeric, command, args):
|
def handle_quit(irc, numeric, command, args):
|
||||||
ouruser = numeric
|
|
||||||
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = utils.networkobjects[netname]
|
||||||
remoteirc.proto.quitClient(remoteirc, user, args['text'])
|
remoteirc.proto.quitClient(remoteirc, user, args['text'])
|
||||||
del relayusers[(irc.name, ouruser)]
|
del relayusers[(irc.name, numeric)]
|
||||||
utils.add_hook(handle_quit, 'QUIT')
|
utils.add_hook(handle_quit, 'QUIT')
|
||||||
|
|
||||||
def handle_squit(irc, numeric, command, args):
|
def handle_squit(irc, numeric, command, args):
|
||||||
@ -288,6 +279,9 @@ utils.add_hook(handle_nick, 'NICK')
|
|||||||
def handle_part(irc, numeric, command, args):
|
def handle_part(irc, numeric, command, args):
|
||||||
channels = args['channels']
|
channels = args['channels']
|
||||||
text = args['text']
|
text = args['text']
|
||||||
|
# Don't allow the PyLink client PARTing to be relayed.
|
||||||
|
if numeric == irc.pseudoclient.uid:
|
||||||
|
return
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = utils.networkobjects[netname]
|
||||||
@ -306,57 +300,55 @@ def handle_privmsg(irc, numeric, command, args):
|
|||||||
text = args['text']
|
text = args['text']
|
||||||
if target == irc.pseudoclient.uid:
|
if target == irc.pseudoclient.uid:
|
||||||
return
|
return
|
||||||
sent = 0
|
|
||||||
relay = findRelay((irc.name, target))
|
relay = findRelay((irc.name, target))
|
||||||
# Don't send any "you must be in common channels" if we're not part
|
remoteusers = relayusers[(irc.name, numeric)]
|
||||||
# of a relay, or we are but there are no links!
|
# HACK: Don't break on sending to @#channel or similar.
|
||||||
remoteusers = relayusers[(irc.name, numeric)].items()
|
try:
|
||||||
'''
|
prefix, target = target.split('#', 1)
|
||||||
if utils.isChannel(target) and ((relay and not db[relay]['links']) or \
|
except ValueError:
|
||||||
relay is None):
|
prefix = ''
|
||||||
|
else:
|
||||||
|
target = '#' + target
|
||||||
|
log.debug('(%s) relay privmsg: prefix is %r, target is %r', irc.name, prefix, target)
|
||||||
|
if utils.isChannel(target) and relay and numeric not in irc.channels[target].users:
|
||||||
|
# The sender must be in the target channel to send messages over the relay;
|
||||||
|
# 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 '
|
||||||
|
'messages over the relay.' % target, notice=True)
|
||||||
return
|
return
|
||||||
'''
|
if utils.isChannel(target):
|
||||||
if not remoteusers:
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
return
|
remoteirc = utils.networkobjects[netname]
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
|
||||||
remoteirc = utils.networkobjects[netname]
|
|
||||||
# HACK: Don't break on sending to @#channel or similar.
|
|
||||||
try:
|
|
||||||
prefix, target = target.split('#', 1)
|
|
||||||
except ValueError:
|
|
||||||
prefix = ''
|
|
||||||
else:
|
|
||||||
target = '#' + target
|
|
||||||
if utils.isChannel(target):
|
|
||||||
log.debug('(%s) relay privmsg: prefix is %r, target is %r', irc.name, prefix, target)
|
|
||||||
real_target = findRemoteChan(irc, remoteirc, target)
|
real_target = findRemoteChan(irc, remoteirc, target)
|
||||||
if not real_target:
|
if not real_target:
|
||||||
continue
|
continue
|
||||||
real_target = prefix + real_target
|
real_target = prefix + real_target
|
||||||
else:
|
if notice:
|
||||||
remoteuser = getLocalUser(irc, target)
|
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
|
||||||
if remoteuser is None:
|
else:
|
||||||
continue
|
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
|
||||||
real_target = remoteuser[1]
|
else:
|
||||||
|
remoteuser = getLocalUser(irc, target)
|
||||||
|
if remoteuser is None:
|
||||||
|
return
|
||||||
|
homenet, real_target = remoteuser
|
||||||
|
# For PMs, we must be on a common channel with the target.
|
||||||
|
# Otherwise, the sender doesn't have a client representing them
|
||||||
|
# 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 '
|
||||||
|
'with %r in order to send messages.' % \
|
||||||
|
irc.users[target].nick, notice=True)
|
||||||
|
return
|
||||||
|
remoteirc = utils.networkobjects[homenet]
|
||||||
|
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
|
||||||
if notice:
|
if notice:
|
||||||
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
|
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
|
||||||
else:
|
else:
|
||||||
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
|
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
|
||||||
sent += 1
|
|
||||||
'''
|
|
||||||
if not sent:
|
|
||||||
# We must be on a common channel with the target. Otherwise, the sender
|
|
||||||
# doesn't have a client representing them on the remote network,
|
|
||||||
# and we won't have anywhere to send our messages from.
|
|
||||||
# In this case, we've iterated over all networks where the sender
|
|
||||||
# has pseudoclients, and found no suitable targets to send to.
|
|
||||||
if target in irc.users:
|
|
||||||
target_s = 'a common channel with %r' % irc.users[target].nick
|
|
||||||
else:
|
|
||||||
target_s = repr(target)
|
|
||||||
utils.msg(irc, numeric, 'Error: You must be in %s in order to send messages.' % \
|
|
||||||
target_s, notice=True)
|
|
||||||
'''
|
|
||||||
utils.add_hook(handle_privmsg, 'PRIVMSG')
|
utils.add_hook(handle_privmsg, 'PRIVMSG')
|
||||||
utils.add_hook(handle_privmsg, 'NOTICE')
|
utils.add_hook(handle_privmsg, 'NOTICE')
|
||||||
|
|
||||||
@ -367,8 +359,10 @@ def handle_kick(irc, source, command, args):
|
|||||||
kicker = source
|
kicker = source
|
||||||
kicker_modes = getPrefixModes(irc, irc, channel, kicker)
|
kicker_modes = getPrefixModes(irc, irc, channel, kicker)
|
||||||
relay = findRelay((irc.name, channel))
|
relay = findRelay((irc.name, channel))
|
||||||
if relay is None:
|
# Don't allow kicks to the PyLink client to be relayed.
|
||||||
|
if relay is None or target == irc.pseudoclient.uid:
|
||||||
return
|
return
|
||||||
|
origuser = getLocalUser(irc, target)
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in utils.networkobjects.items():
|
||||||
if irc.name == name or not remoteirc.connected.is_set():
|
if irc.name == name or not remoteirc.connected.is_set():
|
||||||
continue
|
continue
|
||||||
@ -378,20 +372,21 @@ def handle_kick(irc, source, command, args):
|
|||||||
continue
|
continue
|
||||||
real_kicker = getRemoteUser(irc, remoteirc, kicker, spawnIfMissing=False)
|
real_kicker = getRemoteUser(irc, remoteirc, kicker, spawnIfMissing=False)
|
||||||
log.debug('(%s) Relay kick: real kicker for %s on %s is %s', irc.name, kicker, name, real_kicker)
|
log.debug('(%s) Relay kick: real kicker for %s on %s is %s', irc.name, kicker, name, real_kicker)
|
||||||
if not utils.isInternalClient(irc, target):
|
if not isRelayClient(irc, target):
|
||||||
log.debug('(%s) Relay kick: target %s is NOT an internal client', irc.name, target)
|
log.debug('(%s) Relay kick: target %s is NOT an internal client', irc.name, target)
|
||||||
# Both the target and kicker are external clients; i.e.
|
# Both the target and kicker are external clients; i.e.
|
||||||
# they originate from the same network. We won't have
|
# they originate from the same network. We won't have
|
||||||
# to filter this; the uplink IRCd will handle it appropriately,
|
# to filter this; the uplink IRCd will handle it appropriately,
|
||||||
# and we'll just follow.
|
# and we'll just follow.
|
||||||
real_target = getRemoteUser(irc, remoteirc, target)
|
real_target = getRemoteUser(irc, remoteirc, target, spawnIfMissing=False)
|
||||||
log.debug('(%s) Relay kick: real target for %s is %s', irc.name, target, real_target)
|
log.debug('(%s) Relay kick: real target for %s is %s', irc.name, target, real_target)
|
||||||
else:
|
else:
|
||||||
log.debug('(%s) Relay kick: target %s is an internal client, going to look up the real user', irc.name, target)
|
log.debug('(%s) Relay kick: target %s is an internal client, going to look up the real user', irc.name, target)
|
||||||
real_target = getLocalUser(irc, target, targetirc=remoteirc)
|
real_target = getLocalUser(irc, target, targetirc=remoteirc)
|
||||||
log.debug('(%s) Relay kick: kicker_modes are %r', irc.name, kicker_modes)
|
log.debug('(%s) Relay kick: kicker_modes are %r', irc.name, kicker_modes)
|
||||||
if irc.name not in db[relay]['claim'] and not \
|
if irc.name not in db[relay]['claim'] and not \
|
||||||
any([mode in kicker_modes for mode in ('y', 'q', 'a', 'o', 'h')]):
|
(any([mode in kicker_modes for mode in ('y', 'q', 'a', 'o', 'h')]) \
|
||||||
|
or utils.isInternalClient(irc, kicker)):
|
||||||
log.debug('(%s) Relay kick: kicker %s is not opped... We should rejoin the target user %s', irc.name, kicker, real_target)
|
log.debug('(%s) Relay kick: kicker %s is not opped... We should rejoin the target user %s', irc.name, kicker, real_target)
|
||||||
# Home network is not in the channel's claim AND the kicker is not
|
# Home network is not in the channel's claim AND the kicker is not
|
||||||
# opped. We won't propograte the kick then.
|
# opped. We won't propograte the kick then.
|
||||||
@ -399,13 +394,15 @@ def handle_kick(irc, source, command, args):
|
|||||||
# kick ops, admins can't kick owners, etc.
|
# kick ops, admins can't kick owners, etc.
|
||||||
modes = getPrefixModes(remoteirc, irc, remotechan, real_target)
|
modes = getPrefixModes(remoteirc, irc, remotechan, real_target)
|
||||||
# Join the kicked client back with its respective modes.
|
# Join the kicked client back with its respective modes.
|
||||||
irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, target)])
|
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
|
||||||
if kicker in irc.users:
|
if kicker in irc.users:
|
||||||
utils.msg(irc, kicker, "This channel is claimed; your kick has "
|
utils.msg(irc, kicker, "This channel is claimed; your kick to "
|
||||||
"to %s been blocked because you are not "
|
"%s has been blocked because you are not "
|
||||||
"(half)opped." % channel, notice=True)
|
"(half)opped." % channel, notice=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not real_target:
|
||||||
|
continue
|
||||||
# Propogate the kick!
|
# Propogate the kick!
|
||||||
if real_kicker:
|
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)
|
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)
|
||||||
@ -414,7 +411,7 @@ def handle_kick(irc, source, command, args):
|
|||||||
else:
|
else:
|
||||||
# Kick originated from a server, or the kicker isn't in any
|
# Kick originated from a server, or the kicker isn't in any
|
||||||
# common channels with the target relay network.
|
# common channels with the target relay network.
|
||||||
log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,remoteirc.sid, kicker, irc.name)
|
log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan, remoteirc.sid, kicker, irc.name)
|
||||||
try:
|
try:
|
||||||
if kicker in irc.servers:
|
if kicker in irc.servers:
|
||||||
kname = irc.servers[kicker].name
|
kname = irc.servers[kicker].name
|
||||||
@ -426,10 +423,14 @@ def handle_kick(irc, source, command, args):
|
|||||||
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
|
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
|
||||||
remotechan, real_target, text)
|
remotechan, real_target, text)
|
||||||
|
|
||||||
if target != irc.pseudoclient.uid and not irc.users[target].channels:
|
# If the target isn't on any channels, quit them.
|
||||||
irc.proto.quitClient(irc, target, 'Left all shared channels.')
|
if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels:
|
||||||
remoteuser = getLocalUser(irc, target)
|
del relayusers[origuser][remoteirc.name]
|
||||||
del relayusers[remoteuser][irc.name]
|
remoteirc.proto.quitClient(remoteirc, 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.')
|
||||||
|
|
||||||
utils.add_hook(handle_kick, 'KICK')
|
utils.add_hook(handle_kick, 'KICK')
|
||||||
|
|
||||||
@ -610,7 +611,7 @@ utils.add_hook(handle_topic, 'TOPIC')
|
|||||||
def handle_kill(irc, numeric, command, args):
|
def handle_kill(irc, numeric, command, args):
|
||||||
target = args['target']
|
target = args['target']
|
||||||
userdata = args['userdata']
|
userdata = args['userdata']
|
||||||
realuser = getLocalUser(irc, target)
|
realuser = getLocalUser(irc, target) or userdata.__dict__.get('remote')
|
||||||
log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser)
|
log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser)
|
||||||
# Target user was remote:
|
# Target user was remote:
|
||||||
if realuser and realuser[0] != irc.name:
|
if realuser and realuser[0] != irc.name:
|
||||||
@ -618,15 +619,15 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
# client and rejoin it to its channels.
|
# client and rejoin it to its channels.
|
||||||
del relayusers[realuser][irc.name]
|
del relayusers[realuser][irc.name]
|
||||||
remoteirc = utils.networkobjects[realuser[0]]
|
remoteirc = utils.networkobjects[realuser[0]]
|
||||||
for channel in remoteirc.channels:
|
for remotechan in remoteirc.channels:
|
||||||
remotechan = findRemoteChan(remoteirc, irc, channel)
|
localchan = findRemoteChan(remoteirc, irc, remotechan)
|
||||||
if remotechan:
|
if localchan:
|
||||||
modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1])
|
modes = getPrefixModes(remoteirc, irc, localchan, realuser[1])
|
||||||
log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
|
log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
|
||||||
client = getRemoteUser(remoteirc, irc, realuser[1])
|
client = getRemoteUser(remoteirc, irc, realuser[1])
|
||||||
irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)])
|
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
|
||||||
if userdata and numeric in irc.users:
|
if userdata and numeric in irc.users:
|
||||||
utils.msg(irc, numeric, "Your kill has to %s been blocked "
|
utils.msg(irc, numeric, "Your kill to %s has been blocked "
|
||||||
"because PyLink does not allow killing"
|
"because PyLink does not allow killing"
|
||||||
" users over the relay at this time." % \
|
" users over the relay at this time." % \
|
||||||
userdata.nick, notice=True)
|
userdata.nick, notice=True)
|
||||||
@ -641,7 +642,17 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
|
|
||||||
utils.add_hook(handle_kill, 'KILL')
|
utils.add_hook(handle_kill, 'KILL')
|
||||||
|
|
||||||
def relayJoins(irc, channel, users, ts, modes):
|
def isRelayClient(irc, user):
|
||||||
|
try:
|
||||||
|
if irc.users[user].remote:
|
||||||
|
# Is the .remote attribute set? If so, don't relay already
|
||||||
|
# relayed clients; that'll trigger an endless loop!
|
||||||
|
return True
|
||||||
|
except (KeyError, AttributeError): # Nope, it isn't.
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def relayJoins(irc, channel, users, ts):
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in utils.networkobjects.items():
|
||||||
queued_users = []
|
queued_users = []
|
||||||
if name == irc.name or not remoteirc.connected.is_set():
|
if name == irc.name or not remoteirc.connected.is_set():
|
||||||
@ -654,23 +665,16 @@ def relayJoins(irc, channel, users, ts, modes):
|
|||||||
continue
|
continue
|
||||||
log.debug('(%s) relayJoins: got %r for users', irc.name, users)
|
log.debug('(%s) relayJoins: got %r for users', irc.name, users)
|
||||||
for user in users.copy():
|
for user in users.copy():
|
||||||
if utils.isInternalClient(irc, user) or user not in irc.users:
|
if isRelayClient(irc, user):
|
||||||
# We don't need to clone PyLink pseudoclients... That's
|
# Don't clone relay clients; that'll cause some bad, bad
|
||||||
# meaningless.
|
# things to happen.
|
||||||
continue
|
continue
|
||||||
try:
|
|
||||||
if irc.users[user].remote:
|
|
||||||
# Is the .remote attribute set? If so, don't relay already
|
|
||||||
# relayed clients; that'll trigger an endless loop!
|
|
||||||
continue
|
|
||||||
except AttributeError: # Nope, it isn't.
|
|
||||||
pass
|
|
||||||
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
||||||
assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user)
|
assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user)
|
||||||
u = getRemoteUser(irc, remoteirc, user)
|
u = getRemoteUser(irc, remoteirc, user)
|
||||||
# Only join users if they aren't already joined. This prevents op floods
|
# Only join users if they aren't already joined. This prevents op floods
|
||||||
# on charybdis from all the SJOINing.
|
# on charybdis from all the SJOINing.
|
||||||
if u not in remoteirc.channels[channel].users:
|
if u not in remoteirc.channels[remotechan].users:
|
||||||
ts = irc.channels[channel].ts
|
ts = irc.channels[channel].ts
|
||||||
prefixes = getPrefixModes(irc, remoteirc, channel, user)
|
prefixes = getPrefixModes(irc, remoteirc, channel, user)
|
||||||
userpair = (prefixes, u)
|
userpair = (prefixes, u)
|
||||||
@ -681,7 +685,6 @@ def relayJoins(irc, channel, users, ts, modes):
|
|||||||
u, remoteirc.name, remotechan)
|
u, remoteirc.name, remotechan)
|
||||||
if queued_users:
|
if queued_users:
|
||||||
remoteirc.proto.sjoinServer(remoteirc, remoteirc.sid, remotechan, queued_users, ts=ts)
|
remoteirc.proto.sjoinServer(remoteirc, remoteirc.sid, remotechan, queued_users, ts=ts)
|
||||||
relayModes(irc, remoteirc, irc.sid, channel, modes)
|
|
||||||
|
|
||||||
def relayPart(irc, channel, user):
|
def relayPart(irc, channel, user):
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in utils.networkobjects.items():
|
||||||
@ -696,7 +699,7 @@ def relayPart(irc, channel, user):
|
|||||||
if remotechan is None or remoteuser is None:
|
if remotechan is None or remoteuser is None:
|
||||||
continue
|
continue
|
||||||
remoteirc.proto.partClient(remoteirc, remoteuser, remotechan, 'Channel delinked.')
|
remoteirc.proto.partClient(remoteirc, remoteuser, remotechan, 'Channel delinked.')
|
||||||
if not remoteirc.users[remoteuser].channels:
|
if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels:
|
||||||
remoteirc.proto.quitClient(remoteirc, remoteuser, 'Left all shared channels.')
|
remoteirc.proto.quitClient(remoteirc, remoteuser, 'Left all shared channels.')
|
||||||
del relayusers[(irc.name, user)][remoteirc.name]
|
del relayusers[(irc.name, user)][remoteirc.name]
|
||||||
|
|
||||||
@ -708,7 +711,7 @@ def removeChannel(irc, channel):
|
|||||||
relay = findRelay((irc.name, channel))
|
relay = findRelay((irc.name, channel))
|
||||||
if relay:
|
if relay:
|
||||||
for user in irc.channels[channel].users.copy():
|
for user in irc.channels[channel].users.copy():
|
||||||
if not utils.isInternalClient(irc, user):
|
if not isRelayClient(irc, user):
|
||||||
relayPart(irc, channel, user)
|
relayPart(irc, channel, user)
|
||||||
# Don't ever part the main client from any of its autojoin channels.
|
# Don't ever part the main client from any of its autojoin channels.
|
||||||
else:
|
else:
|
||||||
@ -718,9 +721,9 @@ def removeChannel(irc, channel):
|
|||||||
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
|
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
|
||||||
# Don't ever quit it either...
|
# Don't ever quit it either...
|
||||||
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
||||||
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
|
||||||
remoteuser = getLocalUser(irc, user)
|
remoteuser = getLocalUser(irc, user)
|
||||||
del relayusers[remoteuser][irc.name]
|
del relayusers[remoteuser][irc.name]
|
||||||
|
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def create(irc, source, args):
|
def create(irc, source, args):
|
||||||
@ -847,7 +850,10 @@ def delink(irc, source, args):
|
|||||||
if entry:
|
if entry:
|
||||||
if entry[0] == irc.name: # We own this channel.
|
if entry[0] == irc.name: # We own this channel.
|
||||||
if not remotenet:
|
if not remotenet:
|
||||||
utils.msg(irc, source, "Error: you must select a network to delink, or use the 'destroy' command no remove this relay entirely.")
|
utils.msg(irc, 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).")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
for link in db[entry]['links'].copy():
|
for link in db[entry]['links'].copy():
|
||||||
@ -900,7 +906,7 @@ def handle_save(irc, numeric, command, args):
|
|||||||
realuser = getLocalUser(irc, target)
|
realuser = getLocalUser(irc, target)
|
||||||
log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
|
log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
|
||||||
irc.name, target, realuser)
|
irc.name, target, realuser)
|
||||||
if utils.isInternalClient(irc, target) and realuser:
|
if isRelayClient(irc, target) and realuser:
|
||||||
# Nick collision!
|
# Nick collision!
|
||||||
# It's one of our relay clients; try to fix our nick to the next
|
# It's one of our relay clients; try to fix our nick to the next
|
||||||
# available normalized nick.
|
# available normalized nick.
|
||||||
@ -933,3 +939,16 @@ def linked(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
s += '(no relays yet)'
|
s += '(no relays yet)'
|
||||||
utils.msg(irc, source, s)
|
utils.msg(irc, source, s)
|
||||||
|
|
||||||
|
def handle_away(irc, numeric, command, args):
|
||||||
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
|
remoteirc = utils.networkobjects[netname]
|
||||||
|
remoteirc.proto.awayClient(remoteirc, user, args['text'])
|
||||||
|
utils.add_hook(handle_away, 'AWAY')
|
||||||
|
|
||||||
|
def handle_spawnmain(irc, numeric, command, args):
|
||||||
|
if args['olduser']:
|
||||||
|
# Kills to the main PyLink client force reinitialization; this makes sure
|
||||||
|
# it joins all the relay channels like it's supposed to.
|
||||||
|
initializeAll(irc)
|
||||||
|
utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN')
|
||||||
|
@ -56,10 +56,11 @@ def joinClient(irc, client, channel):
|
|||||||
if not server:
|
if not server:
|
||||||
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
|
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
|
||||||
raise LookupError('No such PyLink PseudoClient exists.')
|
raise LookupError('No such PyLink PseudoClient exists.')
|
||||||
# One channel per line here!
|
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
|
||||||
|
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
|
||||||
_send(irc, server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
|
_send(irc, server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
|
||||||
ts=irc.channels[channel].ts, uid=client, channel=channel,
|
ts=irc.channels[channel].ts, uid=client, channel=channel,
|
||||||
modes=utils.joinModes(irc.channels[channel].modes)))
|
modes=utils.joinModes(modes)))
|
||||||
irc.channels[channel].users.add(client)
|
irc.channels[channel].users.add(client)
|
||||||
irc.users[client].channels.add(channel)
|
irc.users[client].channels.add(channel)
|
||||||
|
|
||||||
@ -70,17 +71,21 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
|||||||
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
|
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
|
||||||
if not server:
|
if not server:
|
||||||
raise LookupError('No such PyLink PseudoClient exists.')
|
raise LookupError('No such PyLink PseudoClient exists.')
|
||||||
if ts is None:
|
orig_ts = irc.channels[channel].ts
|
||||||
ts = irc.channels[channel].ts
|
ts = ts or orig_ts
|
||||||
|
if ts < orig_ts:
|
||||||
|
# If the TS we're sending is lower than the one that existing, clear the
|
||||||
|
# mode lists from our channel state and reset the timestamp.
|
||||||
|
log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)',
|
||||||
|
irc.name, channel, orig_ts, ts)
|
||||||
|
irc.channels[channel].ts = ts
|
||||||
|
irc.channels[channel].modes.clear()
|
||||||
|
for p in irc.channels[channel].prefixmodes.values():
|
||||||
|
p.clear()
|
||||||
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
|
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
|
||||||
time.strftime("%c", time.localtime(ts)))
|
time.strftime("%c", time.localtime(ts)))
|
||||||
''' TODO: handle this properly!
|
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
|
||||||
if modes is None:
|
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
|
||||||
modes = irc.channels[channel].modes
|
|
||||||
else:
|
|
||||||
utils.applyModes(irc, channel, modes)
|
|
||||||
'''
|
|
||||||
modes = irc.channels[channel].modes
|
|
||||||
uids = []
|
uids = []
|
||||||
changedmodes = []
|
changedmodes = []
|
||||||
namelist = []
|
namelist = []
|
||||||
@ -96,7 +101,9 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
|||||||
irc.users[user].channels.add(channel)
|
irc.users[user].channels.add(channel)
|
||||||
except KeyError: # Not initialized yet?
|
except KeyError: # Not initialized yet?
|
||||||
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user)
|
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user)
|
||||||
utils.applyModes(irc, channel, changedmodes)
|
if ts < orig_ts:
|
||||||
|
# Only save our prefix modes in the channel state if our TS is lower than theirs.
|
||||||
|
utils.applyModes(irc, channel, changedmodes)
|
||||||
namelist = ' '.join(namelist)
|
namelist = ' '.join(namelist)
|
||||||
_send(irc, server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
_send(irc, server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
||||||
ts=ts, users=namelist, channel=channel,
|
ts=ts, users=namelist, channel=channel,
|
||||||
@ -119,8 +126,12 @@ def removeClient(irc, numeric):
|
|||||||
|
|
||||||
Removes a client from our internal databases, regardless
|
Removes a client from our internal databases, regardless
|
||||||
of whether it's one of our pseudoclients or not."""
|
of whether it's one of our pseudoclients or not."""
|
||||||
for v in irc.channels.values():
|
for c, v in irc.channels.copy().items():
|
||||||
v.removeuser(numeric)
|
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]
|
||||||
|
|
||||||
sid = numeric[:3]
|
sid = numeric[:3]
|
||||||
log.debug('Removing client %s from irc.users', numeric)
|
log.debug('Removing client %s from irc.users', numeric)
|
||||||
del irc.users[numeric]
|
del irc.users[numeric]
|
||||||
@ -244,6 +255,8 @@ def topicClient(irc, numeric, target, text):
|
|||||||
if not utils.isInternalClient(irc, numeric):
|
if not utils.isInternalClient(irc, numeric):
|
||||||
raise LookupError('No such PyLink PseudoClient exists.')
|
raise LookupError('No such PyLink PseudoClient exists.')
|
||||||
_send(irc, numeric, 'TOPIC %s :%s' % (target, text))
|
_send(irc, numeric, 'TOPIC %s :%s' % (target, text))
|
||||||
|
irc.channels[target].topic = text
|
||||||
|
irc.channels[target].topicset = True
|
||||||
|
|
||||||
def topicServer(irc, numeric, target, text):
|
def topicServer(irc, numeric, target, text):
|
||||||
if not utils.isInternalServer(irc, numeric):
|
if not utils.isInternalServer(irc, numeric):
|
||||||
@ -251,6 +264,8 @@ def topicServer(irc, numeric, target, text):
|
|||||||
ts = int(time.time())
|
ts = int(time.time())
|
||||||
servername = irc.servers[numeric].name
|
servername = irc.servers[numeric].name
|
||||||
_send(irc, numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
|
_send(irc, numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
|
||||||
|
irc.channels[target].topic = text
|
||||||
|
irc.channels[target].topicset = True
|
||||||
|
|
||||||
def inviteClient(irc, numeric, target, channel):
|
def inviteClient(irc, numeric, target, channel):
|
||||||
"""<irc object> <client numeric> <text>
|
"""<irc object> <client numeric> <text>
|
||||||
@ -297,6 +312,16 @@ def numericServer(irc, source, numeric, text):
|
|||||||
"locally by InspIRCd servers, so there is no "
|
"locally by InspIRCd servers, so there is no "
|
||||||
"need for PyLink to send numerics directly yet.")
|
"need for PyLink to send numerics directly yet.")
|
||||||
|
|
||||||
|
def awayClient(irc, source, text):
|
||||||
|
"""<irc object> <numeric> <text>
|
||||||
|
|
||||||
|
Sends an AWAY message with text <text> from PyLink client <numeric>.
|
||||||
|
<text> can be an empty string to unset AWAY status."""
|
||||||
|
if text:
|
||||||
|
_send(irc, source, 'AWAY %s :%s' % (int(time.time()), text))
|
||||||
|
else:
|
||||||
|
_send(irc, source, 'AWAY')
|
||||||
|
|
||||||
def connect(irc):
|
def connect(irc):
|
||||||
ts = irc.start_ts
|
ts = irc.start_ts
|
||||||
|
|
||||||
@ -349,6 +374,9 @@ def handle_part(irc, source, command, args):
|
|||||||
reason = args[1]
|
reason = args[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
reason = ''
|
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}
|
return {'channels': channels, 'text': reason}
|
||||||
|
|
||||||
def handle_error(irc, numeric, command, args):
|
def handle_error(irc, numeric, command, args):
|
||||||
@ -367,6 +395,9 @@ def handle_fjoin(irc, servernumeric, command, args):
|
|||||||
log.debug('(%s) Setting channel TS of %s to %s from %s',
|
log.debug('(%s) Setting channel TS of %s to %s from %s',
|
||||||
irc.name, channel, their_ts, our_ts)
|
irc.name, channel, their_ts, our_ts)
|
||||||
irc.channels[channel].ts = their_ts
|
irc.channels[channel].ts = their_ts
|
||||||
|
irc.channels[channel].modes.clear()
|
||||||
|
for p in irc.channels[channel].prefixmodes.values():
|
||||||
|
p.clear()
|
||||||
modestring = args[2:-1] or args[2]
|
modestring = args[2:-1] or args[2]
|
||||||
parsedmodes = utils.parseModes(irc, channel, modestring)
|
parsedmodes = utils.parseModes(irc, channel, modestring)
|
||||||
utils.applyModes(irc, channel, parsedmodes)
|
utils.applyModes(irc, channel, parsedmodes)
|
||||||
@ -664,3 +695,13 @@ def handle_fname(irc, numeric, command, args):
|
|||||||
|
|
||||||
def handle_endburst(irc, numeric, command, args):
|
def handle_endburst(irc, numeric, command, args):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def handle_away(irc, numeric, command, args):
|
||||||
|
# <- :1MLAAAAIG AWAY 1439371390 :Auto-away
|
||||||
|
try:
|
||||||
|
ts = args[0]
|
||||||
|
irc.users[numeric].away = text = args[1]
|
||||||
|
return {'text': text, 'ts': ts}
|
||||||
|
except IndexError: # User is unsetting away status
|
||||||
|
irc.users[numeric].away = ''
|
||||||
|
return {'text': ''}
|
||||||
|
@ -77,8 +77,17 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
|||||||
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
|
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
|
||||||
if not server:
|
if not server:
|
||||||
raise LookupError('No such PyLink PseudoClient exists.')
|
raise LookupError('No such PyLink PseudoClient exists.')
|
||||||
if ts is None:
|
orig_ts = irc.channels[channel].ts
|
||||||
ts = irc.channels[channel].ts
|
ts = ts or orig_ts
|
||||||
|
if ts < orig_ts:
|
||||||
|
# If the TS we're sending is lower than the one that existing, clear the
|
||||||
|
# mode lists from our channel state and reset the timestamp.
|
||||||
|
log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)',
|
||||||
|
irc.name, channel, orig_ts, ts)
|
||||||
|
irc.channels[channel].ts = ts
|
||||||
|
irc.channels[channel].modes.clear()
|
||||||
|
for p in irc.channels[channel].prefixmodes.values():
|
||||||
|
p.clear()
|
||||||
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
|
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
|
||||||
time.strftime("%c", time.localtime(ts)))
|
time.strftime("%c", time.localtime(ts)))
|
||||||
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
|
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
|
||||||
@ -95,6 +104,7 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
|||||||
pr = irc.prefixmodes.get(prefix)
|
pr = irc.prefixmodes.get(prefix)
|
||||||
if pr:
|
if pr:
|
||||||
prefixchars += pr
|
prefixchars += pr
|
||||||
|
changedmodes.append(('+%s' % prefix, user))
|
||||||
namelist.append(prefixchars+user)
|
namelist.append(prefixchars+user)
|
||||||
uids.append(user)
|
uids.append(user)
|
||||||
try:
|
try:
|
||||||
@ -107,7 +117,9 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
|||||||
ts=ts, users=namelist, channel=channel,
|
ts=ts, users=namelist, channel=channel,
|
||||||
modes=utils.joinModes(modes)))
|
modes=utils.joinModes(modes)))
|
||||||
irc.channels[channel].users.update(uids)
|
irc.channels[channel].users.update(uids)
|
||||||
utils.applyModes(irc, channel, changedmodes)
|
if ts < orig_ts:
|
||||||
|
# Only save our prefix modes in the channel state if our TS is lower than theirs.
|
||||||
|
utils.applyModes(irc, channel, changedmodes)
|
||||||
|
|
||||||
def _sendModes(irc, numeric, target, modes, ts=None):
|
def _sendModes(irc, numeric, target, modes, ts=None):
|
||||||
utils.applyModes(irc, target, modes)
|
utils.applyModes(irc, target, modes)
|
||||||
@ -186,6 +198,8 @@ def topicServer(irc, numeric, target, text):
|
|||||||
ts = irc.channels[target].ts
|
ts = irc.channels[target].ts
|
||||||
servername = irc.servers[numeric].name
|
servername = irc.servers[numeric].name
|
||||||
_send(irc, numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
|
_send(irc, numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
|
||||||
|
irc.channels[target].topic = text
|
||||||
|
irc.channels[target].topicset = True
|
||||||
|
|
||||||
def inviteClient(irc, numeric, target, channel):
|
def inviteClient(irc, numeric, target, channel):
|
||||||
"""<irc object> <client numeric> <text>
|
"""<irc object> <client numeric> <text>
|
||||||
@ -231,6 +245,16 @@ def pingServer(irc, source=None, target=None):
|
|||||||
def numericServer(irc, source, numeric, target, text):
|
def numericServer(irc, source, numeric, target, text):
|
||||||
_send(irc, source, '%s %s %s' % (numeric, target, text))
|
_send(irc, source, '%s %s %s' % (numeric, target, text))
|
||||||
|
|
||||||
|
def awayClient(irc, source, text):
|
||||||
|
"""<irc object> <numeric> <text>
|
||||||
|
|
||||||
|
Sends an AWAY message with text <text> from PyLink client <numeric>.
|
||||||
|
<text> can be an empty string to unset AWAY status."""
|
||||||
|
if text:
|
||||||
|
_send(irc, source, 'AWAY :%s' % text)
|
||||||
|
else:
|
||||||
|
_send(irc, source, 'AWAY')
|
||||||
|
|
||||||
def connect(irc):
|
def connect(irc):
|
||||||
ts = irc.start_ts
|
ts = irc.start_ts
|
||||||
|
|
||||||
@ -358,9 +382,10 @@ def handle_part(irc, source, command, args):
|
|||||||
reason = args[1]
|
reason = args[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
reason = ''
|
reason = ''
|
||||||
|
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}
|
return {'channels': channels, 'text': reason}
|
||||||
|
|
||||||
|
|
||||||
def handle_sjoin(irc, servernumeric, command, args):
|
def handle_sjoin(irc, servernumeric, command, args):
|
||||||
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
|
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
|
||||||
channel = args[1].lower()
|
channel = args[1].lower()
|
||||||
@ -372,6 +397,9 @@ def handle_sjoin(irc, servernumeric, command, args):
|
|||||||
log.debug('(%s) Setting channel TS of %s to %s from %s',
|
log.debug('(%s) Setting channel TS of %s to %s from %s',
|
||||||
irc.name, channel, their_ts, our_ts)
|
irc.name, channel, their_ts, our_ts)
|
||||||
irc.channels[channel].ts = their_ts
|
irc.channels[channel].ts = their_ts
|
||||||
|
irc.channels[channel].modes.clear()
|
||||||
|
for p in irc.channels[channel].prefixmodes.values():
|
||||||
|
p.clear()
|
||||||
modestring = args[2:-1] or args[2]
|
modestring = args[2:-1] or args[2]
|
||||||
parsedmodes = utils.parseModes(irc, channel, modestring)
|
parsedmodes = utils.parseModes(irc, channel, modestring)
|
||||||
utils.applyModes(irc, channel, parsedmodes)
|
utils.applyModes(irc, channel, parsedmodes)
|
||||||
@ -649,3 +677,13 @@ def handle_472(irc, numeric, command, args):
|
|||||||
' desyncs, try adding the line "loadmodule "extensions/%s.so";" to '
|
' desyncs, try adding the line "loadmodule "extensions/%s.so";" to '
|
||||||
'your IRCd configuration.', irc.name, setter, badmode,
|
'your IRCd configuration.', irc.name, setter, badmode,
|
||||||
charlist[badmode])
|
charlist[badmode])
|
||||||
|
|
||||||
|
def handle_away(irc, numeric, command, args):
|
||||||
|
# <- :6ELAAAAAB AWAY :Auto-away
|
||||||
|
|
||||||
|
try:
|
||||||
|
irc.users[numeric].away = text = args[0]
|
||||||
|
except IndexError: # User is unsetting away status
|
||||||
|
irc.users[numeric].away = text = ''
|
||||||
|
return {'text': text}
|
||||||
|
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Shell script to start PyLink under CPUlimit, killing it if it starts abusing the CPU.
|
# Shell script to start PyLink under CPUlimit, throttling it if it starts abusing the CPU.
|
||||||
|
|
||||||
# Set this to whatever you want. cpulimit --help
|
# Set this to whatever you want. cpulimit --help
|
||||||
LIMIT=20
|
LIMIT=35
|
||||||
|
|
||||||
# Change to the PyLink root directory.
|
# Change to the PyLink root directory.
|
||||||
WRAPPER_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
WRAPPER_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
cd "$WRAPPER_DIR"
|
cd "$WRAPPER_DIR"
|
||||||
|
|
||||||
if [[ ! -z "$(which cpulimit)" ]]; then
|
if [[ ! -z "$(which cpulimit)" ]]; then
|
||||||
# -k kills the PyLink daemon if it goes over $LIMIT
|
|
||||||
# -z makes cpulimit exit when PyLink dies.
|
# -z makes cpulimit exit when PyLink dies.
|
||||||
cpulimit -l $LIMIT -z -k ./main.py
|
cpulimit -l $LIMIT -z ./main.py
|
||||||
echo "PyLink has been started (daemonized) under cpulimit, and will automatically be killed if it goes over the CPU limit of ${LIMIT}%."
|
echo "PyLink has been started (daemonized) under cpulimit, and will automatically be throttled if it goes over the CPU limit of ${LIMIT}%."
|
||||||
echo "To kill the process manually, run ./kill.sh"
|
echo "To kill the process manually, run ./kill.sh"
|
||||||
else
|
else
|
||||||
echo 'cpulimit not found in $PATH! Aborting.'
|
echo 'cpulimit not found in $PATH! Aborting.'
|
||||||
|
5
utils.py
5
utils.py
@ -241,7 +241,10 @@ def applyModes(irc, target, changedmodes):
|
|||||||
log.debug('(%s) Applying modes %r on %s (initial modelist: %s)', irc.name, changedmodes, target, modelist)
|
log.debug('(%s) Applying modes %r on %s (initial modelist: %s)', irc.name, changedmodes, target, modelist)
|
||||||
for mode in changedmodes:
|
for mode in changedmodes:
|
||||||
# Chop off the +/- part that parseModes gives; it's meaningless for a mode list.
|
# Chop off the +/- part that parseModes gives; it's meaningless for a mode list.
|
||||||
real_mode = (mode[0][1], mode[1])
|
try:
|
||||||
|
real_mode = (mode[0][1], mode[1])
|
||||||
|
except IndexError:
|
||||||
|
real_mode = mode
|
||||||
if not usermodes:
|
if not usermodes:
|
||||||
pmode = ''
|
pmode = ''
|
||||||
for m in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
for m in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
||||||
|
Loading…
Reference in New Issue
Block a user