mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-25 20:22:45 +01:00
Merge branch 'master' into wip/unrealircd
Conflicts: classes.py
This commit is contained in:
commit
886994475d
10
README.md
10
README.md
@ -1,12 +1,12 @@
|
|||||||
# PyLink
|
# PyLink
|
||||||
|
|
||||||
PyLink is an extensible, plugin-based IRC PseudoService written in Python. It aims to be a replacement for the now-defunct Janus.
|
PyLink is an extensible, plugin-based IRC Services framework written in Python. It aims to be a replacement for the now-defunct Janus.
|
||||||
|
|
||||||
## 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 amongst your users!
|
**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: note that **master** is bugfix only; new stuff goes to the **devel** branch.
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
@ -22,10 +22,10 @@ Dependencies currently include:
|
|||||||
* 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`
|
* Elemental-IRCd (6.6.x / git master) - module `ts6`
|
||||||
|
|
||||||
### Installation
|
### Setup
|
||||||
|
|
||||||
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Not all options are properly implemented yet, and the configuration schema isn't finalized yet - this means that your configuration may break in an update!
|
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Note that the configuration format isn't finalized yet - this means that your configuration may break in an update!
|
||||||
|
|
||||||
2) Run `main.py` from the command line.
|
2) Run `./pylink` from the command line.
|
||||||
|
|
||||||
3) Profit???
|
3) Profit???
|
||||||
|
71
classes.py
71
classes.py
@ -6,6 +6,7 @@ import threading
|
|||||||
import ssl
|
import ssl
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
from conf import conf
|
from conf import conf
|
||||||
@ -26,7 +27,9 @@ class Irc():
|
|||||||
self.lastping = time.time()
|
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: IrcServer(None, self.serverdata['hostname'], internal=True)}
|
self.servers = {self.sid: IrcServer(None, self.serverdata['hostname'],
|
||||||
|
internal=True, desc=self.serverdata.get('serverdesc')
|
||||||
|
or self.botdata['serverdesc'])}
|
||||||
self.users = {}
|
self.users = {}
|
||||||
self.channels = defaultdict(IrcChannel)
|
self.channels = defaultdict(IrcChannel)
|
||||||
# Sets flags such as whether to use halfops, etc. The default RFC1459
|
# Sets flags such as whether to use halfops, etc. The default RFC1459
|
||||||
@ -55,13 +58,6 @@ class Irc():
|
|||||||
self.uplink = None
|
self.uplink = None
|
||||||
self.start_ts = int(time.time())
|
self.start_ts = int(time.time())
|
||||||
|
|
||||||
# UID generators, for servers that need it
|
|
||||||
self.uidgen = {}
|
|
||||||
|
|
||||||
# Local data for the IRC object, which protocol modules may use for
|
|
||||||
# initialization, etc.
|
|
||||||
self.protodata = {}
|
|
||||||
|
|
||||||
def __init__(self, netname, proto):
|
def __init__(self, netname, proto):
|
||||||
# Initialize some variables
|
# Initialize some variables
|
||||||
self.name = netname.lower()
|
self.name = netname.lower()
|
||||||
@ -211,20 +207,24 @@ class Irc():
|
|||||||
line = line.strip(b'\r')
|
line = line.strip(b'\r')
|
||||||
# FIXME: respect other encodings?
|
# FIXME: respect other encodings?
|
||||||
line = line.decode("utf-8", "replace")
|
line = line.decode("utf-8", "replace")
|
||||||
log.debug("(%s) <- %s", self.name, line)
|
self.runline(line)
|
||||||
hook_args = None
|
|
||||||
try:
|
def runline(self, line):
|
||||||
hook_args = self.proto.handle_events(line)
|
"""Sends a command to the protocol module."""
|
||||||
except Exception:
|
log.debug("(%s) <- %s", self.name, line)
|
||||||
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
|
try:
|
||||||
return
|
hook_args = self.proto.handle_events(line)
|
||||||
# Only call our hooks if there's data to process. Handlers that support
|
except Exception:
|
||||||
# hooks will return a dict of parsed arguments, which can be passed on
|
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
|
||||||
# to plugins and the like. For example, the JOIN handler will return
|
log.error('(%s) The offending line was: <- %s', self.name, line)
|
||||||
# something like: {'channel': '#whatever', 'users': ['UID1', 'UID2',
|
return
|
||||||
# 'UID3']}, etc.
|
# Only call our hooks if there's data to process. Handlers that support
|
||||||
if hook_args is not None:
|
# hooks will return a dict of parsed arguments, which can be passed on
|
||||||
self.callHooks(hook_args)
|
# to plugins and the like. For example, the JOIN handler will return
|
||||||
|
# something like: {'channel': '#whatever', 'users': ['UID1', 'UID2',
|
||||||
|
# 'UID3']}, etc.
|
||||||
|
if hook_args is not None:
|
||||||
|
self.callHooks(hook_args)
|
||||||
|
|
||||||
def callHooks(self, hook_args):
|
def callHooks(self, hook_args):
|
||||||
numeric, command, parsed_args = hook_args
|
numeric, command, parsed_args = hook_args
|
||||||
@ -239,15 +239,17 @@ class Irc():
|
|||||||
if command in hook_map:
|
if command in hook_map:
|
||||||
hook_cmd = hook_map[command]
|
hook_cmd = hook_map[command]
|
||||||
hook_cmd = parsed_args.get('parse_as') or hook_cmd
|
hook_cmd = parsed_args.get('parse_as') or hook_cmd
|
||||||
log.debug('Parsed args %r received from %s handler (calling hook %s)', parsed_args, command, hook_cmd)
|
log.debug('(%s) Parsed args %r received from %s handler (calling hook %s)',
|
||||||
|
self.name, parsed_args, command, hook_cmd)
|
||||||
# Iterate over hooked functions, catching errors accordingly
|
# Iterate over hooked functions, catching errors accordingly
|
||||||
for hook_func in world.command_hooks[hook_cmd]:
|
for hook_func in world.command_hooks[hook_cmd]:
|
||||||
try:
|
try:
|
||||||
log.debug('Calling function %s', hook_func)
|
log.debug('(%s) Calling function %s', self.name, hook_func)
|
||||||
hook_func(self, numeric, command, parsed_args)
|
hook_func(self, numeric, command, parsed_args)
|
||||||
except Exception:
|
except Exception:
|
||||||
# We don't want plugins to crash our servers...
|
# We don't want plugins to crash our servers...
|
||||||
log.exception('Unhandled exception caught in %r' % hook_func)
|
log.exception('(%s) Unhandled exception caught in %r',
|
||||||
|
self.name, hook_func)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
@ -275,7 +277,9 @@ class Irc():
|
|||||||
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
|
olduserobj = self.pseudoclient
|
||||||
self.pseudoclient = self.proto.spawnClient(nick, ident, host, modes={("+o", None)})
|
self.pseudoclient = self.proto.spawnClient(nick, ident, host,
|
||||||
|
modes={("+o", None)},
|
||||||
|
manipulatable=True)
|
||||||
for chan in self.serverdata['channels']:
|
for chan in self.serverdata['channels']:
|
||||||
self.proto.joinClient(self.pseudoclient.uid, chan)
|
self.proto.joinClient(self.pseudoclient.uid, chan)
|
||||||
# PyLink internal hook called when spawnMain is called and the
|
# PyLink internal hook called when spawnMain is called and the
|
||||||
@ -285,7 +289,7 @@ class Irc():
|
|||||||
class IrcUser():
|
class IrcUser():
|
||||||
def __init__(self, nick, ts, uid, ident='null', host='null',
|
def __init__(self, nick, ts, uid, ident='null', host='null',
|
||||||
realname='PyLink dummy client', realhost='null',
|
realname='PyLink dummy client', realhost='null',
|
||||||
ip='0.0.0.0'):
|
ip='0.0.0.0', manipulatable=False):
|
||||||
self.nick = nick
|
self.nick = nick
|
||||||
self.ts = ts
|
self.ts = ts
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
@ -300,6 +304,11 @@ class IrcUser():
|
|||||||
self.channels = set()
|
self.channels = set()
|
||||||
self.away = ''
|
self.away = ''
|
||||||
|
|
||||||
|
# Whether the client should be marked as manipulatable
|
||||||
|
# (i.e. we are allowed to play with it using bots.py's commands).
|
||||||
|
# For internal services clients, this should always be False.
|
||||||
|
self.manipulatable = manipulatable
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.__dict__)
|
return repr(self.__dict__)
|
||||||
|
|
||||||
@ -311,11 +320,12 @@ class IrcServer():
|
|||||||
name: The name of the server.
|
name: The name of the server.
|
||||||
internal: Whether the server is an internal PyLink PseudoServer.
|
internal: Whether the server is an internal PyLink PseudoServer.
|
||||||
"""
|
"""
|
||||||
def __init__(self, uplink, name, internal=False):
|
def __init__(self, uplink, name, internal=False, desc="(None given)"):
|
||||||
self.uplink = uplink
|
self.uplink = uplink
|
||||||
self.users = set()
|
self.users = set()
|
||||||
self.internal = internal
|
self.internal = internal
|
||||||
self.name = name.lower()
|
self.name = name.lower()
|
||||||
|
self.desc = desc
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.__dict__)
|
return repr(self.__dict__)
|
||||||
|
|
||||||
@ -337,6 +347,9 @@ class IrcChannel():
|
|||||||
s.discard(target)
|
s.discard(target)
|
||||||
self.users.discard(target)
|
self.users.discard(target)
|
||||||
|
|
||||||
|
def deepcopy(self):
|
||||||
|
return deepcopy(self)
|
||||||
|
|
||||||
### FakeIRC classes, used for test cases
|
### FakeIRC classes, used for test cases
|
||||||
|
|
||||||
class FakeIRC(Irc):
|
class FakeIRC(Irc):
|
||||||
@ -404,7 +417,7 @@ class FakeProto(Protocol):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def spawnClient(self, nick, *args, **kwargs):
|
def spawnClient(self, nick, *args, **kwargs):
|
||||||
uid = randint(1, 10000000000)
|
uid = str(randint(1, 10000000000))
|
||||||
ts = int(time.time())
|
ts = int(time.time())
|
||||||
self.irc.users[uid] = user = IrcUser(nick, ts, uid)
|
self.irc.users[uid] = user = IrcUser(nick, ts, uid)
|
||||||
return user
|
return user
|
||||||
|
3
conf.py
3
conf.py
@ -27,7 +27,8 @@ testconf = {'bot':
|
|||||||
'hostname': "pylink.unittest",
|
'hostname': "pylink.unittest",
|
||||||
'sid': "9PY",
|
'sid': "9PY",
|
||||||
'channels': ["#pylink"],
|
'channels': ["#pylink"],
|
||||||
'maxnicklen': 20
|
'maxnicklen': 20,
|
||||||
|
'sidrange': '8##'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if world.testing:
|
if world.testing:
|
||||||
|
@ -20,6 +20,22 @@ login:
|
|||||||
user: admin
|
user: admin
|
||||||
password: changeme
|
password: changeme
|
||||||
|
|
||||||
|
relay:
|
||||||
|
# This block defines various options for the Relay plugin. You don't need this
|
||||||
|
# if you aren't using it.
|
||||||
|
|
||||||
|
# Determines whether remote opers will have user mode +H (hideoper) set on them.
|
||||||
|
# This has the benefit of lowering the oper count in /lusers and /stats (P|p),
|
||||||
|
# but only on IRCds that supported the mode.
|
||||||
|
# It defaults to true if not set.
|
||||||
|
hideoper: true
|
||||||
|
|
||||||
|
# Determines whether real IPs should be sent across the relay. You should
|
||||||
|
# generally have a consensus with your linked networks whether this should
|
||||||
|
# be turned on. You will see other networks' user IP addresses, and they
|
||||||
|
# will see yours.
|
||||||
|
show_ips: false
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
yournet:
|
yournet:
|
||||||
# Server IP, port, and passwords
|
# Server IP, port, and passwords
|
||||||
@ -28,6 +44,9 @@ servers:
|
|||||||
recvpass: "abcd"
|
recvpass: "abcd"
|
||||||
sendpass: "abcd"
|
sendpass: "abcd"
|
||||||
|
|
||||||
|
# The full network name, used by plugins.
|
||||||
|
netname: "yournet"
|
||||||
|
|
||||||
# Hostname we will use to connect to the remote server
|
# Hostname we will use to connect to the remote server
|
||||||
hostname: "pylink.yournet"
|
hostname: "pylink.yournet"
|
||||||
|
|
||||||
@ -81,6 +100,7 @@ servers:
|
|||||||
sendpass: "abcd"
|
sendpass: "abcd"
|
||||||
hostname: "pylink.example.com"
|
hostname: "pylink.example.com"
|
||||||
sid: "8PY"
|
sid: "8PY"
|
||||||
|
netname: "some network"
|
||||||
|
|
||||||
# Leave this as an empty list if you don't want to join any channels.
|
# Leave this as an empty list if you don't want to join any channels.
|
||||||
channels: []
|
channels: []
|
||||||
|
@ -4,22 +4,22 @@ import utils
|
|||||||
from log import log
|
from log import log
|
||||||
import world
|
import world
|
||||||
|
|
||||||
# Handle KILLs sent to the PyLink client and respawn
|
|
||||||
def handle_kill(irc, source, command, args):
|
def handle_kill(irc, source, command, args):
|
||||||
|
"""Handle KILLs to the main PyLink client, respawning it as needed."""
|
||||||
if args['target'] == irc.pseudoclient.uid:
|
if args['target'] == irc.pseudoclient.uid:
|
||||||
irc.spawnMain()
|
irc.spawnMain()
|
||||||
utils.add_hook(handle_kill, 'KILL')
|
utils.add_hook(handle_kill, 'KILL')
|
||||||
|
|
||||||
# Handle KICKs to the PyLink client, rejoining the channels
|
|
||||||
def handle_kick(irc, source, command, args):
|
def handle_kick(irc, source, command, args):
|
||||||
|
"""Handle KICKs to the main PyLink client, rejoining channels as needed."""
|
||||||
kicked = args['target']
|
kicked = args['target']
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
if kicked == irc.pseudoclient.uid:
|
if kicked == irc.pseudoclient.uid:
|
||||||
irc.proto.joinClient(irc.pseudoclient.uid, channel)
|
irc.proto.joinClient(irc.pseudoclient.uid, channel)
|
||||||
utils.add_hook(handle_kick, 'KICK')
|
utils.add_hook(handle_kick, 'KICK')
|
||||||
|
|
||||||
# Handle commands sent to the PyLink client (PRIVMSG)
|
|
||||||
def handle_commands(irc, source, command, args):
|
def handle_commands(irc, source, command, args):
|
||||||
|
"""Handle commands sent to the PyLink client (PRIVMSG)."""
|
||||||
if args['target'] == irc.pseudoclient.uid:
|
if args['target'] == irc.pseudoclient.uid:
|
||||||
text = args['text'].strip()
|
text = args['text'].strip()
|
||||||
cmd_args = text.split(' ')
|
cmd_args = text.split(' ')
|
||||||
@ -39,12 +39,14 @@ def handle_commands(irc, source, command, args):
|
|||||||
irc.msg(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')
|
utils.add_hook(handle_commands, 'PRIVMSG')
|
||||||
|
|
||||||
# Handle WHOIS queries, for IRCds that send them across servers (charybdis, UnrealIRCd; NOT InspIRCd).
|
|
||||||
def handle_whois(irc, source, command, args):
|
def handle_whois(irc, source, command, args):
|
||||||
|
"""Handle WHOIS queries, for IRCds that send them across servers (charybdis, UnrealIRCd; NOT InspIRCd)."""
|
||||||
target = args['target']
|
target = args['target']
|
||||||
user = irc.users.get(target)
|
user = irc.users.get(target)
|
||||||
if user is None:
|
if user is None:
|
||||||
log.warning('(%s) Got a WHOIS request for %r from %r, but the target doesn\'t exist in irc.users!', irc.name, target, source)
|
log.warning('(%s) Got a WHOIS request for %r from %r, but the target '
|
||||||
|
'doesn\'t exist in irc.users!', irc.name, target, source)
|
||||||
|
return
|
||||||
f = irc.proto.numericServer
|
f = irc.proto.numericServer
|
||||||
server = utils.clientToServer(irc, target) or irc.sid
|
server = utils.clientToServer(irc, target) or irc.sid
|
||||||
nick = user.nick
|
nick = user.nick
|
||||||
@ -72,12 +74,12 @@ def handle_whois(irc, source, command, args):
|
|||||||
f(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.
|
# 312: sends the server the target is on, and its server description.
|
||||||
f(server, 312, source, "%s %s :%s" % (nick, irc.servers[server].name,
|
f(server, 312, source, "%s %s :%s" % (nick, irc.servers[server].name,
|
||||||
irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
|
irc.servers[server].desc))
|
||||||
# 313: sends a string denoting the target's operator privilege,
|
# 313: sends a string denoting the target's operator privilege,
|
||||||
# only if they have umode +o.
|
# only if they have umode +o.
|
||||||
if ('o', None) in user.modes:
|
if ('o', None) in user.modes:
|
||||||
if hasattr(user, 'opertype'):
|
if hasattr(user, 'opertype'):
|
||||||
opertype = user.opertype.replace("_", " ")
|
opertype = user.opertype
|
||||||
else:
|
else:
|
||||||
opertype = "IRC Operator"
|
opertype = "IRC Operator"
|
||||||
# Let's be gramatically correct.
|
# Let's be gramatically correct.
|
||||||
@ -86,6 +88,7 @@ def handle_whois(irc, source, command, args):
|
|||||||
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
|
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
|
||||||
# Only show this to opers!
|
# Only show this to opers!
|
||||||
if sourceisOper:
|
if sourceisOper:
|
||||||
|
f(server, 378, source, "%s :is connecting from %s@%s %s" % (nick, user.ident, user.realhost, user.ip))
|
||||||
f(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
|
# 317: shows idle and signon time. However, we don't track the user's real
|
||||||
# idle time, so we simply return 0.
|
# idle time, so we simply return 0.
|
||||||
@ -106,3 +109,14 @@ def handle_whois(irc, source, command, args):
|
|||||||
# 318: End of WHOIS.
|
# 318: End of WHOIS.
|
||||||
f(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')
|
utils.add_hook(handle_whois, 'WHOIS')
|
||||||
|
|
||||||
|
def handle_mode(irc, source, command, args):
|
||||||
|
"""Protect against forced deoper attempts."""
|
||||||
|
target = args['target']
|
||||||
|
modes = args['modes']
|
||||||
|
# If the sender is not a PyLink client, and the target IS a protected
|
||||||
|
# client, revert any forced deoper attempts.
|
||||||
|
if utils.isInternalClient(irc, target) and not utils.isInternalClient(irc, source):
|
||||||
|
if ('-o', None) in modes and (target == irc.pseudoclient.uid or not utils.isManipulatableClient(irc, target)):
|
||||||
|
irc.proto.modeServer(irc.sid, target, {('+o', None)})
|
||||||
|
utils.add_hook(handle_mode, 'MODE')
|
||||||
|
@ -22,7 +22,7 @@ def spawnclient(irc, source, args):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
irc.msg(source, "Error: Not enough arguments. Needs 3: nick, user, host.")
|
irc.msg(source, "Error: Not enough arguments. Needs 3: nick, user, host.")
|
||||||
return
|
return
|
||||||
irc.proto.spawnClient(nick, ident, host)
|
irc.proto.spawnClient(nick, ident, host, manipulatable=True)
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def quit(irc, source, args):
|
def quit(irc, source, args):
|
||||||
@ -40,6 +40,9 @@ def quit(irc, source, args):
|
|||||||
return
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
||||||
|
if not utils.isManipulatableClient(irc, u):
|
||||||
|
irc.msg(source, "Error: Cannot force quit a protected PyLink services client.")
|
||||||
|
return
|
||||||
irc.proto.quitClient(u, quitmsg)
|
irc.proto.quitClient(u, quitmsg)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
||||||
|
|
||||||
@ -57,6 +60,9 @@ def joinclient(irc, source, args):
|
|||||||
irc.msg(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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
|
if not utils.isManipulatableClient(irc, u):
|
||||||
|
irc.msg(source, "Error: Cannot force join a protected PyLink services client.")
|
||||||
|
return
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(source, "Error: Invalid channel name %r." % channel)
|
irc.msg(source, "Error: Invalid channel name %r." % channel)
|
||||||
@ -85,6 +91,9 @@ def nick(irc, source, args):
|
|||||||
elif not utils.isNick(newnick):
|
elif not utils.isNick(newnick):
|
||||||
irc.msg(source, 'Error: Invalid nickname %r.' % newnick)
|
irc.msg(source, 'Error: Invalid nickname %r.' % newnick)
|
||||||
return
|
return
|
||||||
|
elif not utils.isManipulatableClient(irc, u):
|
||||||
|
irc.msg(source, "Error: Cannot force nick changes for a protected PyLink services client.")
|
||||||
|
return
|
||||||
irc.proto.nickClient(u, newnick)
|
irc.proto.nickClient(u, newnick)
|
||||||
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
|
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
|
||||||
|
|
||||||
@ -102,6 +111,9 @@ def part(irc, source, args):
|
|||||||
irc.msg(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
|
return
|
||||||
u = utils.nickToUid(irc, nick)
|
u = utils.nickToUid(irc, nick)
|
||||||
|
if not utils.isManipulatableClient(irc, u):
|
||||||
|
irc.msg(source, "Error: Cannot force part a protected PyLink services client.")
|
||||||
|
return
|
||||||
for channel in clist:
|
for channel in clist:
|
||||||
if not utils.isChannel(channel):
|
if not utils.isChannel(channel):
|
||||||
irc.msg(source, "Error: Invalid channel name %r." % channel)
|
irc.msg(source, "Error: Invalid channel name %r." % channel)
|
||||||
@ -138,30 +150,35 @@ def kick(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 either the nick of a PyLink client, or the SID of a PyLink server."""
|
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is either the nick of a PyLink client, or the SID of a PyLink server. <target> can be either a nick or a channel."""
|
||||||
utils.checkAuthenticated(irc, source, allowOper=False)
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
try:
|
try:
|
||||||
modesource, target, modes = args[0], args[1], args[2:]
|
modesource, target, modes = args[0], args[1], args[2:]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
irc.msg(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
|
return
|
||||||
if not modes:
|
target = utils.nickToUid(irc, target) or target
|
||||||
irc.msg(source, "Error: No modes given to set!")
|
extclient = target in irc.users and not utils.isInternalClient(irc, target)
|
||||||
return
|
|
||||||
parsedmodes = utils.parseModes(irc, target, modes)
|
parsedmodes = utils.parseModes(irc, target, modes)
|
||||||
targetuid = utils.nickToUid(irc, target)
|
ischannel = target in irc.channels
|
||||||
if targetuid:
|
if not (target in irc.users or ischannel):
|
||||||
target = targetuid
|
|
||||||
elif not utils.isChannel(target):
|
|
||||||
irc.msg(source, "Error: Invalid channel or nick %r." % target)
|
irc.msg(source, "Error: Invalid channel or nick %r." % target)
|
||||||
return
|
return
|
||||||
|
elif not parsedmodes:
|
||||||
|
irc.msg(source, "Error: No valid modes were given.")
|
||||||
|
return
|
||||||
|
elif not (ischannel or utils.isManipulatableClient(irc, target)):
|
||||||
|
irc.msg(source, "Error: Can only set modes on channels or non-protected PyLink clients.")
|
||||||
|
return
|
||||||
if utils.isInternalServer(irc, modesource):
|
if utils.isInternalServer(irc, modesource):
|
||||||
|
# Setting modes from a server.
|
||||||
irc.proto.modeServer(modesource, target, parsedmodes)
|
irc.proto.modeServer(modesource, target, parsedmodes)
|
||||||
irc.callHooks([modesource, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
|
|
||||||
else:
|
else:
|
||||||
sourceuid = utils.nickToUid(irc, modesource)
|
# Setting modes from a client.
|
||||||
irc.proto.modeClient(sourceuid, target, parsedmodes)
|
modesource = utils.nickToUid(irc, modesource)
|
||||||
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
|
irc.proto.modeClient(modesource, target, parsedmodes)
|
||||||
|
irc.callHooks([modesource, 'PYLINK_BOTSPLUGIN_MODE',
|
||||||
|
{'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def msg(irc, source, args):
|
def msg(irc, source, args):
|
||||||
|
@ -126,6 +126,52 @@ def showuser(irc, source, args):
|
|||||||
(userobj.realhost, userobj.ip, userobj.away or '\x1D(not set)\x1D'))
|
(userobj.realhost, userobj.ip, userobj.away or '\x1D(not set)\x1D'))
|
||||||
f('\x02Channels\x02: %s' % (' '.join(userobj.channels).strip() or '\x1D(none)\x1D'))
|
f('\x02Channels\x02: %s' % (' '.join(userobj.channels).strip() or '\x1D(none)\x1D'))
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def showchan(irc, source, args):
|
||||||
|
"""<channel>
|
||||||
|
|
||||||
|
Shows information about <channel>."""
|
||||||
|
try:
|
||||||
|
channel = utils.toLower(irc, args[0])
|
||||||
|
except IndexError:
|
||||||
|
irc.msg(source, "Error: Not enough arguments. Needs 1: channel.")
|
||||||
|
return
|
||||||
|
if channel not in irc.channels:
|
||||||
|
irc.msg(source, 'Error: Unknown channel %r.' % channel)
|
||||||
|
return
|
||||||
|
|
||||||
|
f = lambda s: irc.msg(source, s)
|
||||||
|
c = irc.channels[channel]
|
||||||
|
# Only show verbose info if caller is oper or is in the target channel.
|
||||||
|
verbose = source in c.users or utils.isOper(irc, source)
|
||||||
|
secret = ('s', None) in c.modes
|
||||||
|
if secret and not verbose:
|
||||||
|
# Hide secret channels from normal users.
|
||||||
|
irc.msg(source, 'Error: Unknown channel %r.' % channel)
|
||||||
|
return
|
||||||
|
|
||||||
|
nicks = [irc.users[u].nick for u in c.users]
|
||||||
|
pmodes = ('owner', 'admin', 'op', 'halfop', 'voice')
|
||||||
|
|
||||||
|
f('Information on channel \x02%s\x02:' % channel)
|
||||||
|
f('\x02Channel topic\x02: %s' % c.topic)
|
||||||
|
f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts))
|
||||||
|
# Show only modes that aren't list-style modes.
|
||||||
|
modes = utils.joinModes([m for m in c.modes if m[0] not in irc.cmodes['*A']])
|
||||||
|
f('\x02Channel modes\x02: %s' % modes)
|
||||||
|
if verbose:
|
||||||
|
nicklist = []
|
||||||
|
# Iterate over the user list, sorted by nick.
|
||||||
|
for user, nick in sorted(zip(c.users, nicks),
|
||||||
|
key=lambda userpair: userpair[1].lower()):
|
||||||
|
prefixmodes = [irc.prefixmodes.get(irc.cmodes.get(pmode, ''), '')
|
||||||
|
for pmode in pmodes if user in c.prefixmodes[pmode+'s']]
|
||||||
|
nicklist.append(''.join(prefixmodes) + nick)
|
||||||
|
|
||||||
|
while nicklist[:20]: # 20 nicks per line to prevent message cutoff.
|
||||||
|
f('\x02User list\x02: %s' % ' '.join(nicklist[:20]))
|
||||||
|
nicklist = nicklist[20:]
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def shutdown(irc, source, args):
|
def shutdown(irc, source, args):
|
||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
@ -139,3 +185,11 @@ def shutdown(irc, source, args):
|
|||||||
# Disable auto-connect first by setting the time to negative.
|
# Disable auto-connect first by setting the time to negative.
|
||||||
ircobj.serverdata['autoconnect'] = -1
|
ircobj.serverdata['autoconnect'] = -1
|
||||||
ircobj.aborted.set()
|
ircobj.aborted.set()
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def version(irc, source, args):
|
||||||
|
"""takes no arguments.
|
||||||
|
|
||||||
|
Returns the version of the currently running PyLink instance."""
|
||||||
|
irc.msg(source, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version)
|
||||||
|
irc.msg(source, "The source of this program is available at \x02%s\x02." % world.source)
|
||||||
|
@ -19,3 +19,17 @@ def _exec(irc, source, args):
|
|||||||
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
||||||
exec(args, globals(), locals())
|
exec(args, globals(), locals())
|
||||||
utils.add_cmd(_exec, 'exec')
|
utils.add_cmd(_exec, 'exec')
|
||||||
|
|
||||||
|
def _eval(irc, source, args):
|
||||||
|
"""<Python expression>
|
||||||
|
|
||||||
|
Admin-only. Evaluates the given Python expression and returns the result.
|
||||||
|
\x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02"""
|
||||||
|
utils.checkAuthenticated(irc, source, allowOper=False)
|
||||||
|
args = ' '.join(args)
|
||||||
|
if not args.strip():
|
||||||
|
irc.msg(source, 'No code entered!')
|
||||||
|
return
|
||||||
|
log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source))
|
||||||
|
irc.msg(source, eval(args))
|
||||||
|
utils.add_cmd(_eval, 'eval')
|
||||||
|
1071
plugins/relay.py
1071
plugins/relay.py
File diff suppressed because it is too large
Load Diff
@ -24,9 +24,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
|
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
|
||||||
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
|
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
|
||||||
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
|
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
|
||||||
|
self.sidgen = utils.TS6SIDGenerator(self.irc)
|
||||||
|
self.uidgen = {}
|
||||||
|
|
||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None):
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None,
|
||||||
|
manipulatable=False):
|
||||||
"""Spawns a client with nick <nick> on the given IRC connection.
|
"""Spawns a client with nick <nick> on the given IRC connection.
|
||||||
|
|
||||||
Note: No nick collision / valid nickname checks are done here; it is
|
Note: No nick collision / valid nickname checks are done here; it is
|
||||||
@ -34,17 +37,15 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
server = server or self.irc.sid
|
server = server or self.irc.sid
|
||||||
if not utils.isInternalServer(self.irc, server):
|
if not utils.isInternalServer(self.irc, server):
|
||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||||
# We need a separate UID generator instance for every PseudoServer
|
# Create an UIDGenerator instance for every SID, so that each gets
|
||||||
# we spawn. Otherwise, things won't wrap around properly.
|
# distinct values.
|
||||||
if server not in self.irc.uidgen:
|
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||||
self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
|
||||||
uid = self.irc.uidgen[server].next_uid()
|
|
||||||
ts = ts or int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or self.irc.botdata['realname']
|
realname = realname or self.irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip)
|
realhost=realhost, ip=ip, manipulatable=manipulatable)
|
||||||
utils.applyModes(self.irc, uid, modes)
|
utils.applyModes(self.irc, uid, modes)
|
||||||
self.irc.servers[server].users.add(uid)
|
self.irc.servers[server].users.add(uid)
|
||||||
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
||||||
@ -53,7 +54,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
modes=raw_modes, ip=ip, realname=realname,
|
modes=raw_modes, ip=ip, realname=realname,
|
||||||
realhost=realhost))
|
realhost=realhost))
|
||||||
if ('o', None) in modes or ('+o', None) in modes:
|
if ('o', None) in modes or ('+o', None) in modes:
|
||||||
self._operUp(uid, opertype=opertype or 'IRC_Operator')
|
self._operUp(uid, opertype=opertype or 'IRC Operator')
|
||||||
return u
|
return u
|
||||||
|
|
||||||
def joinClient(self, client, channel):
|
def joinClient(self, client, channel):
|
||||||
@ -142,26 +143,26 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
and the change will be reflected here."""
|
and the change will be reflected here."""
|
||||||
userobj = self.irc.users[target]
|
userobj = self.irc.users[target]
|
||||||
try:
|
try:
|
||||||
otype = opertype or userobj.opertype
|
otype = opertype or userobj.opertype or 'IRC Operator'
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
log.debug('(%s) opertype field for %s (%s) isn\'t filled yet!',
|
log.debug('(%s) opertype field for %s (%s) isn\'t filled yet!',
|
||||||
self.irc.name, target, userobj.nick)
|
self.irc.name, target, userobj.nick)
|
||||||
# whatever, this is non-standard anyways.
|
# whatever, this is non-standard anyways.
|
||||||
otype = 'IRC_Operator'
|
otype = 'IRC Operator'
|
||||||
|
assert otype, "Tried to send an empty OPERTYPE!"
|
||||||
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
|
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
|
||||||
self.irc.name, target)
|
self.irc.name, target)
|
||||||
userobj.opertype = otype
|
userobj.opertype = otype
|
||||||
self._send(target, 'OPERTYPE %s' % otype)
|
self._send(target, 'OPERTYPE %s' % otype.replace(" ", "_"))
|
||||||
|
|
||||||
def _sendModes(self, numeric, target, modes, ts=None):
|
def _sendModes(self, numeric, target, modes, ts=None):
|
||||||
"""Internal function to send mode changes from a PyLink client/server."""
|
"""Internal function to send mode changes from a PyLink client/server."""
|
||||||
# -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
|
# -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
|
||||||
# -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
|
# -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
|
||||||
log.debug('(%s) inspself.ircd._sendModes: received %r for mode list', self.irc.name, modes)
|
log.debug('(%s) inspircd._sendModes: received %r for mode list', self.irc.name, modes)
|
||||||
if ('+o', None) in modes and not utils.isChannel(target):
|
if ('+o', None) in modes and not utils.isChannel(target):
|
||||||
# https://github.com/inspself.ircd/inspself.ircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
|
# https://github.com/inspself.ircd/inspself.ircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
|
||||||
# Servers need a special command to set umode +o on people.
|
# Servers need a special command to set umode +o on people.
|
||||||
# Why isn't this documented anywhere, InspIRCd?
|
|
||||||
self._operUp(target)
|
self._operUp(target)
|
||||||
utils.applyModes(self.irc, target, modes)
|
utils.applyModes(self.irc, target, modes)
|
||||||
joinedmodes = utils.joinModes(modes)
|
joinedmodes = utils.joinModes(modes)
|
||||||
@ -272,8 +273,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# "desc" defaults to the configured server description.
|
# "desc" defaults to the configured server description.
|
||||||
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
|
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
|
||||||
if sid is None: # No sid given; generate one!
|
if sid is None: # No sid given; generate one!
|
||||||
self.irc.sidgen = utils.TS6SIDGenerator(self.irc.serverdata["sidrange"])
|
sid = self.sidgen.next_sid()
|
||||||
sid = self.irc.sidgen.next_sid()
|
|
||||||
assert len(sid) == 3, "Incorrect SID length"
|
assert len(sid) == 3, "Incorrect SID length"
|
||||||
if sid in self.irc.servers:
|
if sid in self.irc.servers:
|
||||||
raise ValueError('A server with SID %r already exists!' % sid)
|
raise ValueError('A server with SID %r already exists!' % sid)
|
||||||
@ -285,7 +285,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
if not utils.isServerName(name):
|
if not utils.isServerName(name):
|
||||||
raise ValueError('Invalid server name %r' % name)
|
raise ValueError('Invalid server name %r' % name)
|
||||||
self._send(uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
|
self._send(uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
|
||||||
self.irc.servers[sid] = IrcServer(uplink, name, internal=True)
|
self.irc.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
|
||||||
self._send(sid, 'ENDBURST')
|
self._send(sid, 'ENDBURST')
|
||||||
return sid
|
return sid
|
||||||
|
|
||||||
@ -329,7 +329,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
if args[2] != self.irc.serverdata['recvpass']:
|
if args[2] != self.irc.serverdata['recvpass']:
|
||||||
# Check if recvpass is correct
|
# Check if recvpass is correct
|
||||||
raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername)
|
raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername)
|
||||||
self.irc.servers[numeric] = IrcServer(None, servername)
|
sdesc = ' '.join(args).split(':', 1)[1]
|
||||||
|
self.irc.servers[numeric] = IrcServer(None, servername, desc=sdesc)
|
||||||
self.irc.uplink = numeric
|
self.irc.uplink = numeric
|
||||||
return
|
return
|
||||||
elif args[0] == 'CAPAB':
|
elif args[0] == 'CAPAB':
|
||||||
@ -459,18 +460,20 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
servername = args[0].lower()
|
servername = args[0].lower()
|
||||||
sid = args[3]
|
sid = args[3]
|
||||||
sdesc = args[-1]
|
sdesc = args[-1]
|
||||||
self.irc.servers[sid] = IrcServer(numeric, servername)
|
self.irc.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
|
||||||
return {'name': servername, 'sid': args[3], 'text': sdesc}
|
return {'name': servername, 'sid': args[3], 'text': sdesc}
|
||||||
|
|
||||||
def handle_fmode(self, numeric, command, args):
|
def handle_fmode(self, numeric, command, args):
|
||||||
"""Handles the FMODE command, used for channel mode changes."""
|
"""Handles the FMODE command, used for channel mode changes."""
|
||||||
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
|
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
|
||||||
channel = utils.toLower(self.irc, args[0])
|
channel = utils.toLower(self.irc, args[0])
|
||||||
|
oldobj = self.irc.channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = utils.parseModes(self.irc, channel, modes)
|
changedmodes = utils.parseModes(self.irc, channel, modes)
|
||||||
utils.applyModes(self.irc, channel, changedmodes)
|
utils.applyModes(self.irc, channel, changedmodes)
|
||||||
ts = int(args[1])
|
ts = int(args[1])
|
||||||
return {'target': channel, 'modes': changedmodes, 'ts': ts}
|
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||||
|
'oldchan': oldobj}
|
||||||
|
|
||||||
def handle_mode(self, numeric, command, args):
|
def handle_mode(self, numeric, command, args):
|
||||||
"""Handles incoming user mode changes."""
|
"""Handles incoming user mode changes."""
|
||||||
@ -543,7 +546,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# command sent for it.
|
# command sent for it.
|
||||||
# <- :70MAAAAAB OPERTYPE Network_Owner
|
# <- :70MAAAAAB OPERTYPE Network_Owner
|
||||||
omode = [('+o', None)]
|
omode = [('+o', None)]
|
||||||
self.irc.users[numeric].opertype = opertype = args[0]
|
self.irc.users[numeric].opertype = opertype = args[0].replace("_", " ")
|
||||||
utils.applyModes(self.irc, numeric, omode)
|
utils.applyModes(self.irc, numeric, omode)
|
||||||
# OPERTYPE is essentially umode +o and metadata in one command;
|
# OPERTYPE is essentially umode +o and metadata in one command;
|
||||||
# we'll call that too.
|
# we'll call that too.
|
||||||
|
@ -17,9 +17,12 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
super(TS6Protocol, self).__init__(irc)
|
super(TS6Protocol, self).__init__(irc)
|
||||||
self.casemapping = 'rfc1459'
|
self.casemapping = 'rfc1459'
|
||||||
self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
|
self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
|
||||||
|
self.sidgen = utils.TS6SIDGenerator(self.irc)
|
||||||
|
self.uidgen = {}
|
||||||
|
|
||||||
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||||
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None):
|
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None,
|
||||||
|
manipulatable=False):
|
||||||
"""Spawns a client with nick <nick> on the given IRC connection.
|
"""Spawns a client with nick <nick> on the given IRC connection.
|
||||||
|
|
||||||
Note: No nick collision / valid nickname checks are done here; it is
|
Note: No nick collision / valid nickname checks are done here; it is
|
||||||
@ -27,11 +30,9 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
server = server or self.irc.sid
|
server = server or self.irc.sid
|
||||||
if not utils.isInternalServer(self.irc, server):
|
if not utils.isInternalServer(self.irc, server):
|
||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||||
# We need a separate UID generator instance for every PseudoServer
|
# Create an UIDGenerator instance for every SID, so that each gets
|
||||||
# we spawn. Otherwise, things won't wrap around properly.
|
# distinct values.
|
||||||
if server not in self.irc.uidgen:
|
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||||
self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
|
||||||
uid = self.irc.uidgen[server].next_uid()
|
|
||||||
# EUID:
|
# EUID:
|
||||||
# parameters: nickname, hopcount, nickTS, umodes, username,
|
# parameters: nickname, hopcount, nickTS, umodes, username,
|
||||||
# visible hostname, IP address, UID, real hostname, account name, gecos
|
# visible hostname, IP address, UID, real hostname, account name, gecos
|
||||||
@ -40,7 +41,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip)
|
realhost=realhost, ip=ip, manipulatable=manipulatable)
|
||||||
utils.applyModes(self.irc, uid, modes)
|
utils.applyModes(self.irc, uid, modes)
|
||||||
self.irc.servers[server].users.add(uid)
|
self.irc.servers[server].users.add(uid)
|
||||||
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||||
@ -99,7 +100,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
self.irc.channels[channel].modes.clear()
|
self.irc.channels[channel].modes.clear()
|
||||||
for p in self.irc.channels[channel].prefixmodes.values():
|
for p in self.irc.channels[channel].prefixmodes.values():
|
||||||
p.clear()
|
p.clear()
|
||||||
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, self.irc.name, ts,
|
log.debug("(%s) sending SJOIN to %s with ts %s (that's %r)", self.irc.name, channel, ts,
|
||||||
time.strftime("%c", time.localtime(ts)))
|
time.strftime("%c", time.localtime(ts)))
|
||||||
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
|
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
|
||||||
changedmodes = []
|
changedmodes = []
|
||||||
@ -135,6 +136,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
def _sendModes(self, numeric, target, modes, ts=None):
|
def _sendModes(self, numeric, target, modes, ts=None):
|
||||||
"""Internal function to send mode changes from a PyLink client/server."""
|
"""Internal function to send mode changes from a PyLink client/server."""
|
||||||
utils.applyModes(self.irc, target, modes)
|
utils.applyModes(self.irc, target, modes)
|
||||||
|
modes = list(modes)
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
|
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
|
||||||
# TMODE:
|
# TMODE:
|
||||||
@ -263,8 +265,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
name = name.lower()
|
name = name.lower()
|
||||||
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
|
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
|
||||||
if sid is None: # No sid given; generate one!
|
if sid is None: # No sid given; generate one!
|
||||||
self.irc.sidgen = utils.TS6SIDGenerator(self.irc.serverdata["sidrange"])
|
sid = self.sidgen.next_sid()
|
||||||
sid = self.irc.sidgen.next_sid()
|
|
||||||
assert len(sid) == 3, "Incorrect SID length"
|
assert len(sid) == 3, "Incorrect SID length"
|
||||||
if sid in self.irc.servers:
|
if sid in self.irc.servers:
|
||||||
raise ValueError('A server with SID %r already exists!' % sid)
|
raise ValueError('A server with SID %r already exists!' % sid)
|
||||||
@ -276,7 +277,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
if not utils.isServerName(name):
|
if not utils.isServerName(name):
|
||||||
raise ValueError('Invalid server name %r' % name)
|
raise ValueError('Invalid server name %r' % name)
|
||||||
self._send(uplink, 'SID %s 1 %s :%s' % (name, sid, desc))
|
self._send(uplink, 'SID %s 1 %s :%s' % (name, sid, desc))
|
||||||
self.irc.servers[sid] = IrcServer(uplink, name, internal=True)
|
self.irc.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
|
||||||
return sid
|
return sid
|
||||||
|
|
||||||
def squitServer(self, source, target, text='No reason given'):
|
def squitServer(self, source, target, text='No reason given'):
|
||||||
@ -407,6 +408,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
sname = args[1].lower()
|
sname = args[1].lower()
|
||||||
log.debug('(%s) Found uplink server name as %r', self.irc.name, sname)
|
log.debug('(%s) Found uplink server name as %r', self.irc.name, sname)
|
||||||
self.irc.servers[self.irc.uplink].name = sname
|
self.irc.servers[self.irc.uplink].name = sname
|
||||||
|
self.irc.servers[self.irc.uplink].desc = ' '.join(args).split(':', 1)[1]
|
||||||
# According to the TS6 protocol documentation, we should send SVINFO
|
# According to the TS6 protocol documentation, we should send SVINFO
|
||||||
# when we get our uplink's SERVER command.
|
# when we get our uplink's SERVER command.
|
||||||
self.irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
|
self.irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
|
||||||
@ -593,7 +595,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
# XXX: don't just save these by their server names; that's ugly!
|
# XXX: don't just save these by their server names; that's ugly!
|
||||||
sid = servername
|
sid = servername
|
||||||
sdesc = args[-1]
|
sdesc = args[-1]
|
||||||
self.irc.servers[sid] = IrcServer(numeric, servername)
|
self.irc.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
|
||||||
return {'name': servername, 'sid': sid, 'text': sdesc}
|
return {'name': servername, 'sid': sid, 'text': sdesc}
|
||||||
|
|
||||||
handle_sid = handle_server
|
handle_sid = handle_server
|
||||||
@ -602,11 +604,13 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
"""Handles incoming TMODE commands (channel mode change)."""
|
"""Handles incoming TMODE commands (channel mode change)."""
|
||||||
# <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
|
# <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
|
||||||
channel = utils.toLower(self.irc, args[1])
|
channel = utils.toLower(self.irc, args[1])
|
||||||
|
oldobj = self.irc.channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = utils.parseModes(self.irc, channel, modes)
|
changedmodes = utils.parseModes(self.irc, channel, modes)
|
||||||
utils.applyModes(self.irc, channel, changedmodes)
|
utils.applyModes(self.irc, channel, changedmodes)
|
||||||
ts = int(args[0])
|
ts = int(args[0])
|
||||||
return {'target': channel, 'modes': changedmodes, 'ts': ts}
|
return {'target': channel, 'modes': changedmodes, 'ts': ts,
|
||||||
|
'oldchan': oldobj}
|
||||||
|
|
||||||
def handle_mode(self, numeric, command, args):
|
def handle_mode(self, numeric, command, args):
|
||||||
"""Handles incoming user mode changes."""
|
"""Handles incoming user mode changes."""
|
||||||
|
@ -81,11 +81,12 @@ class TS6BaseProtocol(Protocol):
|
|||||||
# Clear empty non-permanent channels.
|
# 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)):
|
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]
|
del self.irc.channels[c]
|
||||||
|
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
|
||||||
|
|
||||||
sid = numeric[:3]
|
sid = numeric[:3]
|
||||||
log.debug('Removing client %s from self.irc.users', numeric)
|
log.debug('Removing client %s from self.irc.users', numeric)
|
||||||
del self.irc.users[numeric]
|
del self.irc.users[numeric]
|
||||||
log.debug('Removing client %s from self.irc.servers[%s]', numeric, sid)
|
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
|
||||||
self.irc.servers[sid].users.discard(numeric)
|
self.irc.servers[sid].users.discard(numeric)
|
||||||
|
|
||||||
def partClient(self, client, channel, reason=None):
|
def partClient(self, client, channel, reason=None):
|
||||||
@ -200,6 +201,7 @@ class TS6BaseProtocol(Protocol):
|
|||||||
split_server = args[0]
|
split_server = args[0]
|
||||||
affected_users = []
|
affected_users = []
|
||||||
log.info('(%s) Netsplit on server %s', self.irc.name, split_server)
|
log.info('(%s) Netsplit on server %s', self.irc.name, split_server)
|
||||||
|
assert split_server in self.irc.servers, "Tried to split a server (%s) that didn't exist!" % split_server
|
||||||
# Prevent RuntimeError: dictionary changed size during iteration
|
# Prevent RuntimeError: dictionary changed size during iteration
|
||||||
old_servers = self.irc.servers.copy()
|
old_servers = self.irc.servers.copy()
|
||||||
for sid, data in old_servers.items():
|
for sid, data in old_servers.items():
|
||||||
@ -211,9 +213,10 @@ class TS6BaseProtocol(Protocol):
|
|||||||
affected_users.append(user)
|
affected_users.append(user)
|
||||||
log.debug('Removing client %s (%s)', user, self.irc.users[user].nick)
|
log.debug('Removing client %s (%s)', user, self.irc.users[user].nick)
|
||||||
self.removeClient(user)
|
self.removeClient(user)
|
||||||
|
sname = self.irc.servers[split_server].name
|
||||||
del self.irc.servers[split_server]
|
del self.irc.servers[split_server]
|
||||||
log.debug('(%s) Netsplit affected users: %s', self.irc.name, affected_users)
|
log.debug('(%s) Netsplit affected users: %s', self.irc.name, affected_users)
|
||||||
return {'target': split_server, 'users': affected_users}
|
return {'target': split_server, 'users': affected_users, 'name': sname}
|
||||||
|
|
||||||
def handle_topic(self, numeric, command, args):
|
def handle_topic(self, numeric, command, args):
|
||||||
"""Handles incoming TOPIC changes from clients. For topic bursts,
|
"""Handles incoming TOPIC changes from clients. For topic bursts,
|
||||||
@ -222,9 +225,11 @@ class TS6BaseProtocol(Protocol):
|
|||||||
channel = utils.toLower(self.irc, args[0])
|
channel = utils.toLower(self.irc, args[0])
|
||||||
topic = args[1]
|
topic = args[1]
|
||||||
ts = int(time.time())
|
ts = int(time.time())
|
||||||
|
oldtopic = self.irc.channels[channel].topic
|
||||||
self.irc.channels[channel].topic = topic
|
self.irc.channels[channel].topic = topic
|
||||||
self.irc.channels[channel].topicset = True
|
self.irc.channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic}
|
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic,
|
||||||
|
'oldtopic': oldtopic}
|
||||||
|
|
||||||
def handle_part(self, source, command, args):
|
def handle_part(self, source, command, args):
|
||||||
"""Handles incoming PART commands."""
|
"""Handles incoming PART commands."""
|
||||||
|
@ -18,7 +18,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
self.casemapping = 'ascii'
|
self.casemapping = 'ascii'
|
||||||
self.proto_ver = 2351
|
self.proto_ver = 2351
|
||||||
self.hook_map = {}
|
self.hook_map = {}
|
||||||
|
self.uidgen = {}
|
||||||
|
|
||||||
self.caps = {}
|
self.caps = {}
|
||||||
self._unrealCmodes = {'l': 'limit', 'c': 'blockcolor', 'G': 'censor',
|
self._unrealCmodes = {'l': 'limit', 'c': 'blockcolor', 'G': 'censor',
|
||||||
@ -40,9 +40,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||||
# Unreal 3.4 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
|
# Unreal 3.4 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
|
||||||
# do, but we can do that fine...
|
# do, but we can do that fine...
|
||||||
if server not in self.irc.uidgen:
|
uid = self.uidgen.setdefault(server, utils.TS6UIDGenerator(server)).next_uid()
|
||||||
self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
|
||||||
uid = self.irc.uidgen[server].next_uid()
|
|
||||||
ts = ts or int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or self.irc.botdata['realname']
|
realname = realname or self.irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
|
4
pylink
4
pylink
@ -5,6 +5,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# This must be done before conf imports, so we get the real conf instead of testing one.
|
# This must be done before conf imports, so we get the real conf instead of testing one.
|
||||||
|
os.chdir(os.path.dirname(__file__))
|
||||||
import world
|
import world
|
||||||
world.testing = False
|
world.testing = False
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ import classes
|
|||||||
import coreplugin
|
import coreplugin
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log.info('PyLink starting...')
|
log.info('PyLink %s starting...', world.version)
|
||||||
if conf.conf['login']['password'] == 'changeme':
|
if conf.conf['login']['password'] == 'changeme':
|
||||||
log.critical("You have not set the login details correctly! Exiting...")
|
log.critical("You have not set the login details correctly! Exiting...")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
@ -61,4 +62,3 @@ if __name__ == '__main__':
|
|||||||
world.networkobjects[network] = classes.Irc(network, proto)
|
world.networkobjects[network] = classes.Irc(network, proto)
|
||||||
world.started.set()
|
world.started.set()
|
||||||
log.info("loaded plugins: %s", world.plugins)
|
log.info("loaded plugins: %s", world.plugins)
|
||||||
|
|
||||||
|
@ -11,7 +11,10 @@ suites = []
|
|||||||
|
|
||||||
# Yay, import hacks!
|
# Yay, import hacks!
|
||||||
sys.path.append(os.path.join(os.getcwd(), 'tests'))
|
sys.path.append(os.path.join(os.getcwd(), 'tests'))
|
||||||
for testfile in glob.glob('tests/test_*.py'):
|
|
||||||
|
query = sys.argv[1:] or glob.glob('tests/test_*.py')
|
||||||
|
|
||||||
|
for testfile in query:
|
||||||
# Strip the tests/ and .py extension: tests/test_whatever.py => test_whatever
|
# Strip the tests/ and .py extension: tests/test_whatever.py => test_whatever
|
||||||
module = testfile.replace('.py', '').replace('tests/', '')
|
module = testfile.replace('.py', '').replace('tests/', '')
|
||||||
module = __import__(module)
|
module = __import__(module)
|
||||||
|
@ -139,25 +139,12 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
|
|||||||
# Default channels start with +nt
|
# Default channels start with +nt
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 -nt')
|
self.irc.run(':70M FMODE #pylink 1423790412 -nt')
|
||||||
self.assertEqual(set(), self.irc.channels['#pylink'].modes)
|
self.assertEqual(set(), self.irc.channels['#pylink'].modes)
|
||||||
self.irc.takeHooks()
|
|
||||||
|
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +ikl herebedragons 100')
|
self.irc.run(':70M FMODE #pylink 1423790412 +ikl herebedragons 100')
|
||||||
self.assertEqual({('i', None), ('k', 'herebedragons'), ('l', '100')}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('i', None), ('k', 'herebedragons'), ('l', '100')}, self.irc.channels['#pylink'].modes)
|
||||||
self.irc.run(':70M FMODE #pylink 1423790413 -ilk+m herebedragons')
|
self.irc.run(':70M FMODE #pylink 1423790413 -ilk+m herebedragons')
|
||||||
self.assertEqual({('m', None)}, self.irc.channels['#pylink'].modes)
|
self.assertEqual({('m', None)}, self.irc.channels['#pylink'].modes)
|
||||||
|
|
||||||
hookdata = self.irc.takeHooks()
|
|
||||||
expected = [['70M', 'FMODE', {'target': '#pylink', 'modes':
|
|
||||||
[('+i', None), ('+k', 'herebedragons'),
|
|
||||||
('+l', '100')], 'ts': 1423790412}
|
|
||||||
],
|
|
||||||
['70M', 'FMODE', {'target': '#pylink', 'modes':
|
|
||||||
[('-i', None), ('-l', None),
|
|
||||||
('-k', 'herebedragons'), ('+m', None)],
|
|
||||||
'ts': 1423790413}]
|
|
||||||
]
|
|
||||||
self.assertEqual(expected, hookdata)
|
|
||||||
|
|
||||||
def testHandleFModeWithPrefixes(self):
|
def testHandleFModeWithPrefixes(self):
|
||||||
self.irc.run(':70M FJOIN #pylink 123 +n :o,10XAAAAAA ,10XAAAAAB')
|
self.irc.run(':70M FJOIN #pylink 123 +n :o,10XAAAAAA ,10XAAAAAB')
|
||||||
# Prefix modes are stored separately, so they should never show up in .modes
|
# Prefix modes are stored separately, so they should never show up in .modes
|
||||||
@ -170,13 +157,6 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
|
|||||||
self.irc.run(':70M FMODE #pylink 123 -o %s' % self.u)
|
self.irc.run(':70M FMODE #pylink 123 -o %s' % self.u)
|
||||||
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
self.assertEqual(modes, self.irc.channels['#pylink'].modes)
|
||||||
self.assertNotIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
self.assertNotIn(self.u, self.irc.channels['#pylink'].prefixmodes['ops'])
|
||||||
# Test hooks
|
|
||||||
hookdata = self.irc.takeHooks()
|
|
||||||
expected = [['70M', 'FJOIN', {'channel': '#pylink', 'ts': 123, 'modes': [('+n', None)],
|
|
||||||
'users': ['10XAAAAAA', '10XAAAAAB']}],
|
|
||||||
['70M', 'FMODE', {'target': '#pylink', 'modes': [('+l', '50'), ('+o', '9PYAAAAAA'), ('+t', None)], 'ts': 123}],
|
|
||||||
['70M', 'FMODE', {'target': '#pylink', 'modes': [('-o', '9PYAAAAAA')], 'ts': 123}]]
|
|
||||||
self.assertEqual(expected, hookdata)
|
|
||||||
|
|
||||||
def testHandleFModeRemovesOldParams(self):
|
def testHandleFModeRemovesOldParams(self):
|
||||||
self.irc.run(':70M FMODE #pylink 1423790412 +l 50')
|
self.irc.run(':70M FMODE #pylink 1423790412 +l 50')
|
||||||
@ -184,10 +164,6 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
|
|||||||
self.irc.run(':70M FMODE #pylink 1423790412 +l 30')
|
self.irc.run(':70M FMODE #pylink 1423790412 +l 30')
|
||||||
self.assertIn(('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)
|
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}]]
|
|
||||||
self.assertEqual(expected, hookdata)
|
|
||||||
|
|
||||||
def testHandleFJoinResetsTS(self):
|
def testHandleFJoinResetsTS(self):
|
||||||
curr_ts = self.irc.channels['#pylink'].ts
|
curr_ts = self.irc.channels['#pylink'].ts
|
||||||
@ -238,24 +214,24 @@ class InspIRCdTestCase(tests_common.CommonProtoTestCase):
|
|||||||
self.assertIn('00C', self.irc.servers)
|
self.assertIn('00C', self.irc.servers)
|
||||||
|
|
||||||
def testHandleNick(self):
|
def testHandleNick(self):
|
||||||
self.irc.run(':9PYAAAAAA NICK PyLink-devel 1434744242')
|
self.irc.run(':%s NICK PyLink-devel 1434744242' % self.u)
|
||||||
hookdata = self.irc.takeHooks()[0][-1]
|
hookdata = self.irc.takeHooks()[0][-1]
|
||||||
expected = {'newnick': 'PyLink-devel', 'oldnick': 'PyLink', 'ts': 1434744242}
|
expected = {'newnick': 'PyLink-devel', 'oldnick': 'PyLink', 'ts': 1434744242}
|
||||||
self.assertEqual(hookdata, expected)
|
self.assertEqual(hookdata, expected)
|
||||||
self.assertEqual('PyLink-devel', self.irc.users['9PYAAAAAA'].nick)
|
self.assertEqual('PyLink-devel', self.irc.users[self.u].nick)
|
||||||
|
|
||||||
def testHandleSave(self):
|
def testHandleSave(self):
|
||||||
self.irc.run(':9PYAAAAAA NICK Derp_ 1433728673')
|
self.irc.run(':%s NICK Derp_ 1433728673' % self.u)
|
||||||
self.irc.run(':70M SAVE 9PYAAAAAA 1433728673')
|
self.irc.run(':70M SAVE %s 1433728673' % self.u)
|
||||||
hookdata = self.irc.takeHooks()[-1][-1]
|
hookdata = self.irc.takeHooks()[-1][-1]
|
||||||
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'ts': 1433728673, 'oldnick': 'Derp_'})
|
self.assertEqual(hookdata, {'target': self.u, 'ts': 1433728673, 'oldnick': 'Derp_'})
|
||||||
self.assertEqual('9PYAAAAAA', self.irc.users['9PYAAAAAA'].nick)
|
self.assertEqual(self.u, self.irc.users[self.u].nick)
|
||||||
|
|
||||||
def testHandleInvite(self):
|
def testHandleInvite(self):
|
||||||
self.irc.run(':10XAAAAAA INVITE 9PYAAAAAA #blah 0')
|
self.irc.run(':10XAAAAAA INVITE %s #blah 0' % self.u)
|
||||||
hookdata = self.irc.takeHooks()[-1][-1]
|
hookdata = self.irc.takeHooks()[-1][-1]
|
||||||
del hookdata['ts']
|
del hookdata['ts']
|
||||||
self.assertEqual(hookdata, {'target': '9PYAAAAAA', 'channel': '#blah'})
|
self.assertEqual(hookdata, {'target': self.u, 'channel': '#blah'})
|
||||||
|
|
||||||
def testHandleOpertype(self):
|
def testHandleOpertype(self):
|
||||||
self.irc.run('SERVER whatever. abcd 0 10X :Whatever Server - Hellas Planitia, Mars')
|
self.irc.run('SERVER whatever. abcd 0 10X :Whatever Server - Hellas Planitia, Mars')
|
||||||
|
@ -101,19 +101,50 @@ class TestUtils(unittest.TestCase):
|
|||||||
('+b', '*!*@*.badisp.net')])
|
('+b', '*!*@*.badisp.net')])
|
||||||
self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net')
|
self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net')
|
||||||
|
|
||||||
@unittest.skip('Wait, we need to work out the kinks first! (reversing changes of modes with arguments)')
|
def _reverseModes(self, query, expected, target='#test'):
|
||||||
|
res = utils.reverseModes(self.irc, target, query)
|
||||||
|
self.assertEqual(res, expected)
|
||||||
|
|
||||||
def testReverseModes(self):
|
def testReverseModes(self):
|
||||||
f = lambda x: utils.reverseModes(self.irc, '#test', x)
|
test = lambda x, y: self.assertEqual(utils.reverseModes(self.irc, '#test', x), y)
|
||||||
# Strings.
|
# Strings.
|
||||||
self.assertEqual(f("+nt-lk"), "-nt+lk")
|
self._reverseModes("+mk-t test", "-mk+t test")
|
||||||
self.assertEqual(f("nt-k"), "-nt+k")
|
self._reverseModes("ml-n 111", "-ml+n")
|
||||||
# Lists.
|
# Lists.
|
||||||
self.assertEqual(f([('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')]),
|
self._reverseModes([('+m', None), ('+r', None), ('+l', '3')],
|
||||||
[('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')])
|
{('-m', None), ('-r', None), ('-l', None)})
|
||||||
# Sets.
|
# Sets.
|
||||||
self.assertEqual(f({('s', None), ('+o', 'whoever')}), {('-s', None), ('-o', 'whoever')})
|
self._reverseModes({('s', None)}, {('-s', None)})
|
||||||
# Combining modes with an initial + and those without
|
# Combining modes with an initial + and those without
|
||||||
self.assertEqual(f({('s', None), ('+n', None)}), {('-s', None), ('-n', None)})
|
self._reverseModes({('s', None), ('+R', None)}, {('-s', None), ('-R', None)})
|
||||||
|
|
||||||
|
def testReverseModesUser(self):
|
||||||
|
self._reverseModes({('+i', None), ('l', 'asfasd')}, {('-i', None), ('-l', 'asfasd')},
|
||||||
|
target=self.irc.pseudoclient.uid)
|
||||||
|
|
||||||
|
def testReverseModesExisting(self):
|
||||||
|
utils.applyModes(self.irc, '#test', [('+m', None), ('+l', '50'), ('+k', 'supersecret'),
|
||||||
|
('+o', '9PYAAAAAA')])
|
||||||
|
|
||||||
|
self._reverseModes({('+i', None), ('+l', '3')}, {('-i', None), ('+l', '50')})
|
||||||
|
self._reverseModes('-n', '+n')
|
||||||
|
self._reverseModes('-l', '+l 50')
|
||||||
|
self._reverseModes('+k derp', '+k supersecret')
|
||||||
|
self._reverseModes('-mk *', '+mk supersecret')
|
||||||
|
|
||||||
|
# Existing modes are ignored.
|
||||||
|
self._reverseModes([('+t', None)], set())
|
||||||
|
self._reverseModes('+n', '+')
|
||||||
|
self._reverseModes('+oo GLolol 9PYAAAAAA', '-o GLolol')
|
||||||
|
self._reverseModes('+o 9PYAAAAAA', '+')
|
||||||
|
self._reverseModes('+vvvvM test abcde atat abcd', '-vvvvM test abcde atat abcd')
|
||||||
|
|
||||||
|
# Ignore unsetting prefixmodes/list modes that were never set.
|
||||||
|
self._reverseModes([('-v', '10XAAAAAA')], set())
|
||||||
|
self._reverseModes('-ob 10XAAAAAA derp!*@*', '+')
|
||||||
|
utils.applyModes(self.irc, '#test', [('+o', 'GLolol'), ('+b', '*!user@badisp.tk')])
|
||||||
|
self._reverseModes('-voo GLolol GLolol 10XAAAAAA', '+o GLolol')
|
||||||
|
self._reverseModes('-bb *!*@* *!user@badisp.tk', '+b *!user@badisp.tk')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
212
utils.py
212
utils.py
@ -40,10 +40,9 @@ class TS6UIDGenerator():
|
|||||||
return uid
|
return uid
|
||||||
|
|
||||||
class TS6SIDGenerator():
|
class TS6SIDGenerator():
|
||||||
"""<query>
|
"""
|
||||||
|
|
||||||
TS6 SID Generator. <query> is a 3 character string with any combination of
|
TS6 SID Generator. <query> is a 3 character string with any combination of
|
||||||
uppercase letters, digits, and #'s. <query> must contain at least one #,
|
uppercase letters, digits, and #'s. it must contain at least one #,
|
||||||
which are used by the generator as a wildcard. On every next_sid() call,
|
which are used by the generator as a wildcard. On every next_sid() call,
|
||||||
the first available wildcard character (from the right) will be
|
the first available wildcard character (from the right) will be
|
||||||
incremented to generate the next SID.
|
incremented to generate the next SID.
|
||||||
@ -57,8 +56,12 @@ class TS6SIDGenerator():
|
|||||||
"6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results)
|
"6##" would give: 600, 601, 602, ... 60Y, 60Z, 610, 611, ... 6ZZ (1296 total results)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, query):
|
def __init__(self, irc):
|
||||||
self.query = list(query)
|
self.irc = irc
|
||||||
|
try:
|
||||||
|
self.query = query = list(irc.serverdata["sidrange"])
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError('(%s) "sidrange" is missing from your server configuration block!' % irc.name)
|
||||||
self.iters = self.query.copy()
|
self.iters = self.query.copy()
|
||||||
self.output = self.query.copy()
|
self.output = self.query.copy()
|
||||||
self.allowedchars = {}
|
self.allowedchars = {}
|
||||||
@ -94,8 +97,9 @@ class TS6SIDGenerator():
|
|||||||
self.increment(pos-1)
|
self.increment(pos-1)
|
||||||
|
|
||||||
def next_sid(self):
|
def next_sid(self):
|
||||||
|
while ''.join(self.output) in self.irc.servers:
|
||||||
|
self.increment()
|
||||||
sid = ''.join(self.output)
|
sid = ''.join(self.output)
|
||||||
self.increment()
|
|
||||||
return sid
|
return sid
|
||||||
|
|
||||||
def add_cmd(func, name=None):
|
def add_cmd(func, name=None):
|
||||||
@ -110,10 +114,8 @@ def add_hook(func, command):
|
|||||||
world.command_hooks[command].append(func)
|
world.command_hooks[command].append(func)
|
||||||
|
|
||||||
def toLower(irc, text):
|
def toLower(irc, text):
|
||||||
"""<irc object> <text>
|
"""Returns a lowercase representation of text based on the IRC object's
|
||||||
|
casemapping (rfc1459 or ascii)."""
|
||||||
Returns a lowercase representation of <text> based on <irc object>'s
|
|
||||||
casemapping (rfc1459 vs ascii)."""
|
|
||||||
if irc.proto.casemapping == 'rfc1459':
|
if irc.proto.casemapping == 'rfc1459':
|
||||||
text = text.replace('{', '[')
|
text = text.replace('{', '[')
|
||||||
text = text.replace('}', ']')
|
text = text.replace('}', ']')
|
||||||
@ -122,39 +124,42 @@ def toLower(irc, text):
|
|||||||
return text.lower()
|
return text.lower()
|
||||||
|
|
||||||
def nickToUid(irc, nick):
|
def nickToUid(irc, nick):
|
||||||
"""<irc object> <nick>
|
"""Returns the UID of a user named nick, if present."""
|
||||||
|
|
||||||
Returns the UID of a user named <nick>, if present."""
|
|
||||||
nick = toLower(irc, nick)
|
nick = toLower(irc, nick)
|
||||||
for k, v in irc.users.items():
|
for k, v in irc.users.items():
|
||||||
if toLower(irc, v.nick) == nick:
|
if toLower(irc, v.nick) == nick:
|
||||||
return k
|
return k
|
||||||
|
|
||||||
def clientToServer(irc, numeric):
|
def clientToServer(irc, numeric):
|
||||||
"""<irc object> <numeric>
|
"""Finds the SID of the server a user is on."""
|
||||||
|
|
||||||
Finds the server SID of user <numeric> and returns it."""
|
|
||||||
for server in irc.servers:
|
for server in irc.servers:
|
||||||
if numeric in irc.servers[server].users:
|
if numeric in irc.servers[server].users:
|
||||||
return server
|
return server
|
||||||
|
|
||||||
# A+ regex
|
|
||||||
_nickregex = r'^[A-Za-z\|\\_\[\]\{\}\^\`][A-Z0-9a-z\-\|\\_\[\]\{\}\^\`]*$'
|
_nickregex = r'^[A-Za-z\|\\_\[\]\{\}\^\`][A-Z0-9a-z\-\|\\_\[\]\{\}\^\`]*$'
|
||||||
def isNick(s, nicklen=None):
|
def isNick(s, nicklen=None):
|
||||||
|
"""Checks whether the string given is a valid nick."""
|
||||||
if nicklen and len(s) > nicklen:
|
if nicklen and len(s) > nicklen:
|
||||||
return False
|
return False
|
||||||
return bool(re.match(_nickregex, s))
|
return bool(re.match(_nickregex, s))
|
||||||
|
|
||||||
def isChannel(s):
|
def isChannel(s):
|
||||||
return s.startswith('#')
|
"""Checks whether the string given is a valid channel name."""
|
||||||
|
return str(s).startswith('#')
|
||||||
|
|
||||||
def _isASCII(s):
|
def _isASCII(s):
|
||||||
chars = string.ascii_letters + string.digits + string.punctuation
|
chars = string.ascii_letters + string.digits + string.punctuation
|
||||||
return all(char in chars for char in s)
|
return all(char in chars for char in s)
|
||||||
|
|
||||||
def isServerName(s):
|
def isServerName(s):
|
||||||
|
"""Checks whether the string given is a server name."""
|
||||||
return _isASCII(s) and '.' in s and not s.startswith('.')
|
return _isASCII(s) and '.' in s and not s.startswith('.')
|
||||||
|
|
||||||
|
hostmaskRe = re.compile(r'^\S+!\S+@\S+$')
|
||||||
|
def isHostmask(text):
|
||||||
|
"""Returns whether the given text is a valid hostmask."""
|
||||||
|
return bool(hostmaskRe.match(text))
|
||||||
|
|
||||||
def parseModes(irc, target, args):
|
def parseModes(irc, target, args):
|
||||||
"""Parses a modestring list into a list of (mode, argument) tuples.
|
"""Parses a modestring list into a list of (mode, argument) tuples.
|
||||||
['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')]
|
['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')]
|
||||||
@ -164,11 +169,10 @@ def parseModes(irc, target, args):
|
|||||||
# B = Mode that changes a setting and always has a parameter.
|
# B = Mode that changes a setting and always has a parameter.
|
||||||
# C = Mode that changes a setting and only has a parameter when set.
|
# C = Mode that changes a setting and only has a parameter when set.
|
||||||
# D = Mode that changes a setting and never has a parameter.
|
# D = Mode that changes a setting and never has a parameter.
|
||||||
|
assert args, 'No valid modes were supplied!'
|
||||||
usermodes = not isChannel(target)
|
usermodes = not isChannel(target)
|
||||||
prefix = ''
|
prefix = ''
|
||||||
modestring = args[0]
|
modestring = args[0]
|
||||||
if not modestring:
|
|
||||||
return ValueError('No modes supplied in parseModes query: %r' % modes)
|
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
if usermodes:
|
if usermodes:
|
||||||
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
|
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
|
||||||
@ -207,6 +211,13 @@ def parseModes(irc, target, args):
|
|||||||
# We're setting a prefix mode on someone (e.g. +o user1)
|
# We're setting a prefix mode on someone (e.g. +o user1)
|
||||||
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
|
# Convert nicks to UIDs implicitly; most IRCds will want
|
||||||
|
# this already.
|
||||||
|
arg = nickToUid(irc, arg) or arg
|
||||||
|
if arg not in irc.users: # Target doesn't exist, skip it.
|
||||||
|
log.debug('(%s) Skipping setting mode "%s %s"; the '
|
||||||
|
'target doesn\'t seem to exist!')
|
||||||
|
continue
|
||||||
elif prefix == '+' and mode in supported_modes['*C']:
|
elif prefix == '+' and mode in supported_modes['*C']:
|
||||||
# Only has parameter when setting.
|
# Only has parameter when setting.
|
||||||
log.debug('Mode %s: Only has parameter when setting.', mode)
|
log.debug('Mode %s: Only has parameter when setting.', mode)
|
||||||
@ -220,10 +231,9 @@ def parseModes(irc, target, args):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def applyModes(irc, target, changedmodes):
|
def applyModes(irc, target, changedmodes):
|
||||||
"""<target> <changedmodes>
|
"""Takes a list of parsed IRC modes, and applies them on the given target.
|
||||||
|
|
||||||
Takes a list of parsed IRC modes (<changedmodes>, in the format of parseModes()), and applies them on <target>.
|
The target can be either a channel or a user; this is handled automatically."""
|
||||||
<target> can be either a channel or a user; this is handled automatically."""
|
|
||||||
usermodes = not isChannel(target)
|
usermodes = not isChannel(target)
|
||||||
log.debug('(%s) Using usermodes for this query? %s', irc.name, usermodes)
|
log.debug('(%s) Using usermodes for this query? %s', irc.name, usermodes)
|
||||||
if usermodes:
|
if usermodes:
|
||||||
@ -292,11 +302,10 @@ def applyModes(irc, target, changedmodes):
|
|||||||
irc.channels[target].modes = modelist
|
irc.channels[target].modes = modelist
|
||||||
|
|
||||||
def joinModes(modes):
|
def joinModes(modes):
|
||||||
"""<mode list>
|
"""Takes a list of (mode, arg) tuples in parseModes() format, and
|
||||||
|
joins them into a string.
|
||||||
|
|
||||||
Takes a list of (mode, arg) tuples in parseModes() format, and
|
See testJoinModes in tests/test_utils.py for some examples."""
|
||||||
joins them into a string. See testJoinModes in tests/test_utils.py
|
|
||||||
for some examples."""
|
|
||||||
prefix = '+' # Assume we're adding modes unless told otherwise
|
prefix = '+' # Assume we're adding modes unless told otherwise
|
||||||
modelist = ''
|
modelist = ''
|
||||||
args = []
|
args = []
|
||||||
@ -328,55 +337,112 @@ def joinModes(modes):
|
|||||||
modelist += ' %s' % ' '.join(args)
|
modelist += ' %s' % ' '.join(args)
|
||||||
return modelist
|
return modelist
|
||||||
|
|
||||||
def reverseModes(irc, target, modes):
|
def _flip(mode):
|
||||||
"""<mode string/mode list>
|
"""Flips a mode character."""
|
||||||
|
# Make it a list first, strings don't support item assignment
|
||||||
|
mode = list(mode)
|
||||||
|
if mode[0] == '-': # Query is something like "-n"
|
||||||
|
mode[0] = '+' # Change it to "+n"
|
||||||
|
elif mode[0] == '+':
|
||||||
|
mode[0] = '-'
|
||||||
|
else: # No prefix given, assume +
|
||||||
|
mode.insert(0, '-')
|
||||||
|
return ''.join(mode)
|
||||||
|
|
||||||
Reverses/Inverts the mode string or mode list given.
|
def reverseModes(irc, target, modes, oldobj=None):
|
||||||
|
"""Reverses/Inverts the mode string or mode list given.
|
||||||
|
|
||||||
"+nt-lk" => "-nt+lk"
|
Optionally, an oldobj argument can be given to look at an earlier state of
|
||||||
"nt-k" => "-nt+k"
|
a channel/user object, e.g. for checking the op status of a mode setter
|
||||||
[('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')] =>
|
before their modes are processed and added to the channel state.
|
||||||
[('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')]
|
|
||||||
[('s', None), ('+n', None)] => [('-s', None), ('-n', None)]
|
This function allows both mode strings or mode lists. Example uses:
|
||||||
|
"+mi-lk test => "-mi+lk test"
|
||||||
|
"mi-k test => "-mi+k test"
|
||||||
|
[('+m', None), ('+r', None), ('+l', '3'), ('-o', 'person')
|
||||||
|
=> {('-m', None), ('-r', None), ('-l', None), ('+o', 'person')})
|
||||||
|
{('s', None), ('+o', 'whoever') => {('-s', None), ('-o', 'whoever')})
|
||||||
"""
|
"""
|
||||||
origtype = type(modes)
|
origtype = type(modes)
|
||||||
# Operate on joined modestrings only; it's easier.
|
# If the query is a string, we have to parse it first.
|
||||||
if origtype != str:
|
if origtype == str:
|
||||||
modes = joinModes(modes)
|
modes = parseModes(irc, target, modes.split(" "))
|
||||||
# Swap the +'s and -'s by replacing one with a dummy character, and then changing it back.
|
# Get the current mode list first.
|
||||||
assert '\x00' not in modes, 'NUL cannot be in the mode list (it is a reserved character)!'
|
if isChannel(target):
|
||||||
if not modes.startswith(('+', '-')):
|
c = oldobj or irc.channels[target]
|
||||||
modes = '+' + modes
|
oldmodes = c.modes.copy()
|
||||||
newmodes = modes.replace('+', '\x00')
|
possible_modes = irc.cmodes.copy()
|
||||||
newmodes = newmodes.replace('-', '+')
|
# For channels, this also includes the list of prefix modes.
|
||||||
newmodes = newmodes.replace('\x00', '-')
|
possible_modes['*A'] += ''.join(irc.prefixmodes)
|
||||||
if origtype != str:
|
for name, userlist in c.prefixmodes.items():
|
||||||
# If the original query isn't a string, send back the parseModes() output.
|
try:
|
||||||
return parseModes(irc, target, newmodes.split(" "))
|
oldmodes.update([(irc.cmodes[name[:-1]], u) for u in userlist])
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
return newmodes
|
oldmodes = irc.users[target].modes
|
||||||
|
possible_modes = irc.umodes
|
||||||
|
newmodes = []
|
||||||
|
log.debug('(%s) reverseModes: old/current mode list for %s is: %s', irc.name,
|
||||||
|
target, oldmodes)
|
||||||
|
for char, arg in modes:
|
||||||
|
# Mode types:
|
||||||
|
# A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||||
|
# B = Mode that changes a setting and always has a parameter.
|
||||||
|
# C = Mode that changes a setting and only has a parameter when set.
|
||||||
|
# D = Mode that changes a setting and never has a parameter.
|
||||||
|
mchar = char[-1]
|
||||||
|
if mchar in possible_modes['*B'] + possible_modes['*C']:
|
||||||
|
# We need to find the current mode list, so we can reset arguments
|
||||||
|
# for modes that have arguments. For example, setting +l 30 on a channel
|
||||||
|
# that had +l 50 set should give "+l 30", not "-l".
|
||||||
|
oldarg = [m for m in oldmodes if m[0] == mchar]
|
||||||
|
if oldarg: # Old mode argument for this mode existed, use that.
|
||||||
|
oldarg = oldarg[0]
|
||||||
|
mpair = ('+%s' % oldarg[0], oldarg[1])
|
||||||
|
else: # Not found, flip the mode then.
|
||||||
|
# Mode takes no arguments when unsetting.
|
||||||
|
if mchar in possible_modes['*C'] and char[0] != '-':
|
||||||
|
arg = None
|
||||||
|
mpair = (_flip(char), arg)
|
||||||
|
else:
|
||||||
|
mpair = (_flip(char), arg)
|
||||||
|
if char[0] != '-' and (mchar, arg) in oldmodes:
|
||||||
|
# Mode is already set.
|
||||||
|
log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since we're "
|
||||||
|
"setting a mode that's already set.", irc.name, char, arg, mpair)
|
||||||
|
continue
|
||||||
|
elif char[0] == '-' and (mchar, arg) not in oldmodes and mchar in possible_modes['*A']:
|
||||||
|
# We're unsetting a prefixmode that was never set - don't set it in response!
|
||||||
|
# Charybdis lacks verification for this server-side.
|
||||||
|
log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since it "
|
||||||
|
"wasn't previously set.", irc.name, char, arg, mpair)
|
||||||
|
continue
|
||||||
|
newmodes.append(mpair)
|
||||||
|
|
||||||
|
log.debug('(%s) reverseModes: new modes: %s', irc.name, newmodes)
|
||||||
|
if origtype == str:
|
||||||
|
# If the original query is a string, send it back as a string.
|
||||||
|
return joinModes(newmodes)
|
||||||
|
else:
|
||||||
|
return set(newmodes)
|
||||||
|
|
||||||
def isInternalClient(irc, numeric):
|
def isInternalClient(irc, numeric):
|
||||||
"""<irc object> <client numeric>
|
"""
|
||||||
|
Checks whether the given numeric is a PyLink Client,
|
||||||
Checks whether <client numeric> is a PyLink PseudoClient,
|
returning the SID of the server it's on if so.
|
||||||
returning the SID of the PseudoClient's server if True.
|
|
||||||
"""
|
"""
|
||||||
for sid in irc.servers:
|
for sid in irc.servers:
|
||||||
if irc.servers[sid].internal and numeric in irc.servers[sid].users:
|
if irc.servers[sid].internal and numeric in irc.servers[sid].users:
|
||||||
return sid
|
return sid
|
||||||
|
|
||||||
def isInternalServer(irc, sid):
|
def isInternalServer(irc, sid):
|
||||||
"""<irc object> <sid>
|
"""Returns whether the given SID is an internal PyLink server."""
|
||||||
|
|
||||||
Returns whether <sid> is an internal PyLink PseudoServer.
|
|
||||||
"""
|
|
||||||
return (sid in irc.servers and irc.servers[sid].internal)
|
return (sid in irc.servers and irc.servers[sid].internal)
|
||||||
|
|
||||||
def isOper(irc, uid, allowAuthed=True, allowOper=True):
|
def isOper(irc, uid, allowAuthed=True, allowOper=True):
|
||||||
"""<irc object> <UID>
|
"""
|
||||||
|
Returns whether the given user has operator status on PyLink. This can be achieved
|
||||||
Returns whether <UID> has operator status on PyLink. This can be achieved
|
|
||||||
by either identifying to PyLink as admin (if allowAuthed is True),
|
by either identifying to PyLink as admin (if allowAuthed is True),
|
||||||
or having user mode +o set (if allowOper is True). At least one of
|
or having user mode +o set (if allowOper is True). At least one of
|
||||||
allowAuthed or allowOper must be True for this to give any meaningful
|
allowAuthed or allowOper must be True for this to give any meaningful
|
||||||
@ -390,10 +456,10 @@ def isOper(irc, uid, allowAuthed=True, allowOper=True):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def checkAuthenticated(irc, uid, allowAuthed=True, allowOper=True):
|
def checkAuthenticated(irc, uid, allowAuthed=True, allowOper=True):
|
||||||
"""<irc object> <UID>
|
"""
|
||||||
|
Checks whetherthe given user has operator status on PyLink, raising
|
||||||
Checks whether user <UID> has operator status on PyLink, raising
|
NotAuthenticatedError and logging the access denial if not.
|
||||||
NotAuthenticatedError and logging the access denial if not."""
|
"""
|
||||||
lastfunc = inspect.stack()[1][3]
|
lastfunc = inspect.stack()[1][3]
|
||||||
if not isOper(irc, uid, allowAuthed=allowAuthed, allowOper=allowOper):
|
if not isOper(irc, uid, allowAuthed=allowAuthed, allowOper=allowOper):
|
||||||
log.warning('(%s) Access denied for %s calling %r', irc.name,
|
log.warning('(%s) Access denied for %s calling %r', irc.name,
|
||||||
@ -401,10 +467,17 @@ def checkAuthenticated(irc, uid, allowAuthed=True, allowOper=True):
|
|||||||
raise NotAuthenticatedError("You are not authenticated!")
|
raise NotAuthenticatedError("You are not authenticated!")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getHostmask(irc, user):
|
def isManipulatableClient(irc, uid):
|
||||||
"""<irc object> <UID>
|
"""
|
||||||
|
Returns whether the given user is marked as an internal, manipulatable
|
||||||
|
client. Usually, automatically spawned services clients should have this
|
||||||
|
set True to prevent interactions with opers (like mode changes) from
|
||||||
|
causing desyncs.
|
||||||
|
"""
|
||||||
|
return isInternalClient(irc, uid) and irc.users[uid].manipulatable
|
||||||
|
|
||||||
Gets the hostmask of user <UID>, if present."""
|
def getHostmask(irc, user):
|
||||||
|
"""Gets the hostmask of the given user, if present."""
|
||||||
userobj = irc.users.get(user)
|
userobj = irc.users.get(user)
|
||||||
if userobj is None:
|
if userobj is None:
|
||||||
return '<user object not found>'
|
return '<user object not found>'
|
||||||
@ -421,8 +494,3 @@ def getHostmask(irc, user):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
host = '<unknown host>'
|
host = '<unknown host>'
|
||||||
return '%s!%s@%s' % (nick, ident, host)
|
return '%s!%s@%s' % (nick, ident, host)
|
||||||
|
|
||||||
hostmaskRe = re.compile(r'^\S+!\S+@\S+$')
|
|
||||||
def isHostmask(text):
|
|
||||||
"""Returns whether <text> is a valid hostmask."""
|
|
||||||
return bool(hostmaskRe.match(text))
|
|
||||||
|
12
world.py
12
world.py
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import threading
|
import threading
|
||||||
|
import subprocess
|
||||||
|
|
||||||
# Global variable to indicate whether we're being ran directly, or imported
|
# Global variable to indicate whether we're being ran directly, or imported
|
||||||
# for a testcase.
|
# for a testcase.
|
||||||
@ -16,3 +17,14 @@ schedulers = {}
|
|||||||
plugins = []
|
plugins = []
|
||||||
whois_handlers = []
|
whois_handlers = []
|
||||||
started = threading.Event()
|
started = threading.Event()
|
||||||
|
|
||||||
|
version = "<unknown>"
|
||||||
|
source = "https://github.com/GLolol/PyLink" # CHANGE THIS IF YOU'RE FORKING!!
|
||||||
|
|
||||||
|
# Only run this once.
|
||||||
|
if version == "<unknown>":
|
||||||
|
# Get version from Git tags.
|
||||||
|
try:
|
||||||
|
version = 'v' + subprocess.check_output(['git', 'describe', '--tags']).decode('utf-8').strip()
|
||||||
|
except Exception as e:
|
||||||
|
print('ERROR: Failed to get version from "git describe --tags": %s: %s' % (type(e).__name__, e))
|
||||||
|
Loading…
Reference in New Issue
Block a user